/***************************************************************************
                          main.cpp  -  description
                             -------------------
    begin                : óÂÔ äÅË 13 MSK 2003
    copyright            : (C) 2003 by Evgeney
    email                : dushistov@mail.ru
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <cstdlib>
#include <cstdio>
#include <clocale>
#include <glib.h>
#include <glib/gi18n.h>
#include <getopt.h>
#include <string>
#include <vector>
#include <map>

#include "libstardict.h"

struct TSearchResult{
  std::string bookname;
  std::string def;
  std::string exp;

  TSearchResult(const char *_bookname, const char *_def, const char * _exp)
    : bookname(_bookname), def(_def), exp(_exp)
  {
  }
};

typedef std::vector<TSearchResult> TSearchResultList;
typedef TSearchResultList::iterator PSearchResult;

static gchar *utf8_to_locale_ign_err(const gchar *utf8_str)
{
  gsize bytes_read, bytes_written;
  GError *err=NULL;
  gchar *res;
  
  const char * charset;
  if(g_get_charset(&charset))
    res=g_strdup(utf8_str);
  else{
    res=g_convert_with_fallback(utf8_str, -1, charset, "utf8", NULL, 
				&bytes_read, &bytes_written, &err);
    if(NULL==res){
      fprintf(stderr, _("Can not convert %s to current locale.\n"), utf8_str);
      fprintf(stderr, "%s\n", err->message);
      g_error_free(err);
      exit(EXIT_FAILURE);
    }
  }

  return res;
}

static gchar *locale_to_utf8(const gchar *loc_str)
{
  if(NULL==loc_str)
    return NULL;
  gsize bytes_read;
  gsize bytes_written;
  GError *err=NULL;
  gchar *str=NULL;
  str=g_locale_to_utf8(loc_str, -1, &bytes_read, &bytes_written, &err);
  if(NULL==str){
    fprintf(stderr, _("Can not convert %s to utf8.\n"), loc_str);
    fprintf(stderr, "%s\n", err->message);
    g_error_free(err);
    return str;
  }

  return str;
}

static void print_search_result(FILE *out, const TSearchResult & res, bool utf8_output)
{
  gchar *loc_bookname=NULL, *loc_def=NULL, *loc_exp=NULL;
  if(!utf8_output){
    loc_bookname=utf8_to_locale_ign_err(res.bookname.c_str());
    loc_def=utf8_to_locale_ign_err(res.def.c_str());
    loc_exp=utf8_to_locale_ign_err(res.exp.c_str());
  }
  
  printf("-->%s\n-->%s\n%s\n\n",
	 utf8_output ? res.bookname.c_str() : loc_bookname, 
	 utf8_output ? res.def.c_str() : loc_def, 
	 utf8_output ? res.exp.c_str() : loc_exp);
 
  g_free(loc_bookname);
  g_free(loc_def);
  g_free(loc_exp);
}

static std::string parse_data(const gchar *data)
{
  if(!data)
    return "";

  std::string res;
  glong data_size,sec_size;
  gchar *m_str;
  const gchar *p;


  memcpy(&data_size, data, sizeof(glong));
  p=data+sizeof(glong);
  while(p - (data + sizeof(glong))<data_size){
    switch (*p){
    case 'm':
    case 'o': //need more work...
      sec_size = strlen(p+sizeof(gchar));
      if(sec_size) {
	res+="\n";
	m_str = g_strndup(p+sizeof(gchar), sec_size);
	res += m_str;
	g_free(m_str);							
      }
      sec_size++;
      break;
    case 't':
      sec_size = strlen(p+sizeof(gchar));
      if(sec_size){
	res+="\n";
	m_str = g_strndup(p+sizeof(gchar), sec_size);
	res += "["+std::string(m_str)+"]";
	g_free(m_str);
      }
      sec_size++;
      break;
    case 'y':
      sec_size = strlen(p+sizeof(gchar));
      sec_size++;						
      break;
    case 'W':
      memcpy(&sec_size, p+sizeof(gchar), sizeof(glong));
      
      sec_size+= sizeof(glong);
      break;
    case 'P':
      memcpy(&sec_size, p+sizeof(gchar), sizeof(glong));
      sec_size += sizeof(glong);
      break;						
    }								
    p = p+sizeof(gchar)+sec_size;
  }

  
  return res;
}

static bool getline(FILE *in, std::string & str)
{
  str.clear();
  int ch;
  while((ch=fgetc(in))!=EOF && ch!='\n')
    str+=ch;
  if(EOF==ch)
    return false;
  return true;
}

static void process_phrase(const char *loc_str, Library & lib, bool utf8_input,
			   bool utf8_output, bool force=false)
{
  if(NULL==loc_str)
    return;
  gsize bytes_read;
  gsize bytes_written;
  GError *err=NULL;
  char *str=NULL;
  if(!utf8_input)
    str=g_locale_to_utf8(loc_str, -1, &bytes_read, &bytes_written, &err);
  else
    str=g_strdup(loc_str);
  if(NULL==str){
    fprintf(stderr, _("Can not convert %s to utf8.\n"), loc_str);
    fprintf(stderr, "%s\n", err->message);
    g_error_free(err);
    return;
  }

  if(str[0]=='\0')
    return;

  bool is_found=false;
  TSearchResultList res_list;

  if(str[0]=='/'){
    gchar *** pppWord;
    gchar *** pppWordData;
    gchar ** ppOriginWord;
    gint  count;
    is_found=lib.LookupWithFuzzy(str+1, pppWord, pppWordData, 
				 ppOriginWord, count);
    for(gint j=0;j<count; j++){
      if(!pppWordData[j])
	continue;
      for(gint i=0; i<lib.total_libs(); i++){
	std::string explanation=parse_data(pppWordData[j][i]);
	if(explanation.empty())
	  continue;
	res_list.push_back(TSearchResult(lib.GetBookname(i), pppWord[j][i], explanation.c_str()));
      }
    }
    for(gint i=0; i<count; i++){
      g_free(pppWord[i]);
      g_free(pppWordData[i]);
      g_free(ppOriginWord[i]);
    }
    g_free(pppWord);
    g_free(pppWordData);
    g_free(ppOriginWord);
  }
  else if(bContainRule(str)){
    gchar **ppMatchWord;
    gint count = lib.LookupWithRule(str, ppMatchWord);
    is_found=count>0;
    if(count){
      for(gint i=0; i<count; i++){
	gchar **ppWord;
	gchar **ppWordData;
	gboolean res=FALSE;
	if((res=lib.SimpleLookup(ppMatchWord[i], NULL, ppWord, ppWordData, FALSE, FALSE)))
	  for(gint i=0; i<lib.total_libs(); ++i){
	    std::string explanation=parse_data(ppWordData[i]);
	    if(explanation.empty())
	      continue;
	    res_list.push_back(TSearchResult(lib.GetBookname(i), ppWord[i], explanation.c_str()));
	  }
	
	g_free(ppWord);
	g_free(ppWordData);
      }

      for(gint i=0; i<count; i++)
	g_free(ppMatchWord[i]);
    }
    g_free(ppMatchWord);
  }
  else{
    gchar **ppWord;
    gchar **ppWordData;
    gchar *  SearchWord;
    is_found=lib.SimpleLookup(str, ppWord, ppWordData, SearchWord);
    
    if(is_found){
      for(gint i=0; i<lib.total_libs(); ++i){
	std::string explanation=parse_data(ppWordData[i]);
	if(explanation.empty())
	  continue;
	res_list.push_back(TSearchResult(lib.GetBookname(i), ppWord[i], explanation.c_str()));	
      }
    }

    
    g_free(ppWord);
    g_free(ppWordData);
    g_free(SearchWord);
    if(!is_found){
      gchar *** pppWord;
      gchar *** pppWordData;
      gchar ** ppOriginWord;
      gint  count;
      if((is_found=lib.LookupWithFuzzy(str, pppWord, pppWordData, ppOriginWord, count))){
	for(gint j=0; j<count; j++){
	  if(!pppWordData[j])
	    continue;
	  for(gint i=0; i<lib.total_libs(); i++){
	    std::string explanation=parse_data(pppWordData[j][i]);
	    if(explanation.empty())
	      continue;
	    res_list.push_back(TSearchResult(lib.GetBookname(i), pppWord[j][i], explanation.c_str()));
	  }
	}
	for(gint i=0; i<count; i++){
	  g_free(pppWord[i]);
	  g_free(pppWordData[i]);
	  g_free(ppOriginWord[i]);
	}
      }
      g_free(pppWord);
      g_free(pppWordData);
      g_free(ppOriginWord);
    }    

  }

  if(is_found){
    gchar *loc_str=NULL;
    if(!utf8_output)
      loc_str=utf8_to_locale_ign_err(str);
    printf(_("Found %d items, similar to %s.\n"), res_list.size(), utf8_output ? str : loc_str);

    g_free(loc_str);
    /* try to be more clever, if there are
       one or zero results per dictionary show all
    */
    bool show_all_results=true;
    typedef std::map< std::string, int, std::less<std::string> > DictResMap;
    if (!force) {
      DictResMap res_per_dict;
      for(TSearchResultList::iterator ptr=res_list.begin(); ptr!=res_list.end(); ++ptr){
	std::pair<DictResMap::iterator, DictResMap::iterator> r=res_per_dict.equal_range(ptr->bookname);
	DictResMap tmp(r.first, r.second);
	if (tmp.empty()) //there are no yet such bookname in map
	  res_per_dict.insert(DictResMap::value_type(ptr->bookname, 1));
	else {
	  ++((tmp.begin())->second);
	  if (tmp.begin()->second>1) {
	    show_all_results=false;
	    break;
	  }
	}
      }
    }//if (!force)
