#include <stdio.h>
#include <stdlib.h>
#ifndef CYGWIN_NT_40
#include <strings.h>
#endif
#include <math.h>
#include "structs.h"
#include "binencode.h"

extern int NUM_GENOMES;

int mapToPolarity(int gene, int num_genes) {
  if (gene > num_genes)
    gene -= (2*num_genes+1);
  return (gene);
}

int mapToLinear(int gene, int num_genes) {
  if (gene < 0)
    gene += (2*num_genes+1);
  return (gene);
}

void fillGenomes(struct genome_struct *g, int num_genomes, int num_genes) {
  int i, j;
  int r, tmp;

  g[0].genome_num = 1;
  sprintf(g[0].gnamePtr,"Genome %3d",g[0].genome_num);
  for (j=0 ; j<num_genes ; j++)
    g[0].genes[j] = (j+1);

  for (i=1 ; i<num_genomes ; i++) {
    g[i].genome_num = i+1;
    sprintf(g[i].gnamePtr,"Genome %3d",g[i].genome_num);
    for (j=0 ; j<num_genes ; j++) {
      if (random() <= MAX_RAND/2)
	g[i].genes[j] = (j+1);
      else
	g[i].genes[j] = - (j+1);
    }
    for (j=num_genes-1 ; j>=1 ; j--) {
      r = (int)floor((double)(j-1) * ((double)random()/(double)MAX_RAND));
      tmp = g[i].genes[r];
      g[i].genes[r] = g[i].genes[j];
      g[i].genes[j] = tmp;
    }
  }
  return;
}

void fillGenomeRand(int *genes, int num_genes) {
  int j;
  int r, tmp;

  for (j=0 ; j<num_genes ; j++) {
    if (random() <= MAX_RAND/2)
      genes[j] = (j+1);
    else
      genes[j] = - (j+1);
  }
  for (j=num_genes-1 ; j>=1 ; j--) {
    r = (int)floor((double)(j-1) * ((double)random()/(double)MAX_RAND));
    tmp      = genes[r];
    genes[r] = genes[j];
    genes[j] = tmp;
  }
  return;
}

void printGenomes(struct genome_struct *g, int num_genomes, int num_genes) {
  int i, j;

  fprintf(outfile,"Genomes:\n\n");
  for (i=0 ; i<num_genomes ; i++) {
    fprintf(outfile,"(%3d) %16s:",g[i].genome_num, g[i].gnamePtr);
    for (j=0 ; j<num_genes ; j++) {
      fprintf(outfile,"%3d ",g[i].genes[j]);
    }
    fprintf(outfile,"\n");
  }
  fprintf(outfile,"\n");
  return;
}

void createGeneAdjMatrix(int ***adj, int num_genes) {
  int i, j, num;
  num = 2*num_genes + 1;
  *adj = (int **)malloc(num * sizeof(int *));
  if (*adj == (int **)NULL)
    fprintf(stderr,"ERROR: adj NULL\n");
  for (i=0 ; i<num ; i++) {
    (*adj)[i] = (int *)malloc(num * sizeof(int));
    if ((*adj)[i] == (int *)NULL)
      fprintf(stderr,"ERROR: adj[%d] NULL\n",i);
    for (j=0 ; j<num ; j++)
      (*adj)[i][j] = 0;
  }
  return;
}

void destroyGeneAdjMatrix(int **adj, int num_genes) {
  int i, num;
  num = 2*num_genes;
  for (i=num-1 ; i>=0 ; i--)
    free(adj[i]);
  free(adj);
  return;
}

void setAdj(int **adj, int num_genes, int g0, int g1) {
  int index0, index1;

  index0 = mapToLinear(g0, num_genes);
  index1 = mapToLinear(g1, num_genes);
  adj[index0][index1] = 1;
  
  index0 = mapToLinear(-g1, num_genes);
  index1 = mapToLinear(-g0, num_genes);
  adj[index0][index1] = 1;
  
  return;
}

void fillGeneAdjMatrix(int **adj, struct genome_struct *mygenomes,
		       int num_genomes, int num_genes) {
  int i, j;
  int g0, g1;

  for (i=0 ; i<num_genomes ; i++) {
    for (j=0 ; j<num_genes ; j++) {
      g0 = mygenomes[i].genes[j];
      g1 = mygenomes[i].genes[(j+1)%num_genes];
      setAdj(adj, num_genes, g0, g1);
    }
  }
  return;
}

