/********************************************************
 * euclid.c
 *
 * This is a general program which acts as an oracle for
 * a Euclidean space.  The underlying points can be either
 * read from a file, or else generated uniformly in the
 * unit hypercube.  The default norm is the L_2 norm, however
 * any L_p norm can be used.  Finally, the input can either be
 * interpretted as color coded for classification, or not.
 *
 * Note that an input file should include both the original
 * data points as well as a set of query points.  If data is
 * generated iniformly, then queries will also be generated
 * from the same distribution.
 *
 *
 *
 * Sixth Annual DIMACS Implementation Challenge
 * Author:  Michael Goldwasser (wass@cs.princeton.edu)
 * Created: Aug 24, 1998
 *
 * This program combines and replaces two earlier versions:
 *   'randcube' and 'color_euclid'
 *****************************************************

 Usage:                                                       
                                                              
   -h,-help        this message
   -trace          have server dump trace of messages sent/received
   -port <port>    port number to use (default 0x1234)

   -file <name>    filename (if none given, generates random data
   -p <int>        Use the L_p norm (p=2 by default; p=0 for L_infinity norm)

   The following fields are only used if no input file is given:
   -d <int>        number of dimensions
   -n <int>        number of data points
   -q <int>        number of query points
   -s <seed>       seed for random number generator (default is time())
   -c              force color coded format (although all colors will be zero)



File format:

  The first line contains four integers, <n> <q> <d> <c>,
  separated by white space.  These signify the following:
    n -- the number of original data points
    q -- the number of additional query points
    d -- the dimension of the space
    c -- c=1 if points are color-coded, and c=0 otherwise

  Following this, there should be (n+q) additional lines, each
  specifying a single vector (with the n original vectors preceeding
  the q query vectors.  If the data set is not color coded, then
  each vector should be a list of the d floating point cooridnates,
  separated by spaces.  If the data set is color coded, then the first
  field should be an integer color, with the d floating point
  coordinates to follow.

  Here are two examples of simple metric spaces, the first one color
  coded, and the second one not.

  Example of color coded data file:

2 1 3 1
5 0.25 0.33 0.15
4 0.81 0.22 0.49
0 0.55 0.65 0.78

  Example of uncolored data file:

2 1 3 0
0.25 0.33 0.15
0.81 0.22 0.49
0.55 0.65 0.78

****************************************************************/


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



#define DEFAULT_PORT  0x1234


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


typedef struct RawData {
  int       norm;
  int       dimension;
  int       num_points;
  int       num_query;
  double*   data;
  int*      color;
} RawData;



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


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

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


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

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




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

  ds = (RawData*) ora;

  /*--- check to make sure indicies are in range.  ---*/
  if ((p1<0) && (p1>=ds->num_points+ds->num_query) &&
      (p2<0) && (p2>=ds->num_points+ds->num_query)) {
    dist = -1.0;
  } else {
    for (dist=0.0, dim=0; dim<ds->dimension; dim++) {
      temp = ds->data[ds->dimension*p1 + dim] -
	ds->data[ds->dimension*p2 + dim];
      if (temp<0.0) temp = 0.0 - temp;

      switch (ds->norm)
	{
	case 0:
	  if (temp > dist)
	    dist = temp;
	  break;
	case 1:
	  dist += temp;
	  break;
	case 2:
	  temp *= temp;
	  dist += temp;
	  break;
	default:
	  dist += pow(temp,ds->norm);
	  break;
	}
    }


    switch (ds->norm)
      {
      case 0:
      case 1:
	break;
      case 2:
	dist = sqrt(dist);
	break;
      default:
	temp = 1.0/((double) ds->norm);
	dist = pow(dist,temp);
	break;
      }

  }

  return(dist);
}




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

  ds = (RawData*) ora;


  /*--- check to make sure indicies are in range.  ---*/
  if ((p<0) || (p>=ds->num_points+ds->num_query)) {
    result = 0;
  } else {
    result = ds->dimension + (ds->color?1:0);
  }

  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_points+ds->num_query) ||
      (f<0) || (f>=(ds->dimension+(ds->color?1:0)))) {
    assoc = (char*) strdup("");
  } else {
    assoc = (char*) malloc(20*sizeof(char));
    if (ds->color && f==0) {
      sprintf(assoc,"%d",ds->color[p]);
    } else {
      sprintf(assoc,"%19lf",ds->data[ds->dimension*p + f -(ds->color?1:0)]);
    }
  }

  return(assoc);
}





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