#if 0
    g_message("show_all=%d\n", show_all);
#endif
    if (!show_all_results) {
      if(force){
        PSearchResult ptr;
	for(ptr=res_list.begin(); ptr!=res_list.end(); ++ptr)
	  print_search_result(stdout, *ptr, utf8_output);
      }
      else{
	for(size_t i=0; i<res_list.size(); ++i){
	  gchar *loc_bookname=NULL, *loc_def=NULL;
	  loc_bookname=utf8_to_locale_ign_err(res_list[i].bookname.c_str());
	  loc_def=utf8_to_locale_ign_err(res_list[i].def.c_str());
	  printf("%d)%s-->%s\n", i,
		 utf8_output ?  res_list[i].bookname.c_str() : loc_bookname,
		 utf8_output ? res_list[i].def.c_str() : loc_def);					 
	}
	int choise;
	for(;;){
	  std::string str_choise;
	  printf(_("Your choice: "));

	  if(!getline(stdin, str_choise)){
	    putchar('\n');
	    exit(EXIT_SUCCESS);
	  }
	  sscanf(str_choise.c_str(), "%d", &choise);
	  if(choise>=0 && choise<int(res_list.size())){	  
	    print_search_result(stdout, res_list[choise], utf8_output);
	    break;
	  } else 
	    printf(_("Invalid choise.\nIt must be from 0 to %d.\n"), res_list.size()-1);	  
	}
      }
    } else 
      for(PSearchResult ptr=res_list.begin(); ptr!=res_list.end(); ++ptr)
	print_search_result(stdout, *ptr, utf8_output);
    
  } else {
    gchar *loc_str=NULL;
    if (!utf8_output)
      loc_str=utf8_to_locale_ign_err(str);
    
    printf(_("Nothing similar to %s, sorry :(\n"), utf8_output ? str : loc_str);
    g_free(loc_str);
  }
  g_free(str);
}