void printGeneAdjMatrix(int **adj, int num_genes) {
  int i, j;
  int num;

  num = 2*num_genes;
  
  fprintf(outfile,"    \t");
  for (j=1 ; j<=num ; j++) {
    fprintf(outfile,"%3d \t", mapToPolarity(j, num_genes));
  }    
  fprintf(outfile,"\n");

  for (i=1 ; i<=num ; i++) {
    fprintf(outfile,"%3d: \t",mapToPolarity(i, num_genes));
    for (j=1 ; j<=num ; j++) {
      fprintf(outfile,"%3d \t", mapToPolarity(adj[i][j], num_genes));
    }
    fprintf(outfile,"\n");
  }
  fprintf(outfile,"\n");
  return;
}


int getAdjNum(int **adj, int num_genes) {
  int i, j;
  int num;
  int k;

  num = 2*num_genes;
  k = 0;
  
  for (i=1 ; i<=num ; i++)
    for (j=1 ; j<=num ; j++)
      k += adj[i][j];

  k /= 2;
  
  return(k);
}

int containsPair(struct genome_struct *g, int G0, int G1, int num_genes) {
  int found;
  int i, p0, p1;

  found = FALSE;
  i=0;
  while ((i<num_genes) && (!found)) {
    p0 = g->genes[i];
    p1 = g->genes[(i+1) % num_genes];
    found = ((p0==G0 && p1==G1) || (p0==-G1 && p1==-G0));
    i++;
  }

  return(found);
}

void encodeAdjGenomes(int **adj, struct genome_struct *mygenomes,
		      int num_genomes, int num_genes) {
  int i, j, v, num;
  int width;
  int *geneVectorG0;
  int *geneVectorG1;

  width = getAdjNum(adj, num_genes);
  
  geneVectorG0 = (int *)malloc(width * sizeof(int));
  if (geneVectorG0 == (int *)NULL)
    fprintf(stderr,"ERROR: geneVectorG0 NULL\n");
  geneVectorG1 = (int *)malloc(width * sizeof(int));
  if (geneVectorG1 == (int *)NULL)
    fprintf(stderr,"ERROR: geneVectorG1 NULL\n");

  v = 0;
  for (i=1 ; i<num_genes ; i++) {
    geneVectorG0[v] = i;
    geneVectorG1[v] = i+1;
    adj[mapToLinear(     i, num_genes)][mapToLinear(i+1, num_genes)] = 0;
    adj[mapToLinear(-(i+1), num_genes)][mapToLinear( -i, num_genes)] = 0;
    v++;
  }
  geneVectorG0[v] = num_genes;
  geneVectorG1[v] = 1;
  adj[mapToLinear(num_genes, num_genes)][mapToLinear(          1, num_genes)] = 0;
  adj[mapToLinear(       -1, num_genes)][mapToLinear( -num_genes, num_genes)] = 0;
  v++;

  num = 2*num_genes;
  for (i=1 ; i<=num ; i++)
    for (j=1 ; j<=num ; j++)
      if (adj[i][j]) {
	geneVectorG0[v] = mapToPolarity(i, num_genes);
	geneVectorG1[v] = mapToPolarity(j, num_genes);
	adj[mapToLinear( i, num_genes)][mapToLinear( j, num_genes)] = 0;
	adj[mapToLinear(-j, num_genes)][mapToLinear(-i, num_genes)] = 0;
	v++;
      }

  
#if DEBUG
  fprintf(outfile,"VECTOR GENES:\n");
  fprintf(outfile,"width: %d    v: %d\n",width, v);
  for (i=0 ; i<v ; i++)
    fprintf(outfile,"V[%3d]: ( %4d, %4d)\n",i,geneVectorG0[i], geneVectorG1[i]);
  fprintf(outfile,"\n");
#endif
  
  for (i=0 ; i<num_genomes ; i++) {
    mygenomes[i].encoding = (char *)malloc((width+1)*sizeof(char));
    if (mygenomes[i].encoding == (char *)NULL)
      fprintf(stderr,"ERROR: encoding NULL\n");
    for (j=0 ; j<width ; j++) {
      if (containsPair(mygenomes+i,
		       geneVectorG0[j], geneVectorG1[j],
		       num_genes)) {
	mygenomes[i].encoding[j] = '1';
      }
      else {
	mygenomes[i].encoding[j] = '0';
      }
    }
    mygenomes[i].encoding[width] = '\0';
  }


  free(geneVectorG1);
  free(geneVectorG0);
  return;
}

