/**************************************************************************
 *
 * Queryer.cpp -- simple interactive query program
 * Copyright (C) 1999  Rodger McNab
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **************************************************************************/

#define _XOPEN_SOURCE 1
// This was added for Solaris, but it makes things worse on Solaris for me...
// #define _XOPEN_SOURCE_EXTENDED 1

/* getopt is in posix.2, so cygwin should have it in unistd, but doesn't */
#if defined (__WIN32__) || defined (__CYGWIN__)
# include "getopt_old.h"
#else
# include <unistd.h>
#endif

#include "MGQuery.h"
#include "TextGet.h"

#include "messages.h"
#include "mg_files.h"

#include "GSDLQueryParser.h"

void printHelp() {

  cout << "commands available are:\n"
       << "\t.q\t\tquit\n"
       << "\t.h\t\tprint the help message\n"
       << "\t.i\t\tchange the search level (enter the new level at the prompt)\n"
       << "\t.l\t\tchange the result level ( \"\"   \"\"  )\n"
       << "\t.b\t\tfull text browse (enter a word or fragment at the prompt)\n"
       << "\t.r0/.r1\t\tranking off/on\n"
       << "\t.t0/.t1\t\tquery type some/all\n"
       << "\t.c0/.c1\t\tcasefolding off/on\n"
       << "\t.s0/.s1\t\tstemming off/on\n"
#ifdef ENABLE_ACCENTFOLD
       << "\t.a0/.a1\t\taccentfolding off/on\n"
#endif
       << "\t.o0/.o1\t\tshort output off/on\n"
       << "\t.m\t\tset maxnumeric (enter the number at the prompt)\n\n"
       << "\t.p\t\tprint a document (enter the docnum at the prompt)\n"
       << "otherwise just enter a query\n\n";

}

