/********************************************************
 * text.c
 *
 * This is a sample program which acts as an oracle for
 * querying an underlying space. 
 *
 * This particular oracle implements a metric space based on one
 * of two possible edit distances on lines of text.
 *
 * This source code is a modification of original code
 * provided courtesy of Sergey Brin, for the functions
 * InsDel and Edit described in "Near Neighbor Search in
 * Large Metric Spaces", Sergey Brin, VLDB 1995.
 *
 * Sixth Annual DIMACS Implementation Challenge
 * Author:  Michael Goldwasser (wass@cs.princeton.edu)
 * Created: Jan 16, 1998
 *******************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <math.h>
#include <values.h>
#include "oracle.h"
#include "server.h"



#define DEFAULT_PORT  0x1234

#define MAXLINE 200


#ifndef TRUE
#define TRUE 1
#endif
 
#ifndef FALSE
#define FALSE 0
#endif
 

enum DistFunc { UseInsDel, UseEdit };


typedef struct Line {
  int    num_tokens;
  char** token;        /*-- individual tokens */
  char*  fullline;	/*-- original line --*/
} Line;


typedef struct RawData {
  int            num_lines;
  int            num_query;
  Line*		 data;
  enum DistFunc  fun;
} RawData;



/*------------------------------------------
 *  public functions
 -----------------------------------------*/




int InqNumPoints(
  Oracle* ora
)
{
  RawData* ds;

  ds = (RawData*) ora;
  return(ds->num_lines);
}


int InqNumQuery(
  Oracle* ora
)
{
  RawData* ds;

  ds = (RawData*) ora;
  return(ds->num_query);
}





int  InqNumFields(
  Oracle* ora,
  int p
)
{
  RawData* ds;
  char*    assoc;
  int      result;

  ds = (RawData*) ora;

  /*--- check to make sure indicies are in range.  ---*/
  if ((p<0) || (p>=ds->num_lines+ds->num_query)) {
    result = 0;
  } else {
    result = ds->data[p].num_tokens;
  }

  return(result);
}



char*  InqField(
  Oracle* ora,
  int p,
  int f
)
{
  RawData* ds;
  char*    assoc;

  ds = (RawData*) ora;

  /*--- check to make sure indicies are in range.  ---*/
  if ((p<0) || (p>=ds->num_lines+ds->num_query) ||
      (f<0) || (f>=ds->data[p].num_tokens)) {
    assoc = (char*) strdup("");
  } else {
    assoc = strdup(ds->data[p].token[f]);
  }

  return(assoc);
}



double InsDel(char *s1, char *s2)
{
  static int cost[MAXLINE][MAXLINE];
  int i,j;
  int l1, l2;

  l1 = strlen(s1);
  l2 = strlen(s2);

  for(i=0;i<=l1;i++) cost[i][0] = i;
  for(j=0;j<=l2;j++) cost[0][j] = j;

  for(i=1;i<=l1;i++)
    for(j=1;j<=l2;j++) {
      /* if the two characters are different */
      cost[i][j] = cost[i-1][j] < cost[i][j-1] ?
	cost[i-1][j] + 1 : cost[i][j-1] + 1;
      if (s1[i-1] == s2[j-1] && cost[i-1][j-1]<cost[i][j])
	cost[i][j] = cost[i-1][j-1];
    }

  return (double) cost[l1][l2];
}



double Edit(char *s1, char *s2)
{
  static int cost[MAXLINE][MAXLINE];
  int i,j;
  int l1, l2;

  l1 = strlen(s1);
  l2 = strlen(s2);

  for(i=0;i<=l1;i++) cost[i][0] = i;
  for(j=0;j<=l2;j++) cost[0][j] = j;

  for(i=1;i<=l1;i++)
    for(j=1;j<=l2;j++) {
      /* if the two characters are different */
      cost[i][j] = cost[i-1][j] < cost[i][j-1] ?
	cost[i-1][j] + 1 : cost[i][j-1] + 1;
      if (s1[i-1] == s2[j-1] && cost[i-1][j-1]<cost[i][j])
	cost[i][j] = cost[i-1][j-1];
      else if (cost[i-1][j-1]+1 < cost[i][j]) {
	cost[i][j] = cost[i-1][j-1]+1;
      }
    }

  return (double) cost[l1][l2];
}




double InqDist(
  Oracle* ora,
  int p1,
  int p2
)
{
  RawData* ds;
  double dist;

  ds = (RawData*) ora;

  /*--- check to make sure indicies are in range.  ---*/
  if ((p1<0) && (p1>=ds->num_lines+ds->num_query) &&
      (p2<0) && (p2>=ds->num_lines+ds->num_query)) {
    dist = -1.0;
  } else {
    switch(ds->fun) {
    case UseInsDel:
      dist = InsDel(ds->data[p1].fullline,ds->data[p2].fullline);
      break;
    case UseEdit:
      dist = Edit(ds->data[p1].fullline,ds->data[p2].fullline);
      break;
    }
  }

  return(dist);
}






/*------------------------------------------
 *  private functions
 -----------------------------------------*/


