#define PROG_NAME "compute_normals_from_table"

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <i2.h>
#include <frgb.h>
#include <values.h>
#include <float_image.h>
#include <float_pnm_image_io.h>
#include <argparser.h>
#include "normais.h"
#include "imagem_vetores.h"
#include "tabela.h"
#include "super_tabela.h"
#include "hash.h"
#include <time.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <sys/times.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>

#define PROG_HELP \
  PROG_NAME " \\\n" \
  "    -nLights {NUM} \\\n" \
  "    -prefix {FILE_PREFIX} \\\n" \
  "    [ -refNormalMap {FILE_NAME} ] \\\n" \
  "    [ -channels {R | G | B| RG | RB | GB | RGB } \\\n" \
  "    -gamma {NUM} [ -gray ] \\\n" \
  "    -sceneImages {FILENAME}...  \\\n" \
  "    [ -rectangle {XMIN] {XMAX] {YMIN] {YMAX} ] \\\n" \
  "    [ -sigma {NUM} \\\n" \
  "    [ -logProbFunction {NUM} ] \\\n" \
  "    [ -albedoFunction {NUM} ] \\\n" \
  "    { -brute | -hash | -bruteAndHash } \\\n" \
  "    { -nsig | -alpha | -nsigOrAlpha } \\\n" \
  "    -tableFiles {FILENAME}... \\\n" \
  "    [ -debug {NP} {H[1]} {V[1]} ... {H[NP]} {V[NP]} ] \\\n" \
  "    " argparser_help_info_HELP ""

#define PROG_INFO \
  "NAME\n" \
  "  Etc. etc..\n" \
  "\n" \
  "SYNOPSIS\n" \
  PROG_HELP "\n" \
  "\n" \
  "OPTIONS" \
  "  Etc. etc.."

#define PROGRESS_METER 1

typedef enum { NSIG_APENAS = 0, NSIG_OR_ALPHA = 1, ALPHA_APENAS = 2  } alphaOption_t;
typedef enum { BRUTA_APENAS = 1, HASH_APENAS = 2, BRUTA_E_HASH = 3 , SUPER_TABLE =4 } opcao_hash_t;

typedef struct options_t {
	/* General parameters: */
  	int nLights;
  	char *prefix;
  	char *refNormalMap;          /* Reference normal map, or NULL if not specified. */
  	char *channels;              /* Channels to process (a substring of "RGB"). */
  	int hMin, hMax, vMin, vMax;  /* Rectangle of interest, or {[-BIG .. +BIG]^2} (PNM indices). */
   	/* Input image parameters: */
  	double gamma;
  	double bias;
  	bool_t gray;
  	char** scene_image_name;  /* Scene image filenames are {scene_image_name[0..nLights-1]} */
  	/* Observation matching parameters: */
  	double sigma;
  	double omg0;
  	double omg1;
  	int logProbFunction;
  	int albedoFunction;
  	int hashOption;
  	int alphaOption;
  	/* Bucket Grid only*/
  	int gridSize;
  	int showBucketsEPS;
  	int showBucketsPPM;
  	int showBucketsPLT;
  	int showBucketData;
  	int performanceTestOnly;
  	/*Super Table Options*/
  	int numSubsets;
  	int subsetSize;
  	int** subsets;
  	/* Signature table parameters: */
  	/* Debugging */
  	int nDebug;
  	i2_t *debug;  /* PNM-style indices of pixels to debug. */
  	/*Gauge Parameters*/
  	frgb_t gaugeAlbedo;
	  
} options_t;

options_t *parse_options(int argc, char **argv);

double user_cpu_time_usec(void);

double user_cpu_time_usec(void){
	struct tms buf;
	(void)times(&buf);
	return(1000000.0 * ((double) buf.tms_utime)/((double)sysconf(_SC_CLK_TCK)));
}

