#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <sysexits.h>
#include <vlcutils/error.h>
#include <config.h>
#include "index.h"
#include "mhist.h"
#include "mhistd.h"

static void usage(FILE *stream)
{
     fprintf(stream, "Usage: %s [OPTION]...\n", program_name);
     fprintf(stream, "Construct an F-index\n");
     if (stream == stderr)
	  fprintf(stream, "Try `%s -h` for more information.\n", program_name);
}

typedef enum { 
  bc_static, bc_volume, bc_density, bc_mhistv, bc_mhistd 
} box_capacity_t;

#define DEFAULT_WINDOW_SIZE 4096
#define DEFAULT_BOX_CAPACITY 1000
#define DEFAULT_BOX_CAPACITY_METHOD bc_static

static void help(void)
{
     usage(stdout);
     printf("\n"
"  -h            Display this help.\n"
"  -V            Display version number.\n"
"  -w INTEGER    Window size (default: %d).", DEFAULT_WINDOW_SIZE);
     printf("\n"
"  -c ARGUMENT   Box capacity (default: %d).", DEFAULT_BOX_CAPACITY);
     printf("\n"
"                A numerical argument implies static box capacity.  The following\n"
"                arguments specify that adaptive techniques should be used:\n"
"                  volume  - Fixed volume.  Requires -v.\n"
"                  density - Fixed density.  Requires -d, can use -l.\n"
"                  mhistv  - Minimal total volume.  Requires -n.\n"
"                  mhistd  - Maximal total density.  Requires -n.\n"
"  -v NUMBER     Maximum volume for boxes.  For use with -c volume.");
     printf("\n"
"  -d NUMBER     Minimum density for boxes.  For use with -c density.\n"
"  -l            Use lookahead.  For use with -c density.\n"
"  -n INTEGER    Desired number of boxes.  For use with -c mhistv and -c mhistd.\n"
"  -i FILENAME   Read input from FILENAME instead of standard input.\n"
"  -o FILENAME   Write index to FILENAME instead of standard output.\n"
"  -q            Be quiet, don't print statistics to standard output.\n"
"  -D            Debug; turn on verbose output.\n");
}

static const char *input_filename = NULL, *output_filename = NULL;
static int window_size = DEFAULT_WINDOW_SIZE;
static box_capacity_t box_capacity_method = DEFAULT_BOX_CAPACITY_METHOD;
static int box_capacity = DEFAULT_BOX_CAPACITY;
static double max_volume = 0.0, min_density = 0.0;
static int lookahead = 0;
static int num_boxes = 0;
int quiet = 0;
int debug = 0;