void freeGenomes(struct genome_struct *mygenomes, int num_genomes) {
  int i;
  for (i=0 ; i<num_genomes ; i++)
    free(mygenomes[i].encoding);
  free(mygenomes);
  return;
}


void printGenomeEncodings(struct genome_struct *g, int num_genomes) {
  int i;
  fprintf(outfile,"GENOME ENCODINGS:\n");
  for (i=0 ; i<num_genomes ; i++) {
    fprintf(outfile,"%s: %s\n",g[i].gnamePtr, g[i].encoding);
  }
  fprintf(outfile,"\n");

  return;  
}

void encodeGenomes(struct genome_struct *mygenomes,
		   int num_genomes, int num_genes) {
  int **adj;

  createGeneAdjMatrix(&adj, num_genes);

  fillGeneAdjMatrix(adj, mygenomes, num_genomes, num_genes);

#if DEBUG
  printGeneAdjMatrix(adj, num_genes);
#endif

  encodeAdjGenomes(adj, mygenomes, num_genomes, num_genes);

  destroyGeneAdjMatrix(adj, num_genes);

  return;
}

int hamming_distance_from_encoding(struct genome_struct *g1,
				   struct genome_struct *g2) {
  int i, num;
  int width;
  num=0;

  width = strlen(g1->encoding);
  
  if (width != strlen(g2->encoding))
    fprintf(stderr,"ERROR: hamming_distance_from_encoding\n");
  
  for (i=0 ; i<width ; i++) {
    num += (g1->encoding[i] ^ g2->encoding[i]);
  }
  return (num);
}

void findAllHammingDistancesFromEncoding(struct genome_struct *mygenomes,
					 int num_genomes, int num_genes) {
  int i,j, dist;

  fprintf(outfile,"HAMMING DISTANCE From ENCODING:\n");
  for (i=0 ; i<num_genomes ; i++) {
    for (j=i+1; j<num_genomes ; j++) {
      dist = hamming_distance_from_encoding(mygenomes+i, mygenomes+j);
      fprintf(outfile,"The Hamming Distance between %s and %s is %3d (BP=%3d)\n",
	      mygenomes[i].gnamePtr, mygenomes[j].gnamePtr,
	      dist, dist/2);
    }
  }
  fprintf(outfile,"\n");
  return;
}

int hamming_distance_quad(struct genome_struct *g1, struct genome_struct *g2,
			  int num_genes) {
  int i, num;
  int gene0, gene1;
  num=0;

  for (i=0 ; i<num_genes ; i++) {
    gene0 = g1->genes[i];
    gene1 = g1->genes[(i+1)%num_genes];
    num += containsPair(g2, gene0, gene1, num_genes);
  }

  num = num_genes - num;
  return (num);
}


#define setAdj(g0, g1, gAdj, n) \
    if ((g0)>0) \
      gAdj[(g0)] = (g1); \
    else \
      gAdj[(n) - (g0)] = (g1); \
    if ((g1)>0) \
      gAdj[(n) + (g1)] = -(g0); \
    else \
      gAdj[-(g1)] = -(g0);

#define checkAdj(g0, g1, gAdj, n) \
    if ((g0)>0) { \
      if ((g1)>0) { \
        if ((gAdj[(g0)] != (g1)) && (gAdj[(n) + (g1)] != -(g0))) \
          num++; \
      } \
      else { \
        if ((gAdj[(g0)] != (g1)) && (gAdj[-(g1)] != -(g0))) \
          num++; \
      } \
    } \
    else { \
      if ((g1)>0) { \
        if ((gAdj[(n) - (g0)] != (g1)) && (gAdj[(n) + (g1)] != -(g0))) \
          num++; \
      } \
      else { \
        if ((gAdj[(n) - (g0)] != (g1)) && (gAdj[-(g1)] != -(g0))) \
          num++; \
      } \
    }