options_t *parse_options(int argc, char **argv)
{
	argparser_t *pp = argparser_new(stderr, argc, argv);
	argparser_set_help(pp, PROG_HELP);
  	argparser_set_info(pp, PROG_INFO);
  	argparser_process_help_info_options(pp);
	
  	options_t *o = (options_t *)malloc(sizeof(options_t));
	
  	if (argparser_keyword_present(pp, "-logProbFunction")) {
    		o->logProbFunction = argparser_get_next_int(pp, 0, 999);
  	} else {
    		o->logProbFunction = 0;
  	}
  	fprintf(stderr, "  -logProbFunction %d \\\n", o->logProbFunction);

  	if (argparser_keyword_present(pp, "-albedoFunction")) {
    		o->albedoFunction = argparser_get_next_int(pp, 0, 999);
  	} else {
    		o->albedoFunction = 0;
  	}
  	fprintf(stderr, "  -albedoFunction %d \\\n", o->albedoFunction);

  	if (argparser_keyword_present(pp, "-sigma")) {
    		o->sigma = argparser_get_next_double(pp, 0.0001, 10.0000);
  	} else {
    		o->sigma = 0.05;
  	}
  	fprintf(stderr, "  -sigma %lf \\\n", o->sigma);

  	if (argparser_keyword_present(pp, "-omega")) {
    		o->omg0 = argparser_get_next_double(pp, 0.0001, 10.0000);
    		o->omg1 = argparser_get_next_double(pp, 0.0001, 10.0000);
  	} else {
    		o->omg0 = 0.05;
    		o->omg1 = 0.005;
  	}
  	fprintf(stderr, "  -omega %lf %lf \\\n", o->omg0, o->omg1);

  	if (argparser_keyword_present(pp, "-channels")) {
    		o->channels = argparser_get_next(pp);
  	} else {
    		o->channels = "RGB";
  	}
  
  	fprintf(stderr, "  -channels %s \\\n", o->channels);

  	if (argparser_keyword_present(pp, "-rectangle")) {
    		o->hMin = argparser_get_next_int(pp, INT_MIN, INT_MAX);
    		o->hMax = argparser_get_next_int(pp, o->hMin, INT_MAX);
    		o->vMin = argparser_get_next_int(pp, INT_MIN, INT_MAX);
    		o->vMax = argparser_get_next_int(pp, o->vMin, INT_MAX);
    		fprintf(stderr, "  -rectangle %d %d  %d %d \\\n", o->hMin, o->hMax, o->vMin, o->vMax);
  	} else {
    		o->hMin = o->vMin = INT_MIN;
    		o->hMax = o->vMax = INT_MAX;
  	}

  	argparser_get_keyword(pp, "-nLights");
  	o->nLights = argparser_get_next_int(pp, 3, 1000);
  	fprintf(stderr, "  -nLights %d \\\n", o->nLights);


	argparser_get_keyword(pp, "-prefix");
  	o->prefix = argparser_get_next(pp);
  	fprintf(stderr, "  -prefix %s \\\n", o->prefix);
  
  	argparser_get_keyword(pp, "-sceneImages");
  	fprintf(stderr, "Imagens:\n");
  	o->scene_image_name = (char**) malloc(sizeof(char*)*(o->nLights));
  	int ind;
  	for (ind = 0; ind < (o->nLights); ind++){
    		o->scene_image_name[ind] = argparser_get_next(pp);
    		fprintf(stderr, "    S[%02d] = %s\n", ind, o->scene_image_name[ind]);
  	}

  
  
  	if (argparser_keyword_present(pp, "-gridSize")) {
    		o->gridSize = argparser_get_next_int(pp, 1, 1000000);
  	} else {
    		o->gridSize = -1;
  	}
  	fprintf(stderr,"   -gridSize %d \\\n",o->gridSize);

  
  	if(argparser_keyword_present(pp,"-showBucketsPPM")){
		o->showBucketsPPM = 1;
		fprintf(stderr,"    -showBucketsPPM \\\n");
  	} 
  	else{
		o->showBucketsPPM = 0;
  	}
  	if(argparser_keyword_present(pp,"-showBucketsEPS")){
		o->showBucketsEPS = 1;
		fprintf(stderr,"    -showBucketsEPS \\\n");
  	} 
  	else{
		o->showBucketsEPS = 0;
  	}
  
  	if(argparser_keyword_present(pp,"-showBucketsPLT")){
		o->showBucketsPLT = 1;
		fprintf(stderr,"    -showBucketsPLT \\\n");
  	} 
  	else{
		o->showBucketsPLT = 1;
  	}
  
  	o->showBucketData = 1;
  
  	if(argparser_keyword_present(pp,"-performanceTestOnly") ){
		o->performanceTestOnly = 1;
		fprintf(stderr,"    -performanceTestOnly\\\n");
   	}else{
		o->performanceTestOnly = 0;
   	}

  	if(argparser_keyword_present(pp,"-numSubsets") ){
		o->numSubsets = argparser_get_next_int(pp, 1, 1000000);
		fprintf(stderr,"    -numSubsets %d\\\n",o->numSubsets);
		argparser_get_keyword(pp,"-subsetSize");
		o->subsetSize = argparser_get_next_int(pp, 3, 100000);
		fprintf(stderr,"    -subsetSize %d\\\n",o->subsetSize);
		int random_subsets = !argparser_keyword_present(pp,"-subsets");
		if(random_subsets){
			fprintf(stderr,"USING Random Subsets\n");
		}else{
			fprintf(stderr,"USING User-Defined Subsets\n");
		}
		o->subsets = (int**)malloc(sizeof(int*)*(o->numSubsets));
		srand ( time(NULL) );
		for(ind = 0; ind < o->numSubsets;ind++){
			o->subsets[ind] = (int*)malloc(sizeof(int)*(o->subsetSize));
		}
		if(random_subsets){
			stGenerateSubsets(o->subsets,o->subsetSize,o->nLights,o->numSubsets);
		}else{
			for(ind = 0; ind < o->numSubsets; ind ++){
				int ind2;
				for(ind2 = 0; ind2 < o->subsetSize; ind2++){
					o->subsets[ind][ind2] = argparser_get_next_int(pp,0, 100000);
				}
			}
		}
		int n_uso[o->nLights];
		for(ind = 0; ind < o->nLights; ind++){
			n_uso[ind] = 0;
		}
		for(ind = 0; ind < o->numSubsets; ind ++){
			fprintf(stderr,"Subset[%d]: ",ind);
			int ind2;
			for(ind2 = 0; ind2 < o->subsetSize; ind2++){
				fprintf(stderr,"%03d ",o->subsets[ind][ind2]);
				n_uso[o->subsets[ind][ind2]]++;
			}
			fprintf(stderr,"\n");
		}
		fprintf(stderr,"Light Usage:\n");
		for(ind = 0; ind < o->nLights; ind++){
			fprintf(stderr,"Light[%03d]: %5d\n",ind,n_uso[ind]);
		}
	}else{
		o->numSubsets = 0;
		o->subsetSize = 0;
		o->subsets = NULL;
  	}
	
	if (argparser_keyword_present(pp, "-gaugeAlbedo")) {
    		o->gaugeAlbedo.c[0] = argparser_get_next_double(pp, 0.001, 1.000);
    		o->gaugeAlbedo.c[1] = argparser_get_next_double(pp, 0.001, 1.000);
    		o->gaugeAlbedo.c[2] = argparser_get_next_double(pp, 0.001, 1.000);
  	} else {
    		o->gaugeAlbedo.c[0] = o->gaugeAlbedo.c[1] = o->gaugeAlbedo.c[2] = 1.000;
  	}
  	fprintf(stderr, "  -gaugeAlbedo %5.3lf %5.3lf %5.3lf \\\n", o->gaugeAlbedo.c[0],o->gaugeAlbedo.c[1],o->gaugeAlbedo.c[2]);

  	argparser_get_keyword(pp, "-gamma");
  	o->gamma = argparser_get_next_double(pp, 0.100, 9.000);
  	o->bias = (o->gamma == 1.000 ? 0.000 : VIEW_BIAS); /* Hack... */
  	fprintf(stderr, "Gamma das imagens:%lf bias: %lf\n", o->gamma, o->bias);

  	o->gray = argparser_keyword_present(pp, "-gray");

  	if (argparser_keyword_present(pp, "-refNormalMap")) {
    		o->refNormalMap = argparser_get_next(pp);
    		fprintf(stderr, "  -refNormalMap %s \\\n", o->refNormalMap);
  	} else {
    		o->refNormalMap = NULL;
  	}

  	/* Analisa opção de hash: */
  	o->hashOption = BRUTA_APENAS;
  	if (argparser_keyword_present(pp, "-brute")){
    		o->hashOption = BRUTA_APENAS;
    		fprintf(stderr, "Força bruta apenas.\n");
  	} else if (argparser_keyword_present(pp, "-hash")){
    		o->hashOption = HASH_APENAS;
    		fprintf(stderr, "Hash (bucket grid) apenas.\n");
  	} else if (argparser_keyword_present(pp, "-bruteAndHash")){
    		o->hashOption = BRUTA_E_HASH;
    		fprintf(stderr, "Força bruta e hash, comparando os resultados.\n");
  	} else if (argparser_keyword_present(pp, "-superTable")){
    		o->hashOption = SUPER_TABLE;
    		fprintf(stderr, "Utilizando SuperTable.\n");
  	} else {
    		argparser_error(pp, "Must specify a hash/brute option");
  	}

  	/* Analisa opção de estimador de probabilidade usada na busca: */
  	o->alphaOption = NSIG_APENAS;
  	if (argparser_keyword_present(pp, "-nsig")) {
    		o->alphaOption = NSIG_APENAS;
    		fprintf(stderr, "Usando apenas comparação de assinaturas normalizadas.\n");
  	} else if (argparser_keyword_present(pp, "-alpha")) {
    		o->alphaOption = ALPHA_APENAS;
    		fprintf(stderr, "Usando apenas análise de Bayes.\n");
  	} else if (argparser_keyword_present(pp, "-nsigOrAlpha")) {
    		o->alphaOption = NSIG_OR_ALPHA;
    		fprintf(stderr, "Usando comp. de assinaturas, se erro grande usa análise de Bayes.\n");
  	} else {
    		argparser_error(pp, "Must specify an alpha/nsig option");
  	}

	/* Hash só pode ser usado com comparação de assinaturas normalizadas: */
  	if ((o->hashOption != BRUTA_APENAS) && (o->alphaOption == ALPHA_APENAS)) {
    		argparser_error(pp, "Hash é inútil quando se usa análise de Bayes apenas");
  	}

  	/* Opções de debug: */
  	if (argparser_keyword_present(pp, "-debug")) {
    		o->nDebug = argparser_get_next_int(pp, 0, INT_MAX);
    		o->debug = (i2_t*) malloc(o->nDebug * sizeof(i2_t));
    		int id;
    		for (id = 0; id < o->nDebug; id++) {
      			o->debug[id].c[0] = argparser_get_next_int(pp, 0, INT_MAX);
      			o->debug[id].c[1] = argparser_get_next_int(pp, 0, INT_MAX);
      			fprintf(stderr, "  debugging pixel [%d %d] (PNM indices)\n", o->debug[id].c[0], o->debug[id].c[1]);
    		}
  	} else {
    		o->nDebug = 0;
    		o->debug = NULL;
  	}
  	argparser_finish(pp);
	return o;
}