static void parse_options(int argc, char *argv[])
{
     int opt;
     char *parse_end;

     while ((opt = getopt(argc, argv, "hVw:c:v:d:ln:i:o:qD")) != -1)
	  switch (opt) {
	  case 'h':
	       help();
	       exit(EX_OK);
	  case 'V':
	       printf("%s %s\n", PACKAGE, VERSION);
	       exit(EX_OK);
	  case 'w':
	       window_size = strtol(optarg, &parse_end, 10);
	       if (parse_end == optarg)
		    fatal_error("Invalid number: %s", optarg);
	       if (window_size < 0)
		    fatal_error("Negative window size: %d", window_size);
	       break;
	  case 'c':
	       box_capacity = strtol(optarg, &parse_end, 10);
	       if (parse_end == optarg) {
		    box_capacity = -1;
		    /* Not a number */
		    if (strcasecmp(optarg, "volume") == 0)
			 box_capacity_method = bc_volume;
		    else if (strcasecmp(optarg, "density") == 0)
			 box_capacity_method  = bc_density;
		    else if (strcasecmp(optarg, "mhistv") == 0)
			 box_capacity_method  = bc_mhistv;
		    else if (strcasecmp(optarg, "mhistd") == 0)
			 box_capacity_method  = bc_mhistd;
	       } else {
		    /* Static box capacity chosen, so check argument. */
		    if (box_capacity <= 0)
			 fatal_error("Nonpositive box capacity: %d", box_capacity);
	       }
	       break;
	  case 'v':
	       max_volume = strtod(optarg, &parse_end);
	       if (parse_end == optarg)
		    fatal_error("Invalid number: %s", optarg);
	       if (max_volume < 0)
		    fatal_error("Negative max. volume: %f", max_volume);
	       break;
	  case 'd':
	       min_density = strtod(optarg, &parse_end);
	       if (parse_end == optarg)
		    fatal_error("Invalid number: %s", optarg);
	       if (min_density < 0)
		    fatal_error("Negative min. density: %f", min_density);
	       break;
	  case 'l':
	       lookahead = 1;
	       break;
	  case 'n':
	       num_boxes = strtol(optarg, &parse_end, 10);
	       if (parse_end == optarg)
		    fatal_error("Invalid number: %s", optarg);
	       if (num_boxes <= 0)
		    fatal_error("Nonpositive number of boxes: %d", num_boxes);
	       break;
	  case 'i':
	       input_filename = optarg;
	       break;
	  case 'o':
	       output_filename = optarg;
	       break;
	  case 'q':
	       quiet = 1;
	       break;
	  case 'D':
	       debug = 1;
	       break;
	  default:
	       usage(stderr);
	       exit(EX_USAGE);
	  }
     if (argc != optind) {
	  usage(stderr);
	  exit(EX_USAGE);
     }
     if (num_boxes && box_capacity_method != bc_mhistv && 
	 box_capacity_method != bc_mhistd)
	  fatal_error("-n can only be used with -c mhistv and -c mhistd.");
     if (!num_boxes && (box_capacity_method == bc_mhistv ||
			box_capacity_method == bc_mhistd))
	  fatal_error("When using -c mhistv or -c mhistd, -n must be specified.");
     if (max_volume && box_capacity_method != bc_volume)
	  fatal_error("-v can only be used with -c volume.");
     if (!max_volume && box_capacity_method == bc_volume)
	  fatal_error("When using -c volume, -v must be specified.");
     if (min_density && box_capacity_method != bc_density)
	  fatal_error("-d can only be used with -c density.");
     if (!min_density && box_capacity_method == bc_density)
	  fatal_error("When using -c density, -d must be specified.");
     if (lookahead && box_capacity_method != bc_density)
	  fatal_error("-l can only be used with -c density.");
}

void debug_print_boxes(struct index *index)
{
     int i;
     MBR *r;

     for (i = 0; i < index->num_boxes; i++) {
	  r = &index->elements[i];
	  fprintf(stderr, "Box %d: Volume = %d × %d × %d × %d × %d = %e\n", i, 
		 r->higher[0] - r->lower[0] + 1,
		 r->higher[1] - r->lower[1] + 1,
		 r->higher[2] - r->lower[2] + 1,
		 r->higher[3] - r->lower[3] + 1,
		 r->higher[4] - r->lower[4] + 1,
		 r->key);
	  fprintf(stderr, "        Start = %ld, Capacity = %d\n", r->start, 
		  r->capacity);
     }
}

int main(int argc, char *argv[])
{
     FILE *input, *output;
     struct index *index;

     program_name = argv[0];
     parse_options(argc, argv);
     
     if (input_filename) {
	  input = fopen(input_filename, "r");
	  if (!input)
	       fatal_perror("%s", input_filename);
     } else
	  input = stdin;

     if (output_filename) {
	  output = fopen(output_filename, "w");
	  if (!output)
	       fatal_perror("%s", output_filename);
     } else
	  output = stdout;

     switch (box_capacity_method) {
     case bc_static:
	  create_index_file(input, output, window_size, box_capacity);
	  break;
     case bc_volume:
	  create_index_volume(input, output, window_size, max_volume);
	  break;
     case bc_density:
	  if (lookahead)
	       create_index_density_lookahead(input, output, window_size, 
					      min_density);
	  else
	       create_index_density_nolookahead(input, output, window_size, 
						min_density);
	  break;
     case bc_mhistv:
	  index = make_index_mhistv(input, window_size, num_boxes);
	  if (debug)
	       debug_print_boxes(index);
	  store_index(index, output);
	  break;
     case bc_mhistd:
	  index = make_index_mhistd(input, window_size, num_boxes);
	  if (debug)
	       debug_print_boxes(index);
	  store_index(index, output);
	  break;
     default:
	  abort_error("Unexpected box capacity: %d", box_capacity_method);
     }
     fclose(input);
     fclose(output);
     return EX_OK;
}