struct option longopts[] ={
  {"version", no_argument, NULL, 'v' },
  {"help", no_argument, NULL, 'h' },
  {"list-dicts", no_argument, NULL, 'l'},
  {"use-dict", required_argument, NULL, 'u'},
  {"non-interactive", no_argument, NULL, 'n'},
  {"utf8-output", no_argument, NULL, 0},
  {"utf8-input", no_argument, NULL, 1},
  {"data-dir", required_argument, NULL, 2},
  { NULL, 0, NULL, 0 }
};

extern char version[];

static char *progname;


static inline void print_help(void)
{
  printf("sdcv - console version of StarDict.\n");
  printf(_("Usage: %s [OPTIONS] words\n"), progname);
  printf(_("-h, --help               display this help and exit\n"));
  printf(_("-v, --version            display version information and exit\n"));
  printf(_("-l, --list-dicts         display list of available dictionaries and exit\n"));
  printf(_("-u, --use-dict bookname  for search use only dictionary with this bookname\n"));
  printf(_("-n, --non-interactive    for use in scripts\n"));
  printf(_("--utf8-output            output must be in utf8\n"));
  printf(_("--utf8-input             input of sdcv in utf8\n"));
  printf(_("--data-dir path/to/dir   use this directory as path to stardict data directory\n"));
}

static inline void print_version(void){
  printf(_("Console version of Stardict, version %s\n"), version);
}