int main (int argc, char **argv) {
  int ch;
  char *textfilename = (char*)"";
  char *indexfilename = (char*)"";
  char *basePath = (char*)"";
  
  opterr = 0;
  msg_prefix = argv[0];
  
  // process the command line arguments
  while ((ch = getopt (argc, argv, "f:t:d:h")) != -1) {
    switch (ch) {
    case 'f':		/* input file */
      indexfilename = optarg;
      break;
    case 't':
      textfilename = optarg;
      break;
    case 'd':
      basePath = optarg;
      set_basepath (basePath);
      break;
    case 'h':
    case '?':
      fprintf (stderr, "usage: %s [-h] [-d directory] -f indexname -t textname\n", argv[0]);
      exit (1);
    }
  }

  if (textfilename[0] == '\0' || indexfilename[0] == '\0') {
    FatalError (1, "Index and text file names must be specified with -f and -t \n");
  }
  
  // init the text system
  TextData textData;
  if (!textData.LoadData (basePath, textfilename)) {
    FatalError (1, "Couldn't load text information for \"%s\"", textfilename);
  }
  
  // init the query system
  IndexData indexData;
  if (!indexData.LoadData (basePath, indexfilename)) {
    FatalError (1, "Couldn't load index information for \"%s\"", indexfilename);
  }

  int maxnumeric = 4;

  // debug output
  cerr << "num docs: "<<indexData.bdh.num_docs       
       << "\nnum frags: "<<indexData.bdh.num_frags
       << "\nnum words: "<<indexData.bdh.num_words
       << "\ntotal bytes: "<<indexData.bdh.total_bytes
       << "\nindex string bytes: "<<indexData.bdh.index_string_bytes
       << "\nnum levels: "<<indexData.bdh.num_levels<<endl;
  
  // do querying
  QueryInfo queryInfo;
  SetCStr (queryInfo.docLevel, "Doc", 3);
  queryInfo.maxDocs = 50;
  queryInfo.sortByRank = true;
  queryInfo.exactWeights = false;
  queryInfo.needRankInfo = true;
  queryInfo.needTermFreqs = true;
  
  ExtQueryResult queryResult;
  char query[2048];
  UCArray queryArray;
  QueryNode *queryTree = NULL;


  UCArray docLevel;
  SetCStr(docLevel, "Doc", 3);

  UCArray level;
  UCArrayClear(level);
  //SetCStr(level, "");
   
  int defaultStemMethod = 0; // uncasefolded, unstemmed, unaccentfolded
  int defaultBoolCombine = 0; // OR
  bool shortOutput = false;
  BrowseQueryNode browseNode;
  browseNode.startPosition = -10;
  browseNode.numTerms = 40;

  BrowseQueryResult browseResult;

  while (true) {
    cout << "> ";
    cin.getline(query, 2048, '\n');
    SetCStr (queryArray, query, strlen(query));

    // check for commands
    if (queryArray.size() >= 2 && queryArray[0] == '.') {
      if (queryArray[1] == 'q') break;  // quit

      if (queryArray[1] == 'h') { // help
	printHelp();
      } else if (queryArray[1] == 'i') {
	cout << "current index="<< queryInfo.docLevel << "\nchange to index:";
	cin >> query;
	UCArrayClear(queryInfo.docLevel);
	SetCStr(queryInfo.docLevel, query, strlen(query));
	cout << "index set to " << queryInfo.docLevel <<"\n";
	cin.getline(query, 2048, '\n');
      } else if (queryArray[1] == 'l') {
	cout << "current level="<< level << "\nchange to level:";
	cin >> query;
	UCArrayClear(level);
	SetCStr(level, query, strlen(query));
	cout << "level set to " << level <<"\n";
	cin.getline(query, 2048, '\n');
      }

      else if (queryArray[1] == 'm') {
	// maxnumeric
	int m = 0;
	cin >> m;
	cin.getline(query, 2048, '\n'); // eat up return
	if (m > 4 && m < 512) {
	  maxnumeric = m;
	}
      }
      else if (queryArray[1] == 'p') {
	// print
	UCArray docText;
	mg_u_long docNum = 0;
	cin >> docNum;
	cin.getline(query, 2048, '\n'); // eat up return
	
	if (!GetDocText (textData, queryInfo.docLevel, docNum, docText)) {
	  FatalError (1, "Error while trying to get document %u", docNum);
	}
	
	cout << docText << "\n";
      }
      else if (queryArray[1] == 't') { // query type - all/some
	if (queryArray[2] == '1') defaultBoolCombine = 1;
	else if (queryArray[2] == '0') defaultBoolCombine = 0;
	else {
	  cout << "Error: please enter .t0 (some) or .t1 (all)\n";
	}
      }
      else if (queryArray[1] == 'r') { // ranking - on/off
	if (queryArray[2] == '1') queryInfo.sortByRank = true;
	else if (queryArray[2] == '0') queryInfo.sortByRank = false;
	else {
	  cout << "Error: please enter .r0 (non-ranked) or .r1 (ranked)\n";
	}
      }
      else if (queryArray[1] == 'c') { // casefolding - on/off
	if (queryArray[2] == '1') defaultStemMethod |= STEM_CaseFolding;
	else if (queryArray[2] == '0') defaultStemMethod &= (~STEM_CaseFolding);
	else {
	  cout << "Error: please enter .c0 (case sensitive) or .c1 (casefolded)\n";
	}
      }
      else if (queryArray[1] == 's') { // stemming - on/off
	if (queryArray[2] == '1') defaultStemMethod |= STEM_Stemming;
	else if (queryArray[2] == '0') defaultStemMethod &= (~STEM_Stemming);
	else {
	  cout << "Error: please enter .s0 (unstemmed) or .s1 (stemmed)\n";
	}
      }      
#ifdef ENABLE_ACCENTFOLD
      else if (queryArray[1] == 'a') { // accentfolding - on/off
	if (queryArray[2] == '1') defaultStemMethod |= STEM_AccentFolding;
	else if (queryArray[2] == '0') defaultStemMethod &= (~STEM_AccentFolding);
	else {
	  cout << "Error: please enter .a0 (accent sensitive) or .a1 (accentfolded)\n";
	}
      }
#endif
      else if (queryArray[1] == 'o') { // output - short/long
	if (queryArray[2] == '1') shortOutput = true;
	else if (queryArray[2] == '0') shortOutput = false;
	else {
	  cout << "Error: please enter .o0 (long output) or .o1 (short output)\n";
	}
      }
      else if (queryArray[1] == 'b') {
	// full text browse
	cout<<"enter a few letters to start browsing from:";
	cin>>query;
	UCArrayClear(browseNode.term);
	SetCStr(browseNode.term, query, strlen(query));
	cin.getline(query, 2048, '\n'); // get rest of line

	// print the query
	PrintNode (cout, &browseNode);

	MGBrowseQuery(indexData, docLevel, browseNode, browseResult);
	cout << browseResult;
	cout << "\n";

      }
      else { // bad option
	cout << "bad command\n\n";
	printHelp();
      }
    } // if a .x query
    else {
      // regular query
      queryTree = ParseQuery (queryArray, defaultBoolCombine, defaultStemMethod, maxnumeric);
      if (queryTree == NULL) {
	cout << "invalid syntax\n";
      } else {
	// print the query
	PrintNode (cout, queryTree);
	
	MGQuery (indexData, queryInfo, queryTree, queryResult, level);
	if (shortOutput) {
	  queryResult.printShort(cout);
	  cout << "\n";
	} else {
	  cout << queryResult;
	  cout << "\n";
	}
	// delete the query
	delete queryTree;
	queryTree = NULL;
      }
    }
  }


  // clean up, everybody clean up
  textData.UnloadData ();
  indexData.UnloadData ();

  return (0);
}