void read_line(
  FILE      *fp,
  RawData   *ds,
  int       p
)
{
  int i,numt;
  char*   parse;
  char    temp[MAX_FIELD_LENGTH];
  char    line[MAXLINE];

  if (!fgets(line, MAXLINE, fp)!=0) {
    printf("Problem reading file at line %d.\n",p);
    printf("It read: '%s'\n",line);
    exit(1);
  }
  ds->data[p].fullline = strdup(line);


    /*-- simple two pass method for breaking into tokens --*/
  for (parse=line,numt=0; (sscanf(parse," %s",temp)==1);
       numt++,parse += (strlen(temp)+1));


  ds->data[p].num_tokens = numt;
  ds->data[p].token = (char**) malloc(numt*sizeof(char*));
  for (parse=line,i=0; (sscanf(parse," %s",temp)==1);
       ds->data[p].token[i++] = strdup(temp), 
	 parse += (strlen(temp)+1));

}


RawData* generate_data(
  FILE           *fp,
  int            n,
  FILE           *qfp,
  int            q,
  enum DistFunc  fun
)
{
  int     i;
  RawData *ds;


  ds = (RawData*) malloc(sizeof(RawData));
  ds->num_lines = n;
  ds->num_query = q;
  ds->fun = fun;
  ds->data = (Line*) malloc((n+q)*sizeof(Line));


  for (i=0; i<n; i++) {
    read_line(fp,ds,i);
  }

  for (i=n; i<(n+q); i++) {
    read_line(qfp,ds,i);
  }

  return(ds);
}




void destroy_data(
  RawData *ds
)
{
  int i,j;

  for (i=0; i<(ds->num_lines+ds->num_query); i++) {
    for (j=0; j<(ds->data[i].num_tokens); j++) {
      free(ds->data[i].token[j]);
    }
    free(ds->data[i].token);
    free(ds->data[i].fullline);
  }
  free(ds->data);
  free(ds);
}






#define LEN 70
static char usage[][LEN] = {
" Usage:                                                              ",
"                                                                     ",
"   -help             this message",
"   -port <port>      port number to use",
"   -file <filename>  filename of text source (default stdin)",
"   -qfile <filename> filename of query source (default stdin)",
"   -insdel           use the InsDel distance function (default)",
"   -edit             use the Edit distance function",
"   -n <num>          number of lines of text for original set",
"   -q <num>          number of query lines",
"   -s <seed>         select a specific seed for any randomization",
"   -trace            have server dump trace of messages",
"                                                                     "
};

 

Print_Usage()
{
    int lines,i,j;
 
    lines = sizeof(usage)/(LEN*sizeof(char));
 
    for (i=0; i<lines; i++) {
        for (j=0; j<LEN; j++) {
            printf("%c",usage[i][j]);
        }
        printf("\n");
    }
}
 




main(
  int argc,
  char **argv
)
{
  int       i;
  RawData*  data;
  long      seed;
  int       quiet;
  

  /*--- parameters ---*/
  int port;
  int n,q;
  enum DistFunc  fun;
  FILE* inputfp;
  FILE* qfp;


  /*--- initialize default parameters ----*/
  port = DEFAULT_PORT;
  n = 100;
  q = 10;
  fun = UseInsDel;
  quiet = TRUE;
  seed = time(NULL);
  inputfp = qfp = stdin;


  /*--- parse command line args ---*/
  for (i=1; i<argc; i++) {
    if (strcmp(argv[i],"-port")==0) {
      port = atoi(argv[++i]);
    } else if (strcmp(argv[i],"-file")==0) {
      if ((inputfp=fopen(argv[++i],"r"))==NULL) {
	fprintf(stderr,"Can't open file %s.\n",argv[i]);
	inputfp = stdin;
      }
    } else if (strcmp(argv[i],"-qfile")==0) {
      if ((qfp=fopen(argv[++i],"r"))==NULL) {
	fprintf(stderr,"Can't open qfile %s.\n",argv[i]);
	qfp = stdin;
      }
    } else if (strcmp(argv[i],"-n")==0) {
      n = atoi(argv[++i]);
    } else if (strcmp(argv[i],"-q")==0) {
      q = atoi(argv[++i]);
    } else if (strcmp(argv[i],"-s")==0) {
      seed = atol(argv[++i]);
    } else if (strcmp(argv[i],"-insdel")==0) {
      fun = UseInsDel;
    } else if (strcmp(argv[i],"-edit")==0) {
      fun = UseEdit;
    } else if (strcmp(argv[i],"-trace")==0) {
      quiet = FALSE;
    } else if ((strcmp(argv[i],"-help")==0) ||
	       (strcmp(argv[i],"-h")==0)) {
      Print_Usage();
      exit(1);
    } else {
      printf("unrecognized option: %s (ignored)\n",argv[i]);
    }
  }




  /*--- initialize data ---*/
  srandom(seed);
  data = generate_data(inputfp,n,qfp,q,fun);




  /*--- start serving ---*/
  ServerOpen(port,data,quiet);


  /*--- cleanup ---*/
  /* (of course I don't know why the code will ever reach this */
  destroy_data(data);
}