int main(int argc, char *argv[])
{
  setlocale(LC_ALL, "");
#if ENABLE_NLS
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
#endif	 
  int optc;
  bool h = false, v = false, show_list_dicts=false, 
  use_book_name=false, non_interactive=false, utf8_output=false, utf8_input=false;
  GSList *enable_list=NULL;
  std::string data_dir;
  progname = argv[0];
  int option_index = 0;
  while((optc = getopt_long (argc, argv, "hvu:ln", longopts, 
                             &option_index))!=-1)
    switch (optc){
    case 0:
      utf8_output=true;
      break;
    case 1:   
      utf8_input=true;
      break;
    case 2:
      data_dir=optarg;
      break;
    case 'v':
      v = true;
      break;
    case 'h':
      h = true;
      break;
    case 'l':
      show_list_dicts=true;
      break;
    case 'u':
      use_book_name=true;
      enable_list=g_slist_append(enable_list, locale_to_utf8(optarg));
      break;
    case 'n':
      non_interactive=true;
      break;
    case '?':
      fprintf(stderr, _("Unknown option.\nTry '%s --help' for more information.\n"), progname);
      return EXIT_FAILURE;
    }
  
  if(h){
    print_help();
    return EXIT_SUCCESS;
  }

  if(v){
    print_version();
    return EXIT_SUCCESS;
  }

  const gchar *stardict_data_dir=g_getenv("STARDICT_DATA_DIR");
  if(data_dir.empty() && stardict_data_dir)
    data_dir=stardict_data_dir;

  if(show_list_dicts){
    TDictInfoList  dict_info_list;
    Library::get_dict_info_list(dict_info_list, data_dir.empty() ? NULL : data_dir.c_str());
    printf(_("bookname     wordcount\n"));
    for(size_t i=0; i<dict_info_list.size(); ++i){
      gchar *bookname=utf8_to_locale_ign_err(dict_info_list[i].bookname.c_str());
      printf("%s    %ld\n",
	     bookname,
	     dict_info_list[i].wordcount);
      g_free(bookname);
#if 0      
#define PRINT_STR_FIELD(field) \
do{ \
 if(!dict_info_list[i].field.empty()){\
   gchar *str=utf8_to_locale_ign_err(dict_info_list[i].field.c_str());\
   printf("  %s", str);\
   g_free(str);\
}while(0)
      
      PRINT_STR_FIELD(author);
      PRINT_STR_FIELD(email);
      PRINT_STR_FIELD(website);
      PRINT_STR_FIELD(date);
      PRINT_STR_FIELD(description);
      printf("  %ld", dict_info_list[i].idxfilesize);
      PRINT_STR_FIELD(sametypesequence);
      putchar('\n');
#endif      
    }
    
    return EXIT_SUCCESS;
  }

  GSList *disable_list=NULL;
  TDictInfoList  dict_info_list;
  
  if(use_book_name){
    Library::get_dict_info_list(dict_info_list, data_dir.empty() ? NULL : data_dir.c_str());
    
    GSList *not_disable_list=NULL;
    for(GSList *ptr=enable_list; ptr; ptr=g_slist_next(ptr)){
      bool there_is_dict_with_this_name=false;
      for(size_t i=0; i<dict_info_list.size(); ++i){
	if(strcmp((char *)(ptr->data), dict_info_list[i].bookname.c_str())==0){
	  there_is_dict_with_this_name=true;
	  not_disable_list=g_slist_append(not_disable_list, 
			                  g_strdup(dict_info_list[i].ifofilename.c_str()));
	}
      }
      if(!there_is_dict_with_this_name){
	fprintf(stderr, _("There is no dictionary with this bookname: %s.\n"), ptr->data);
	exit(EXIT_FAILURE);
      }
    }

    for(size_t i=0; i<dict_info_list.size(); ++i){
      bool disable=true;
      std::string ifofilename=dict_info_list[i].ifofilename;
      for(GSList *ptr=not_disable_list; ptr; ptr=g_slist_next(ptr))
	if(strcmp(ifofilename.c_str(), (char *)(ptr->data))==0)
	  disable=false;
      if(disable)
	disable_list=g_slist_append(disable_list,
		                    g_strdup(ifofilename.c_str()));
    }
    g_slist_foreach(not_disable_list, (GFunc)g_free, NULL);
    g_slist_free(not_disable_list);
  }

    
  gchar *conf_dir = g_build_filename(g_get_home_dir(), ".stardict", NULL);
  mkdir(conf_dir, S_IRWXU);
  gchar *cache_dir=g_build_filename(conf_dir, "cache", NULL);
  g_free(conf_dir);
  mkdir(cache_dir, S_IRWXU);
  Library lib(cache_dir);
  lib.Load(NULL, disable_list, data_dir.empty() ? NULL : data_dir.c_str());
  
  if (optind < argc){
    for(int i=optind; i<argc; ++i)
      process_phrase(argv[i], lib, utf8_input, utf8_output, non_interactive);
  }
  else if(!non_interactive){
    std::string phrase;
    
    printf(_("Enter word or phrase: "));
    while(getline(stdin, phrase)){
      process_phrase(phrase.c_str(), lib, utf8_input, utf8_output);
      printf(_("Enter word or phrase: "));
    }
    putchar('\n');
  }
  else{
    fprintf(stderr, _("There are no words/phrases to translate.\n"));
  }
  
  g_slist_foreach(disable_list, (GFunc)g_free, NULL); 
  g_slist_free(disable_list);
  
  return EXIT_SUCCESS;
}