RawData* read_data(
  FILE* fp,
  int   p
)
{
  int n,q,d,c;
  int point,dim;
  RawData *ds;

  fscanf(fp," %d %d %d %d",&n,&q,&d,&c);

  ds = (RawData*) malloc(sizeof(RawData));
  ds->norm = p;
  ds->dimension = d;
  ds->num_points = n;
  ds->num_query = q;
  ds->data = (double*) malloc(((n+q)*d)*sizeof(double));
  if (c==0) {
    ds->color = NULL;
  } else {
    ds->color = (int*) malloc((n+q)*sizeof(int));
  }

  for (point=0; point<(n+q); point++) {
    if (ds->color) {
      fscanf(fp," %d", &ds->color[point]);
    }
    for (dim=0; dim<d; dim++) {
      fscanf(fp," %lf", &ds->data[d*point + dim]);
    }
  }

  return(ds);
}



RawData* generate_data(
  int d,
  int n,
  int q,
  int p,
  int c,
  int quiet
)
{
  int point,dim;

  RawData *ds;

  ds = (RawData*) malloc(sizeof(RawData));
  ds->norm = p;
  ds->dimension = d;
  ds->num_points = n;
  ds->num_query = q;
  ds->data = (double*) malloc(((n+q)*d)*sizeof(double));
  if (c==0) {
    ds->color = NULL;
  } else {
    ds->color = (int*) malloc((n+q)*sizeof(int));
  }

  for (point=0; point<(n+q); point++) {
    if (ds->color) {
      ds->color[point]=0;
    }
    for (dim=0; dim<d; dim++) {
      ds->data[d*point + dim] =
	((float) random())/((float) MAXLONG);
    }

    if (!quiet) {
      printf("Point %d: ( ",point);
      for (dim=0; dim<d; dim++) {
      printf("%9.7lf ",ds->data[d*point + dim]);
      }
      printf(")\n");
    }

  }

  return(ds);
}



void destroy_data(
  RawData *ds
)
{
  if (ds->color) free(ds->color);
  free(ds->data);
  free(ds);
}






#define LEN 100
static char usage[][LEN] = {
" Usage:                                                       ",
"                                                              ",
"   -h,-help        this message",
"   -trace          have server dump trace of messages sent/received",
"   -port <port>    port number to use (default 0x1234)",
"",
"   -file <name>    filename (if none given, generates random data",
"   -p <int>        Use the L_p norm (p=2 by default; p=0 for L_infinity norm)",
"",

"   The following fields are only used if no input file is given:",
"   -d <int>        number of dimensions",
"   -n <int>        number of data points",
"   -q <int>        number of query points",
"   -s <seed>       seed for random number generator (default is time())",
"   -c              force color coded format (although all colors will be zero)",
""
};

 

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(argc, argv)
  int argc;
  char **argv;
{
  int       i;
  RawData*  data;

  /*--- parameters ---*/
  int       port   = DEFAULT_PORT;
  FILE*     fp     = NULL;           /* use random */
  int       quiet  = TRUE;           /* do not trace */
  int       p      = 2;              /* L_2 norm */

  /*--- parameters when randomly generated ---*/
  long      seed   = time(NULL);
  int       c      = 0;              /* uncolored */
  int       d      = 5;
  int       n      = 100;
  int       q      = 10;




  /*--- 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 ((fp=fopen(argv[++i],"r"))==NULL) {
	fprintf(stderr,"Cant read cutoff file %s.\n",argv[i]);
	fprintf(stderr,"Generating at random.\n");
	fp = NULL;
      }
    } else if (strcmp(argv[i],"-p")==0) {
      p = atoi(argv[++i]);
    } else if (strcmp(argv[i],"-d")==0) {
      d = atoi(argv[++i]);
    } 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],"-c")==0) {
      c = 1;
    } else if (strcmp(argv[i],"-s")==0) {
      seed = atol(argv[++i]);
    } 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 ---*/
  if (fp) {
    /*-- read from file --*/
    data = read_data(fp,p);
  } else {
    /*-- generate at random --*/
    srandom(seed);
    data = generate_data(d,n,q,q,c,quiet);
  }


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


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