int main(int argc, char** argv){
	options_t *o = parse_options(argc, argv);

	/* Lê as imagens da cena e define o tamanho {nx,ny}: */
  	float_image_t  *S[o->nLights];
  	int nx = -1, ny = -1;
  	int i;
  	for(i = 0; i < o->nLights; i++){
    		fprintf(stderr, "Abrindo arquivo[%d] %s ... \n",i,o->scene_image_name[i]);
    		float_image_t *im = float_pnm_image_read(o->scene_image_name[i], o->gamma, o->bias, TRUE,TRUE,FALSE);
    		assert(im->sz[0]== 3);
    		if (i == 0){
			nx = im->sz[1]; ny = im->sz[2];
		}else{
			if ((nx != im->sz[1]) || (ny != im->sz[2])){
				fprintf(stderr, "Imagem S[%d] com tamanho inconsistente!\n", i); exit(1);
			}
      		}
     		S[i] = im;
     	
  	}
  	fprintf(stderr, "Imagens da cena Lidas.\n");

  	/* Ajusta o retângulo a calcular: */
  	if (o->hMin < 0)      { o->hMin = 0; }
  	if (o->hMax > nx - 1) { o->hMax = nx - 1; }
  	assert(o->hMin <= o->hMax);
  	if (o->vMin < 0)      { o->vMin = 0; }
  	if (o->vMax > ny - 1) { o->vMax = ny - 1; }
  	assert(o->vMin <= o->vMax);

  	/* Compute the toal number of pixel to be computed: */
  	int nx_comp = o->hMax - o->hMin + 1;
  	int ny_comp = o->vMax - o->vMin + 1;
  	int total_compute_pixels = nx_comp*ny_comp;
  	fprintf(stderr, "Total de iteracoes a executar: %d\n", total_compute_pixels);
  	fprintf(stderr, "Processando\n");
	
	/* Lê imagem com normais de referência, se houver: */
  	//imagem_vetores_t *nref = NULL;
	float_image_t* nref = NULL;
  	if (o->refNormalMap != NULL) {
    		//nref = le_imagem_vetores(o->refNormalMap);
		FILE* arq_nref = fopen(o->refNormalMap,"rt");
		assert(arq_nref != NULL);
		nref = float_image_read(arq_nref);
		fclose(arq_nref);
  	}
	
	/* Criando imagens de saída: */
  	float_image_t  *imagem_peso = float_image_new(1, nx, ny);
  	//float_image_t  *imagem_gab_select = float_image_new(1, nx, ny);
  	float_image_t  *imagem_albedo = float_image_new(3, nx, ny);
  	float_image_t  *imagem_Li_list[o->nLights];
  	float_image_t  *imagem_Ki_list[o->nLights];
  	for(i = 0; i < o->nLights;i++){
    		imagem_Li_list[i] = float_image_new(3, nx, ny);
    		imagem_Ki_list[i] = float_image_new(3, nx, ny);
  	}

  	fprintf(stderr, "Imagens de saída criadas.\n");

	int x,y,linha,stats_scan,stats_euclid;
  	int linha_bruta = 0;
  	int linha_hash = 0;
	
	/* Abre arquivos de saída: */
  	char *nome_im_peso = NULL;
  	//char *nome_im_gab_select = NULL;
  	asprintf(&nome_im_peso,"%s_weights.pgm",o->prefix);
  	//asprintf(&nome_im_gab_select,"%s_gab_select.pgm",o->prefix);

  	/* Tabelas de normais e pesos, indexadas por {[canal][x + y*nx]}: */
  	r3_t **normais_calculadas = malloc(3*sizeof(r3_t *));
  	double **logPrSG_busca =  malloc(3*sizeof(double *));

 	estima_log_prob_S_G_t *estLogPrSG = escolhe_estLogPrSG(o->logProbFunction);
  	estima_albedo_t *estAlbedo = escolhe_estAlbedo(o->albedoFunction);

  	/* Marca tempo de processamento real: */
  	time_t tempo_start = time(NULL);

  	struct tm *timeinfo = localtime(&tempo_start);

  	/* Loop sobre os canais: */
  	int canal;
  	for (canal = 0; canal < 3; canal++) {
		int process_channel = strchr(o->channels, "RGB"[canal]) != NULL;
		/* Abre arquivo da tabela de normais do canal: */
    		char *nome_arq_normais_canal = NULL;
    		asprintf(&nome_arq_normais_canal, "%s_%d_normals.fni", o->prefix, canal);
    		FILE* arq_normais_canal = fopen(nome_arq_normais_canal,"wt");
		float_image_t* fi_normais_canal = float_image_new(3,nx,ny);
//     		fprintf(arq_normais_canal,"tx = %d\n",nx);
//     		fprintf(arq_normais_canal,"ty = %d\n",ny);
// 		
		/* Abre arquivo da tabela de erros de normais por canal: */
    		FILE* arq_err_normais_canal = NULL;
		float_image_t* fi_err_normais_canal = NULL;
    		if(nref != NULL){
			char *nome_arq_err_normais_canal = NULL;
			asprintf(&nome_arq_err_normais_canal, "%s_%d_norm_errors.fni", o->prefix, canal);
			arq_err_normais_canal = fopen(nome_arq_err_normais_canal,"wt");
// 			fprintf(arq_err_normais_canal,"tx = %d\n",nx);
//     			fprintf(arq_err_normais_canal,"ty = %d\n",ny);
			fi_err_normais_canal = float_image_new(3,nx,ny);
    		}
		fprintf(stderr,"---------------------------------------------------------------------");
    		fprintf(stderr, "INICIO DO PROCESSAMENTO DO CANAL %d: %s", canal, asctime(timeinfo));

    		/* Aloca tabelas de pesos e normais calculadas para este canal: */
    		normais_calculadas[canal] = malloc(nx*ny*sizeof(r3_t));
    		logPrSG_busca[canal] = malloc(nx*ny*sizeof(double));
    		/* Inicializa as estatísticas de busca deste canal: */
    		int stats_brut = 0;
    		int stats_hash = 0;
		/* Carrega a tabela {tab} para este canal: */
    		Tabela* tab = NULL;
		char *nome_arq_table = NULL;
		asprintf(&nome_arq_table, "%s_%d_TableData.txt", o->prefix, canal);
		int num_linhas;
		if(process_channel){
			fprintf(stderr,"Loading signature table %s...",nome_arq_table);
			LoadTable(nome_arq_table,&tab);
			if(tab != NULL){
				num_linhas = get_num_linhas(tab);
				fprintf(stderr,"OK !\n");
			}else{
				fprintf(stderr,"FAILED !\n");
			}
			assert(tab != NULL);
		}
		fprintf(stderr, "Tabela carregada.\n");
		/* Gera a tabela de hash, se solicitado: */
    		bucketGrid* bg = NULL;
    		int tam_grid = 0;
    		if (o->hashOption != BRUTA_APENAS) {
      			if(process_channel){
      				bg = CriaBucketGrid(tab,o->gridSize);
      				fprintf(stderr, "Bucket grid gerada.\n");
      				tam_grid = get_tam_grid(bg);
			}
    		}
		
		/*Gera Super Tabelas se Solicitado*/
   		SuperTabela* ST_set[o->numSubsets];
   		if (o->hashOption == SUPER_TABLE) {
			int ind;
			if(process_channel){
				for(ind = 0; ind < o->numSubsets; ind++){
					fprintf(stderr,"CREATING SUPERTABLE %d\n",ind);
					ST_set[ind] = stCreate(
    					o->nLights, 
    					canal, 
    					o->subsetSize,
    					o->subsets[ind],
    					o->gridSize,
					tab
					);
				}
			}
   		}
		/* Estrutura para debug de pixels: */
    		normal_debug_opts_t *dbopt = notnull(malloc(sizeof(normal_debug_opts_t)), "no mem");
		/* Loop sobre pixels da cena: */
    		int contador = 0;
    		time_t last_tempo = time(NULL);
    		int x, y;
    		fprintf(stderr,"\n");
    		/* Marca o CPU time para o canal: */
      		double usec_busca_total = user_cpu_time_usec() ;	
		
		if(o->performanceTestOnly){
	
			for(y = 0 ;y < ny; y++){
     				for(x = 0; x < nx; x++){
					int h = x;            /* Indice horizontal estilo PPM. */
        				int v = ny - 1 - y;   /* Indice vertical estilo PPM. */	
        				bool_t process_pixel = ((h >= o->hMin) && (h <= o->hMax) && (v >= o->vMin) && (v <= o->vMax));
					if((!process_pixel ) || (!process_channel)) continue;
 					double SO[o->nLights];
	       				int preto = 1;
					double albedo = 0.0;        /* Albedo calculado do ponto {p} da cena, ou 0.0. */
		        		double logPrSG = -INF;      /* Maximo {log(Pr(S|G)} para {G} na tabela, ou {-oo} */
          				for (i = 0; i < o->nLights; i++){
    	    					SO[i] = float_image_get_sample(S[i], canal, x, y);
            					if (SO[i] > 0.0) { preto = 0; }
    	  				}
					if ((o->hashOption == BRUTA_APENAS) || (o->hashOption == BRUTA_E_HASH)){
            					if (o->alphaOption == NSIG_OR_ALPHA) {
              						linha_bruta = localiza_alternativa (
                					tab, SO, 
                					estLogPrSG, estAlbedo, o->sigma, o->omg0, o->omg1, 
                					&logPrSG, &albedo, 
                					NULL
              					);
	    					} else {
              						linha_bruta = localiza_simples (
                					tab, SO, 
                					estLogPrSG, estAlbedo, o->sigma, o->omg0, o->omg1,
                					&logPrSG, &albedo, 
                					NULL
              						);
						}
					}
    	  				if ((o->hashOption == HASH_APENAS) || (o->hashOption == BRUTA_E_HASH)){
    	    					if (preto) {
    	      						linha_hash = 0;
    	      						logPrSG = -INF;
	      						albedo = 0;
    	    					}
    	    					else {
    	      						linha_hash = localiza_normal_hash(bg, tab, SO, &logPrSG, &albedo);
    	      						stats_hash++;
    	    					}
    	  				}
			
			
    				}
			}
			usec_busca_total = user_cpu_time_usec() - usec_busca_total ;
    		}
		r3_t rnp;
    		for(y = 0 ;(y < ny) && (!o->performanceTestOnly); y++){
      			for(x = 0; x < nx; x++){
        			/* Decide se pixel deve ser processado: */
        			int h = x;            /* Indice horizontal estilo PPM. */
        			int v = ny - 1 - y;   /* Indice vertical estilo PPM. */
        			bool_t process_pixel = ((h >= o->hMin) && (h <= o->hMax) && (v >= o->vMin) && (v <= o->vMax));
				/* Decide se pixel deve ser debugado: */
        			int id;
				bool_t debug = FALSE;
				for (id = 0; id < o->nDebug; id++) {
          				if ((h == o->debug[id].c[0]) && (v == o->debug[id].c[1])) { debug = TRUE; }
    				}
				if (debug){ fprintf(stderr,"debugging pixel [%d %d] = PNM [%d %d]\n",x,y,h,v); }
				/* Aqui deveria vir a seleção do gabarito: */
    				/* int gabarito_selecionado = select_gabarito(tab,(double)x,(double)y); */
    				//int gabarito_selecionado = 0;
    				/* Preenche a imagem de selecao de gabarito: */
    				//double gsamp = ((double)gabarito_selecionado)/((double)o->nGauges);
    				//float_image_set_sample(imagem_gab_select, 0,x,y, gsamp);
			        double albedo = 0.0;        /* Albedo calculado do ponto {p} da cena, ou 0.0. */
        			r3_t snp = (r3_t){{0,0,0}}; /* Normal estimada do ponto {p} da cena, ou (0,0,0). */
        			double logPrSG = -INF;      /* Maximo {log(Pr(S|G)} para {G} na tabela, ou {-oo} */
        			if ((process_channel) && (process_pixel)) {
					/* Extrai a normal de referência {rnp} para este pixel: */
					rnp = (r3_t){{ 0,0,0 }};
					if(nref != NULL){
						//vetor_t rnpv = nref->pixel[y][x]; /* Normal de referência do pixel {p}. */
						//vetor_t rnpv;
						rnp.c[0] = float_image_get_sample(nref, 0, x,y);
						rnp.c[1] = float_image_get_sample(nref, 1, x,y);
						rnp.c[2] = float_image_get_sample(nref, 2, x,y);
						//rnp = (r3_t){{ rnpv.x, rnpv.y, rnpv.z }}; /* Idem como {r3_t}. */
					}
					/* Cria arquivo para debug dos cálculo de nrmal do pixel: */
					FILE *arq_debug_pixel = NULL;
					if (debug) {
						char *nome_arq_debug_pixel = NULL;
						asprintf(&nome_arq_debug_pixel, "%s_debug_%d_%04d_%04d.txt", o->prefix, canal, h, v);
						arq_debug_pixel = fopen(nome_arq_debug_pixel, "wt");
						(*dbopt) = (normal_debug_opts_t) {
							.c = canal,
							.hp = h,
							.vp = v,
							.arq_debug = arq_debug_pixel,
							.prefixo = o->prefix,
							.mapa_gabarito = TRUE,
							.normal_ref = rnp,
							.gera_plots_q = TRUE
						};
          				}
					/* Extrai o vetor de observação {SO} deste pixel: */
          				double SO[o->nLights];
					int preto = 1;
					for (i = 0; i < o->nLights; i++){
						SO[i] = float_image_get_sample(S[i], canal, x, y);
						if (SO[i] > 0.0) { preto = 0; }
					}
					/*Ticks antes da busca*/
					double so[o->nLights];
					double Smag;
					extrai_assinatura(SO, so, &Smag, o->nLights);
					/* Localiza {SO} na tabela, determinando a linha {linha} da mesma (ponto {q} do gabarito): */
					if ((o->hashOption == BRUTA_APENAS) || (o->hashOption == BRUTA_E_HASH)){
						if (o->alphaOption == NSIG_OR_ALPHA) {
							linha_bruta = localiza_alternativa (
							tab, SO, 
							estLogPrSG, estAlbedo, o->sigma, o->omg0, o->omg1, 
							&logPrSG, &albedo, 
							(debug ? dbopt : NULL)
							);
						} else {
							linha_bruta = localiza_simples (
							tab, SO, 
							estLogPrSG, estAlbedo, o->sigma, o->omg0, o->omg1,
							&logPrSG, &albedo, 
							(debug ? dbopt : NULL)
							);
						}
						stats_brut++;
					}
					if ((o->hashOption == HASH_APENAS) || (o->hashOption == BRUTA_E_HASH)){
						if (preto) {
							linha_hash = 0;
							logPrSG = -INF;
							albedo = 0;
						}
						else {
							linha_hash = localiza_normal_hash(bg, tab, SO, &logPrSG, &albedo);
							stats_hash++;
						}
					}
					if(o->hashOption == SUPER_TABLE){
						int ind_st;
						double best_prob= -MAXDOUBLE;
						double best_albedo;
						Tabela* best_tab;
						int best_line = -1;
						double test_distance;
						double test_albedo;
						int test_line;
						double SO_test_mag;
						int tableIndex;
						for(ind_st = 0; ind_st < o->numSubsets; ind_st++){
							test_line = stSearchNormal(
    							ST_set[ind_st],
    							SO,
							&SO_test_mag, 
    							&test_distance,
    							&test_albedo,
							&tableIndex
							);
							//printf("TEST %f\n",test_distance);
							if(tableIndex != -1){
								const double* go = get_intdir(tab,tableIndex);
								double Gmag = get_intmag(tab,tableIndex);
								//double test_prob = EstLogPrSG_09(so, Smag,go,Gmag,o->nLights, o->sigma,o->omg0,o->omg1);
								double test_prob = estLogPrSG(so, Smag,go,Gmag,o->nLights, o->sigma,o->omg0,o->omg1);
								if(best_prob <  test_prob){
									best_prob = test_prob;
									best_albedo = test_albedo;
									best_line = tableIndex;
									best_tab = stGetTable(ST_set[ind_st]);
								}
								if(debug){
									r3_t norm,norm2;
									norm = get_normal(tab, tableIndex);
									norm2 = get_normal(stGetTable(ST_set[ind_st]), test_line);
									fprintf(dbopt->arq_debug,"[%03d]: %04d (%6.3f ,%6.3f ,%6.3f ) ",ind_st,tableIndex,norm.c[0],norm.c[1],norm.c[2]);
									fprintf(dbopt->arq_debug," (%6.3f ,%6.3f ,%6.3f ) ",norm2.c[0],norm2.c[1],norm2.c[2]);
									int inder;
									const int* ind_luz = stGetIndLuz(ST_set[ind_st]);
									fprintf(dbopt->arq_debug,"SO { ");
									for(inder = 0; inder < o->subsetSize; inder++){
										fprintf(dbopt->arq_debug,"%f ",SO[ind_luz[inder]]);
									}
									fprintf(dbopt->arq_debug," }  ");
									fprintf(dbopt->arq_debug,"GO { ");
									for(inder = 0; inder < o->subsetSize; inder++){
										fprintf(dbopt->arq_debug,"%f ",go[ind_luz[inder]]*Gmag);
									}
									fprintf(dbopt->arq_debug," } Dist: %6.4f\n\n",test_prob);
								}
							}
						}
						albedo = best_albedo;
						logPrSG = best_prob;
						linha_hash = best_line;
						//tab = best_tab;	
	  				}
					//printf("BEST %f\n",logPrSG);
	  				if ((o->hashOption == BRUTA_E_HASH) && (linha_hash != linha_bruta)){
    	    					fprintf(stderr, "BUSCA HASH DIFERENTE DE BUSCA BRUTA\n");
    	    					fprintf(stderr, "HASH = "); print_linha(tab,linha_hash);
    	    					fprintf(stderr, "BRUT = "); print_linha(tab,linha_bruta);
    	  				}
    	  				linha = ((o->hashOption == BRUTA_APENAS) || (o->hashOption == BRUTA_E_HASH) ? linha_bruta : linha_hash);
	
					/* Obtém o vetor de observações {GO} do ponto {q} do gabarito: */
					double GO[o->nLights]; /* Vetor de observação do ponto {q} no gabarito, ou (0..). */
					const double *go;      /* Assinatura normalizada de {q}, ou (0..). */
					double Gmag;           /* Magnitude de {GO}, ou 0. */
					if(linha >= 0){
						go = get_intdir(tab, linha);  /* Assinatura do ponto {q} no gabarito. */
						Gmag = get_intmag(tab, linha);  /* Magnitude do vetor de observações do ponto {q}. */
						for (i = 0; i < o->nLights; i++){ GO[i] = go[i] * Gmag; }
   	  				}
    	  				else{
            					for (i = 0; i < o->nLights; i++){ GO[i] = 0.0; }
						go = GO; /* Dirty trick. */
						Gmag = 0.0;
					}
					
					/* Considera que a normal do ponto é a normal do gabarito: */
          				snp = get_normal(tab,linha); /* Normal do ponto {q} do gabarito. */

          				/* Dados para estudar os alphas: */
					/* Estima log verossimilhança {log(Pr(S|G)} para a linha escolhida: */
					if (arq_debug_pixel != NULL){ fclose(arq_debug_pixel);}
					for(i = 0; i < o->nLights;i++){
						double Li = go[i]*Gmag;
						double Ki = SO[i]/(albedo*Li);
						// printf("%f %f %f\n",go[i],Gmag,get_intmag(tab, linha));
						// printf("%f %f %f %f\n",albedo,o->gaugeAlbedo[canal],Li,Ki);
						float_image_set_sample(imagem_Li_list[i],canal,x,y,Li);
						float_image_set_sample(imagem_Ki_list[i],canal,x,y,Ki);
					}
        			}/*fim do processamento do pixel*/

				/* Armazena o albedo em {imagem_albedo}: */
				double albedo_dimming = 0.90; /* Fator de redução para caso do albedo ser maior que 1.0. */
				float_image_set_sample(imagem_albedo, canal, x, y, albedo_dimming*albedo);
				
				/* Grava normal no mapa de normais: */
				float_image_set_sample(fi_normais_canal,0,x,y,snp.c[0]);
				float_image_set_sample(fi_normais_canal,1,x,y,snp.c[1]);
				float_image_set_sample(fi_normais_canal,2,x,y,snp.c[2]);
				//fprintf(arq_normais_canal,"%d %d %f %f %f\n", x, y, snp.c[0], snp.c[1], snp.c[2]);
				if(nref != NULL){
					r3_t enp ;
					r3_sub(&snp,&rnp,&enp);
					//fprintf(arq_err_normais_canal,"%d %d %f %f %f\n", x, y, enp.c[0], enp.c[1], enp.c[2]);
					float_image_set_sample(fi_err_normais_canal,0,x,y,enp.c[0]);
					float_image_set_sample(fi_err_normais_canal,1,x,y,enp.c[1]);
					float_image_set_sample(fi_err_normais_canal,2,x,y,enp.c[2]);
				}
				/* Salva normal e log verossimilhança para imagens médias de canais: */
				int ip = x + y*nx;
        			normais_calculadas[canal][ip] = snp;
        			logPrSG_busca[canal][ip] = logPrSG;
	
				/*perfumaria - A.K.A - contador de progresso*/
				if (process_pixel) {
          				/* Imprime relatório de progresso: */
    	  				contador++;
					if (PROGRESS_METER) {
						if ((contador % 10) == 0) {
							fprintf(stderr,"\033[1A");
							double normals_per_sec = contador/(double)(time(NULL) - last_tempo  + 0.001);
							double total_secs_remaining = (total_compute_pixels - contador)/normals_per_sec;
							int total_seconds = (int)floor(total_secs_remaining);
							int hour = total_seconds/(60*60);
							int min = (total_seconds - (hour*60*60))/60;
							int sec = (total_seconds - (hour*60*60) - (min*60));
							fprintf(stderr,"[%d][%9d] of [%9d] - %6.6f%% - %6.6f n/s   - %02d h %02d m %02d s       \n",
							canal,contador,total_compute_pixels, contador*100.0/total_compute_pixels,normals_per_sec,hour,min,sec
							);
						}
					} else {
						int step = nx*ny/10;
						if ((contador % step == 0) || (contador == nx*ny)) {
							fprintf(stderr, "canal %d, %6d pixels - %5.5lf%%\n", canal, contador, 100*((double)contador)/(nx*ny));
						}
					}
				}
			}/*fim eixo X*/
    		}/*fim eixo Y*/
		if(! o->performanceTestOnly ) usec_busca_total = user_cpu_time_usec() - usec_busca_total ;
    		/* Fecha arquivo de normais do canal: */
		//fprintf(arq_normais_canal,"\n");
		float_image_write(arq_normais_canal,fi_normais_canal);
		 fclose(arq_normais_canal);
		
		if(nref != NULL){
			//fprintf(arq_err_normais_canal,"\n"); 
			float_image_write(arq_err_normais_canal,fi_err_normais_canal);
			fclose(arq_err_normais_canal);
		}

		/* Escreve estatísticas da busca para este canal: */
    		double fpercent = (stats_brut*100.0/total_compute_pixels)/3.0;
    		fprintf(stderr, "Normais calculadas por forca bruta - %d ( %3.2lf%% ) \n",stats_brut,fpercent);
    		fpercent = (stats_hash*100.0/total_compute_pixels)/3.0;
    		fprintf(stderr, "Normais calculadas por hash - %d ( %3.2lf%% ) \n",stats_hash,fpercent);

    		stats_euclid = 0;
    		stats_scan = 0;
    		int i,j;

		/* Anexa estatísticas de buckets no arquivo: */
    		char *nome_arq_estat = NULL;
    		asprintf(&nome_arq_estat, "%s_times_%02d.txt", o->prefix, canal);
    		FILE* arq_estat = fopen(nome_arq_estat,"wt");
    		if(bg != NULL){
      			for(i = 0; i < get_tam_grid(bg); i++){
    				for(j = 0; j < tam_grid; j++){
    	  				stats_euclid += acessaMatriz_Statistic_Euclid(bg)[i][j];
    	  				stats_scan += acessaMatriz_Statistic_Scan(bg)[i][j];
    				}
      			}
      			double buckets =  tam_grid*tam_grid;
      			double entries_per_bucket = ((double)num_linhas)/buckets;
      			int queries = stats_brut + stats_hash;
      			double usec_per_query = usec_busca_total/(queries);
      			double euclids_per_query = ((double) stats_euclid)/(queries);
      			double bucket_scans_per_query = ((double) stats_scan)/(queries);
      			fprintf(arq_estat,"#Queries N T entries_per_bucket t_tot t_avg d_med b_med \n");
      			fprintf(arq_estat," %d %d %d", queries, tam_grid, num_linhas);
      			fprintf(arq_estat," %lf %lf %lf", entries_per_bucket, usec_busca_total, usec_per_query);
      			fprintf(arq_estat,"  %lf %lf\n", euclids_per_query, bucket_scans_per_query);
    		}
    		fclose(arq_estat);
    		free(nome_arq_estat);
		/* Escreve estatísticas da tabela de hash deste canal: */
    		if((o->hashOption != BRUTA_APENAS) && (o->hashOption != SUPER_TABLE) && (process_channel)){
      			char *nome_arq_grid_show = NULL;
      			asprintf(&nome_arq_grid_show, "%s_t_%d_", o->prefix, canal);
		        if(o->showBucketsEPS)   showBucketsEPS(nome_arq_grid_show,bg,get_num_linhas(tab));
      			if(o->showBucketsPPM)   showBucketsPPM(nome_arq_grid_show,bg,get_num_linhas(tab));
      			if(o->showBucketsPLT)   showBucketsPLT(nome_arq_grid_show,bg,get_num_linhas(tab));
      			if(o->showBucketData)   showBucketsData(nome_arq_grid_show,bg,get_num_linhas(tab));
			showBucketGridStatistic(nome_arq_grid_show,bg);
			char *nome_arq_grid_stats = NULL;
      			asprintf(&nome_arq_grid_stats, "%s_bucket_grid_stats_%d.txt", o->prefix, canal);
      			FILE* arq_grid_stats = fopen(nome_arq_grid_stats, "wt");
      			flushBucketGrid(arq_grid_stats, bg);
      			fclose(arq_grid_stats); free(nome_arq_grid_stats);
    		}else if((o->hashOption == SUPER_TABLE) && (process_channel)){
			char *nome_arq_grid_show = NULL;
			int ind;
			for(ind =0 ; ind < o->numSubsets; ind++){
				asprintf(&nome_arq_grid_show, "%s_t_s_%d_%d_", o->prefix,ind, canal);
				bucketGrid* buck = stGetBucketGrid(ST_set[ind]);
				if(o->showBucketsEPS)   showBucketsEPS(nome_arq_grid_show,buck,get_num_linhas(tab));
      				if(o->showBucketsPPM)   showBucketsPPM(nome_arq_grid_show,buck,get_num_linhas(tab));
      				if(o->showBucketsPLT)   showBucketsPLT(nome_arq_grid_show,buck,get_num_linhas(tab));
				if(o->showBucketData)   showBucketsData(nome_arq_grid_show,bg,get_num_linhas(tab));
				//showBucketGridStatistic(nome_arq_grid_show,buck);
			}
		}else{
			fprintf(stderr, "TOT DIST EUCLIDIANAS %d",total_compute_pixels*num_linhas);
		}
		
	}
   
	/* Abre arquivo de tabela de médias de pesos: */
	char *nome_arq_pesos = NULL;
	asprintf(&nome_arq_pesos,"%s_weights.fni",o->prefix);
	FILE* arq_pesos = fopen(nome_arq_pesos,"wt");
	float_image_t* fi_pesos = float_image_new(1,nx,ny);
// 	fprintf(arq_pesos,"tx = %d\n",nx);
// 	fprintf(arq_pesos,"ty = %d\n",ny);
	fprintf(stderr, "Arquivos Abertos\n");
	

	/* Abre arquivo da tabela de médias de normais: */
	char *nome_arq_normais_media = NULL;
	asprintf(&nome_arq_normais_media, "%s_%d_normals.fni", o->prefix, 3);
	FILE* arq_normais_media = fopen(nome_arq_normais_media,"wt");
// 	fprintf(arq_normais_media,"tx = %d\n",nx);
// 	fprintf(arq_normais_media,"ty = %d\n",ny);
	float_image_t* fi_normais_media = float_image_new(3,nx,ny);
	
	FILE* arq_normais_media_err = NULL;
	float_image_t* fi_normais_media_err = NULL;
	if(nref != NULL){
		char* nome_arq_normais_media_err = NULL;
		asprintf(&nome_arq_normais_media_err, "%s_%d_norm_errors.fni", o->prefix, 3);
		arq_normais_media_err = fopen(nome_arq_normais_media_err,"wt");
// 		fprintf(arq_normais_media_err,"tx = %d\n",nx);
// 		fprintf(arq_normais_media_err,"ty = %d\n",ny);
		fi_normais_media_err = float_image_new(3,nx,ny);
	}

	/*Grava imagens Li e Ki*/
	for(i = 0; i < o->nLights;i++){
		char *nome_arq_list = NULL;
		asprintf(&nome_arq_list,"%s_L_%02d.ppm",o->prefix,i);
		float_pnm_image_write(nome_arq_list, imagem_Li_list[i], o->gamma, o->bias,TRUE,TRUE,TRUE);
		free(nome_arq_list); nome_arq_list = NULL;
		asprintf(&nome_arq_list,"%s_K_%02d.ppm",o->prefix,i);
		float_pnm_image_write(nome_arq_list, imagem_Ki_list[i], o->gamma, o->bias,TRUE,TRUE,TRUE);
		free(nome_arq_list);
	}	
	
	/* Calcula imagens e grava tabelas com médias de normais e pesos dos três canais: */
 
	for (y = 0; (y < ny) && (!o->performanceTestOnly); y++) {
		for (x = 0; x < nx; x++) {
			int ip = x + nx*y;
			/* Converte {logPrSG} em pesos: */
			double logPrMax = fmax(logPrSG_busca[0][ip], fmax(logPrSG_busca[1][ip], logPrSG_busca[2][ip]));
			double peso_0 = exp(fmax(logPrSG_busca[0][ip] - logPrMax, -60.0));
			double peso_1 = exp(fmax(logPrSG_busca[1][ip] - logPrMax, -60.0));
			double peso_2 = exp(fmax(logPrSG_busca[2][ip] - logPrMax, -60.0));
			/* Combina as normais calculadas para os três canais: */
			r3_t normal_media = calcula_media_das_normais
			( peso_0, normais_calculadas[0][ip],
			peso_1, normais_calculadas[1][ip],
			peso_2, normais_calculadas[2][ip]
			);
			//fprintf(arq_normais_media,"%d %d %f %f %f\n", x, y, normal_media.c[0], normal_media.c[1], normal_media.c[2]);
			float_image_set_sample(fi_normais_media,0,x,y,normal_media.c[0]);
			float_image_set_sample(fi_normais_media,1,x,y,normal_media.c[1]);
			float_image_set_sample(fi_normais_media,2,x,y,normal_media.c[2]);
			if(nref != NULL){
				r3_t rnp;
				//vetor_t rnpv = nref->pixel[y][x]; /* Normal de referência do pixel {p}. */
				rnp.c[0] = float_image_get_sample(nref, 0, x,y);
				rnp.c[1] = float_image_get_sample(nref, 1, x,y);
				rnp.c[2] = float_image_get_sample(nref, 2, x,y);
				//rnp = (r3_t){{ rnpv.x, rnpv.y, rnpv.z }}; /* Idem como {r3_t}. */
				r3_t enp ;
				r3_sub(&normal_media,&rnp,&enp);
				//fprintf(arq_normais_media_err,"%d %d %f %f %f\n", x, y, enp.c[0], enp.c[1], enp.c[2]);
				float_image_set_sample(fi_normais_media_err,0,x,y,enp.c[0]);
				float_image_set_sample(fi_normais_media_err,1,x,y,enp.c[1]);
				float_image_set_sample(fi_normais_media_err,2,x,y,enp.c[2]);
			}
			/* Combina os logPrSG_busca calculados para os três canais: */
			double peso = calcula_media_dos_pesos
			( peso_0, normais_calculadas[0][ip],
			peso_1, normais_calculadas[1][ip],
			peso_2, normais_calculadas[2][ip],
			normal_media
			);
			//fprintf(arq_pesos,"%4d %4d %8.6f \n", x, y, peso);
			float_image_set_sample(fi_pesos,0,x,y,peso);
			/* Salva peso na imagem de pesos (escala linear): */
			float_image_set_sample(imagem_peso, 0, x, y, peso);
		}
	}

	/* Fecha os arquivos de médias: */
	float_image_write(arq_pesos,fi_pesos);
	fclose(arq_pesos);
	float_image_write(arq_normais_media,fi_normais_media);
	fclose(arq_normais_media);
	if(nref != NULL){
		float_image_write(arq_normais_media_err,fi_normais_media_err);
		fclose(arq_normais_media_err);
	}
	
	/* Grava e libera as imagens resultado: */
	int ind;
	if(!o->performanceTestOnly){
		float_pnm_image_write(nome_im_peso, imagem_peso, 1.000, 0.000,TRUE,TRUE,TRUE);
		//float_pnm_image_write(nome_im_gab_select, imagem_gab_select, 1.000, 0.000);
		for (ind = 0; ind < o->nLights; ind++){ float_image_free(S[ind]); }
		char *nome_im_albedo = NULL;
		asprintf(&nome_im_albedo, "%s_albedo.ppm", o->prefix);
		float_pnm_image_write(nome_im_albedo, imagem_albedo, VIEW_GAMMA, VIEW_BIAS,TRUE,TRUE,TRUE);
	}
	fprintf(stderr, "Concluido!\nO programa rodou com sucesso!\n");

	time_t tempo_fim = time(NULL);
	timeinfo = localtime(&tempo_fim);
	fprintf(stderr, "FIM DO PROCESSAMENTO: %s",asctime(timeinfo));
	
	double usec_total = difftime(tempo_fim,tempo_start);
	fprintf(stderr, "Executado em %6.3f segs.\n",usec_total/1000000);
	
	return 0;
}