int hamming_distance(struct genome_struct *g1, struct genome_struct *g2,
                     int num_genes, int CIRCULAR, int *geneAdj) {
  register int i;
  register int gene0, gene1, firstgene, g1first, g1last;
  int num;

  g1first = 0;
  g1last  = 0;
  num=0;

  firstgene = gene0 = g1->genes[0];
  gene1 = g1->genes[1];
  setAdj(gene0, gene1, geneAdj, num_genes);
  for (i=1 ; i<num_genes-1 ; i++) {
    gene0 = gene1;
    gene1 = g1->genes[i+1];
    setAdj(gene0, gene1, geneAdj, num_genes);
  }
  if (CIRCULAR) {
    gene0 = gene1;
    gene1 = firstgene;
    setAdj(gene0, gene1, geneAdj, num_genes);
  }
  else {
    g1first = firstgene;
    g1last  = gene1;
  }
  
  firstgene = gene0 = g2->genes[0];
  gene1 = g2->genes[1];
  checkAdj(gene0, gene1, geneAdj, num_genes);
  for (i=1 ; i<num_genes-1 ; i++) {
    gene0 = gene1;
    gene1 = g2->genes[i+1];
    checkAdj(gene0, gene1, geneAdj, num_genes);
  }
  
  if (CIRCULAR) {
    gene0 = gene1;
    gene1 = firstgene;
    checkAdj(gene0, gene1, geneAdj, num_genes);
  }
  else {
    if (firstgene != g1first)
      num++;
    if (gene1 != g1last)
      num++;
  }

  return (num);
}

int hamming_distance_nomem(struct genome_struct *g1, struct genome_struct *g2,
			   int num_genes, int CIRCULAR) {
  int dist;
  int *geneAdj;
  
  geneAdj = (int *)malloc((num_genes+1)*2*sizeof(int));
  if (geneAdj == (int *)NULL)
    fprintf(stderr,"ERROR: geneAdj NULL\n");

  dist = hamming_distance(g1, g2, num_genes, CIRCULAR, geneAdj);

  free(geneAdj);
  return(dist);
}

void setBPmatrix(int **distmatrix,struct genome_struct *genomes,
		 int num_genes,int num_genomes,distmem_t *distmem,
		 int CIRCULAR) {
  int i, j;

  for (i=0 ; i<num_genomes ; i++) {
    distmatrix[i][i] = 0;
    for (j=i+1 ; j<num_genomes ; j++) {
      distmatrix[i][j] = distmatrix[j][i] =
	hamming_distance(genomes+i,genomes+j,num_genes,CIRCULAR,distmem->hammingArr);
    }
  }
}

void findAllHammingDistancesFromScratch(struct genome_struct *mygenomes,
					int num_genomes, int num_genes,
					int CIRCULAR) {
  int i,j, dist;
  int *geneAdj;

  geneAdj = (int *)malloc((num_genes+1)*2*sizeof(int));
  
  fprintf(outfile,"HAMMING DISTANCE From SCRATCH:\n");
  for (i=0 ; i<num_genomes ; i++) {
    for (j=i+1; j<num_genomes ; j++) {
      dist = hamming_distance(mygenomes+i, mygenomes+j, num_genes,
			      CIRCULAR, geneAdj);
      fprintf(outfile,"The Hamming Distance between %s and %s is %3d (BP=%3d)\n",
	      mygenomes[i].gnamePtr, mygenomes[j].gnamePtr,
	      dist, dist/2);
    }
  }
  fprintf(outfile,"\n");

  free(geneAdj);
  
  return;
}

void main_binencode() {
  int NUM_GENES=0;
  int NUM_GENOMES=0;
  int CIRCULAR=TRUE;
  
  struct genome_struct *mygenomes;

  /* Allocate the genomes */
  mygenomes = (struct genome_struct *)
    malloc(NUM_GENOMES*sizeof(struct genome_struct));
  if (mygenomes == (struct genome_struct *)NULL)
    fprintf(stderr,"ERROR: mygenomes is NULL\n");

  /* Fill the genomes with sample data */
  fillGenomes(mygenomes, NUM_GENOMES, NUM_GENES);

  /* Print out the original genomes */
  printGenomes(mygenomes, NUM_GENOMES, NUM_GENES);
  
  /* Encode the genomes */
  encodeGenomes(mygenomes, NUM_GENOMES, NUM_GENES);

  /* Print out the genome encodings */
  printGenomeEncodings(mygenomes, NUM_GENOMES);

  /* Calculate Hamming Distances */
  findAllHammingDistancesFromEncoding(mygenomes, NUM_GENOMES, NUM_GENES);

  /* Calculate Hamming Distances */
  findAllHammingDistancesFromScratch(mygenomes, NUM_GENOMES, NUM_GENES,
				     CIRCULAR);

  /* Free the genomes */
  freeGenomes(mygenomes, NUM_GENOMES);

  return;
}
