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

                             manage queries

    -----------------------------------------------------------------------

    begin          : Sat Jul 11 20:50:53 MET 1999

    copyright      : (C) 1999-2001 Ewald Arnold <kvoctrain@ewald-arnold.de>
                     (C) 2001 The KDE-EDU team
                     (C) 2005 Peter Hedlund <peter.hedlund@kdemail.net>

    -----------------------------------------------------------------------

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

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <tdelocale.h>
#include <tdeconfig.h>

#include <iostream>
#include <vector>
using namespace std;

#include "QueryManager.h"
#include "kvoctraincore.h"
#include <prefs.h>

vector<TQString> QueryManager::userTypes;

struct t_type_rel
{
   const char *short_ref;
   const char *long_ref;
};

// types are hierarchical !
// - . divides main from sub type
// - sub types must follow main type

// user types are strings-references like this: #1

static t_type_rel InternalTypeRelations [] =
{
  { TQM_ADJ,                           I18N_NOOP("Adjective") },

  { TQM_ADV,                           I18N_NOOP("Adverb") },

  { TQM_ART,                           I18N_NOOP("Article") },
  { TQM_ART TQM_TYPE_DIV TQM_ART_DEF,    I18N_NOOP("Article Definite") },
  { TQM_ART TQM_TYPE_DIV TQM_ART_IND,    I18N_NOOP("Article Indefinite") },

  { TQM_CON,                           I18N_NOOP("Conjunction") },

  { TQM_NAME,                          I18N_NOOP("Name") },   // old type "3"

  { TQM_NOUN,                          I18N_NOOP("Noun") },   // old type "2"
  { TQM_NOUN TQM_TYPE_DIV TQM_NOUN_M,    I18N_NOOP("Noun Male") },
  { TQM_NOUN TQM_TYPE_DIV TQM_NOUN_F,    I18N_NOOP("Noun Female") },
  { TQM_NOUN TQM_TYPE_DIV TQM_NOUN_S,    I18N_NOOP("Noun Neutral") },

  { TQM_NUM,                           I18N_NOOP("Numeral") },
  { TQM_NUM TQM_TYPE_DIV TQM_NUM_ORD,    I18N_NOOP("Numeral Ordinal") },
  { TQM_NUM TQM_TYPE_DIV TQM_NUM_CARD,   I18N_NOOP("Numeral Cardinal") },

  { TQM_PHRASE,                        I18N_NOOP("Phrase") },

  { TQM_PREP,                          I18N_NOOP("Preposition") },

  { TQM_PRON,                          I18N_NOOP("Pronoun") },
  { TQM_PRON TQM_TYPE_DIV TQM_PRON_POS,  I18N_NOOP("Pronoun Possessive") },
  { TQM_PRON TQM_TYPE_DIV TQM_PRON_PER,  I18N_NOOP("Pronoun Personal") },

  { TQM_QUEST,                         I18N_NOOP("Question") },

  { TQM_VERB,                          I18N_NOOP("Verb") },   // old type "1"
  { TQM_VERB  TQM_TYPE_DIV TQM_VERB_IRR, I18N_NOOP("Verb Irregular") },
  { TQM_VERB  TQM_TYPE_DIV TQM_VERB_REG, I18N_NOOP("Verb Regular") },

  { 0, 0 }  // the end
};


TQString QueryManager::getSubType (const TQString & type)
{
  int i;
  TQString t = type;
  if ((i = t.find(TQM_TYPE_DIV)) >= 0) {
    t.remove(0, i+1);
    return t;
  }
  else
    return TQString();
}


TQString QueryManager::getMainType (const TQString & type)
{
  int i;
  if ((i = type.find(TQM_TYPE_DIV)) >= 0)
    return type.left(i);
  else
    return type;
}


QueryManager::QueryManager()
{
  lessonitems.clear();
}


QuerySelection QueryManager::select(kvoctrainDoc *doc, int act_lesson, int oindex, int tindex)
{
   QuerySelection random;
   random.resize(doc->numLessons()+1);
   for (int i = 0; i < doc->numEntries(); i++)
     doc->getEntry(i)->setInQuery(false);

   // selecting might take rather long
   int ent_no = 0;
   int ent_percent = doc->numEntries() / 100;
   float f_ent_percent = doc->numEntries() / 100.0;
   emit doc->progressChanged(doc, 0);

   //Note that Leitner style learning (altlearn) normally only uses 20
   //entries, we just ignore that here
   for (int i = 0; i < doc->numEntries(); i++) {
     ent_no++;
     if (ent_percent != 0 && (ent_no % ent_percent) == 0 )
       emit doc->progressChanged(doc, int (ent_no / f_ent_percent));

     kvoctrainExpr *expr = doc->getEntry(i);
     unsigned int lessonno;
     if (Prefs::altLearn())
       lessonno = 0; //We only use a single array in Leitner style
     else
       lessonno = expr->getLesson();

     if (expr->isActive() ){
       if (Prefs::swapDirection()) {
         if (  validate (expr, act_lesson, oindex, tindex)
             || validate (expr, act_lesson, tindex, oindex)) {
           random[lessonno].push_back (QueryEntryRef(expr, i));
           expr->setInQuery(true);
         }
       }
       else {
         if (validate (expr, act_lesson, oindex, tindex)) {
           random[lessonno].push_back (QueryEntryRef(expr, i));
           expr->setInQuery(true);
         }
       }
     }
   }

   // remove empty lesson elements
   for (int i = (int) random.size()-1; i >= 0; i--)
     if (random[i].size() == 0)
       random.erase(random.begin() + i);
   return random;
}


bool QueryManager::validate(kvoctrainExpr *expr, int act_lesson, int oindex, int tindex)
{
   int index = tindex ? tindex : oindex;
   if ((compareExpiring(expr->getGrade(index, oindex != 0),
                        expr->getQueryDate(index, oindex != 0), Prefs::expire())
        ||

        (
            compareGrade ((CompType) Prefs::compType(Prefs::EnumType::Grade), expr->getGrade(index, oindex != 0), Prefs::gradeItem())
         && compareQuery ((CompType) Prefs::compType(Prefs::EnumType::Query), expr->getQueryCount(index, oindex != 0), Prefs::queryItem())
         && compareBad ((CompType) Prefs::compType(Prefs::EnumType::Bad), expr->getBadCount(index, oindex != 0), Prefs::badItem())
         && compareDate ((CompType) Prefs::compType(Prefs::EnumType::Date), expr->getQueryDate(index, oindex != 0), Prefs::dateItem())

         && compareBlocking(expr->getGrade(index, oindex != 0),
                            expr->getQueryDate(index, oindex != 0), Prefs::block())
        )
       )
//     lesson + word type must ALWAYS match
//     (and there must be a word on both sides)
       && compareLesson ((CompType) Prefs::compType(Prefs::EnumType::Lesson), expr->getLesson(), lessonitems, act_lesson)
       && compareType ((CompType) Prefs::compType(Prefs::EnumType::WordType), expr->getType(index), Prefs::typeItem())
       && !expr->getOriginal().stripWhiteSpace().isEmpty()
       && !expr->getTranslation(index).stripWhiteSpace().isEmpty()
      )
     return true;
   else
     return false;

}


QuerySelection QueryManager::select(kvoctrainDoc *doc, int act_lesson, int idx, TQString type)
{
   QuerySelection random;
   random.resize(doc->numLessons()+1);
   for (int i = 0; i < doc->numEntries(); i++)
     doc->getEntry(i)->setInQuery(false);

   // selecting might take rather long
   int ent_no = 0;
   int ent_percent = doc->numEntries() / 100;
   float f_ent_percent = doc->numEntries() / 100.0;
   emit doc->progressChanged(doc, 0);

   for (int i = 0; i < doc->numEntries(); i++) {
     ent_no++;
     if (ent_percent != 0 && (ent_no % ent_percent) == 0 )
       emit doc->progressChanged(doc, int (ent_no / f_ent_percent));

     kvoctrainExpr *expr = doc->getEntry(i);
     if (expr->isActive() && validate (expr, act_lesson, idx, type)) {
       random[expr->getLesson()].push_back (QueryEntryRef(expr, i));
       expr->setInQuery(true);
     }
   }

   // remove empty lesson elements
   for (int i = (int) random.size()-1; i >= 0; i--)
     if (random[i].size() == 0)
       random.erase(random.begin() + i);

   return random;
}


bool QueryManager::validate(kvoctrainExpr *expr, int act_lesson, int idx, TQString query_type)
{
   TQString qtype;
   int pos = query_type.find (TQM_TYPE_DIV);
   if (pos >= 0)
     qtype = query_type.left (pos);
   else
     qtype = query_type;

   TQString expr_type = expr->getType(idx);
   bool type_ok = false;
   if (qtype == TQM_NOUN) {
     type_ok =    expr_type == TQM_NOUN  TQM_TYPE_DIV  TQM_NOUN_S
               || expr_type == TQM_NOUN  TQM_TYPE_DIV  TQM_NOUN_M
               || expr_type == TQM_NOUN  TQM_TYPE_DIV  TQM_NOUN_F;

   }
   else if (qtype == TQM_VERB) {
     type_ok = (   expr_type == TQM_VERB
                || expr_type == TQM_VERB  TQM_TYPE_DIV  TQM_VERB_IRR
                || expr_type == TQM_VERB  TQM_TYPE_DIV  TQM_VERB_REG
               )
               && expr->getConjugation(idx).numEntries() > 0;

   }
   else if (qtype == TQM_ADJ) {
     type_ok =    expr_type == TQM_ADJ
               && !expr->getComparison(idx).isEmpty();
   }
   else
     return false;

   if (compareLesson ((CompType) Prefs::compType(Prefs::EnumType::Lesson), expr->getLesson(),
                      lessonitems, act_lesson)
       && type_ok) {
     return true;
     }
   else {
     return false;
   }
}


QuerySelection QueryManager::select(kvoctrainDoc *doc, int act_lesson, int idx, QueryType type)
{
   QuerySelection random;
   random.resize(doc->numLessons()+1);
   for (int i = 0; i < doc->numEntries(); i++)
     doc->getEntry(i)->setInQuery(false);

   // selecting might take rather long
   int ent_no = 0;
   int ent_percent = doc->numEntries() / 100;
   float f_ent_percent = doc->numEntries() / 100.0;
   emit doc->progressChanged(doc, 0);

   for (int i = 0; i < doc->numEntries(); i++) {
     ent_no++;
     if (ent_percent != 0 && (ent_no % ent_percent) == 0 )
       emit doc->progressChanged(doc, int (ent_no / f_ent_percent));

     kvoctrainExpr *expr = doc->getEntry(i);
     if (expr->isActive() && validate (expr, act_lesson, idx, type)) {
       random[expr->getLesson()].push_back (QueryEntryRef(expr, i));
       expr->setInQuery(true);
     }
   }

   // remove empty lesson elements
   for (int i = (int) random.size()-1; i >= 0; i--)
     if (random[i].size() == 0)
       random.erase(random.begin() + i);

   return random;
}


bool QueryManager::validate(kvoctrainExpr *expr, int act_lesson, int idx, QueryType query_type)
{
   bool type_ok = false;
   if (query_type == TQT_Synonym) {
     type_ok = !expr->getSynonym(idx).stripWhiteSpace().isEmpty();
   }
   else if (query_type == TQT_Antonym) {
     type_ok = !expr->getAntonym(idx).stripWhiteSpace().isEmpty();
   }
   else if (query_type == TQT_Paraphrase) {
     type_ok = !expr->getParaphrase(idx).stripWhiteSpace().isEmpty();
   }
   else if (query_type == TQT_Example) {
     type_ok = !expr->getExample(idx).stripWhiteSpace().isEmpty();
   }

   if (compareLesson ((CompType) Prefs::compType(Prefs::EnumType::Lesson), expr->getLesson(),
                      lessonitems, act_lesson)
       && type_ok)
     return true;

   return false;
}


TQString QueryManager::compStr(Prefs::EnumCompType::type type)
{
   TQString str = "???";
   switch (type)
   {
    case DontCare: str = i18n("Do not Care"); break;
    case WorseThan: str = i18n("Worse Than"); break;
    case WorseEqThan: str = i18n("Equal/Worse Than"); break;
    case MoreThan: str = i18n(">"); break;
    case MoreEqThan: str = i18n(">="); break;
    case BetterEqThan: str = i18n("Equal/Better Than"); break;
    case BetterThan: str = i18n("Better Than"); break;
    case LessEqThan: str = i18n("<="); break;
    case LessThan: str = i18n("<"); break;

    case EqualTo : str = i18n("Equal To"); break;
    case NotEqual: str = i18n("Not Equal"); break;

    case OneOf   : str = i18n("Contained In"); break;
    case NotOneOf: str = i18n("Not Contained In"); break;

    case Within    : str = i18n("Within Last"); break;
    case Before    : str = i18n("Before"); break;
    case NotQueried: str = i18n("Not Queried"); break;

    case Current    : return i18n("Current Lesson"); break;
    case NotAssigned: return i18n("Not Assigned"); break;
    default:
      ;
   }
   return str;
}


TQString QueryManager::gradeStr (int i)
{
  switch (i) {
    case KV_NORM_GRADE:    return i18n(KV_NORM_TEXT); break;
    case KV_LEV1_GRADE:    return i18n(KV_LEV1_TEXT); break;
    case KV_LEV2_GRADE:    return i18n(KV_LEV2_TEXT); break;
    case KV_LEV3_GRADE:    return i18n(KV_LEV3_TEXT); break;
    case KV_LEV4_GRADE:    return i18n(KV_LEV4_TEXT); break;
    case KV_LEV5_GRADE:    return i18n(KV_LEV5_TEXT); break;
    case KV_LEV6_GRADE:    return i18n(KV_LEV6_TEXT); break;
    case KV_LEV7_GRADE:    return i18n(KV_LEV7_TEXT); break;
    default:               return i18n(KV_LEV1_TEXT); break;
  }
}


vector<TypeRelation> QueryManager::getRelation (bool only_maintypes)
{
  vector<TypeRelation> vec;
  for (int i = 0; i < (int) userTypes.size(); i++) {
    TQString s;
    s.setNum(i+1);
    s.insert(0, TQM_USER_TYPE);
    vec.push_back(TypeRelation(s, userTypes[i]));
  }


  t_type_rel *type = InternalTypeRelations;
  while (type->short_ref != 0) {
    if (!only_maintypes || strstr (type->short_ref, TQM_TYPE_DIV) == 0)
      vec.push_back(TypeRelation(type->short_ref, i18n(type->long_ref)));
    type++;
  }

  return vec;
}


TQString QueryManager::typeStr (const TQString id)
{
  if (id.left(1) == TQM_USER_TYPE) {
    TQString num = id;
    num.remove (0, 1);
    int i = num.toInt()-1;
    if (i >= 0 && i < (int) userTypes.size() )
      return userTypes[i];
    else
      return TQString();
  }
  else {
    t_type_rel *type = InternalTypeRelations;
    while (type->short_ref != 0) {
      if (type->short_ref == id)
        return i18n(type->long_ref);
      type++;
    }
  }
  return TQString();
}


bool QueryManager::compareBlocking (int grade, int date, bool use_it)
{
   time_t cmp = Prefs::blockItem(grade);
   if (grade == KV_NORM_GRADE || cmp == 0 || !use_it) // don't care || all off
     return true;
   else {
     time_t now = time(0);
     return date+cmp < now;
   }
}


bool QueryManager::compareExpiring (int grade, int date, bool use_it)
{
   time_t cmp = Prefs::expireItem(grade);
   if (grade == KV_NORM_GRADE || cmp == 0 || !use_it) // don't care || all off
     return false;
   else {
     time_t now = time(0);
     return date+cmp < now;
   }
}


bool QueryManager::compareDate (CompType type, time_t qd, time_t limit)
{
   time_t now = time(0);
   bool erg = true;
   switch (type) {
    case DontCare: erg = true;
    break;

    case Before: erg = qd == 0 || qd < now-limit; // never queried or older date
    break;

    case Within: erg = qd >= now-limit;           // newer date
    break;

    case NotQueried: erg = qd == 0;
    break;

    default:
      ;
   }
   return erg;
}


bool QueryManager::compareQuery (CompType type, int qgrade, int limit)
{
   bool erg = true;
   switch (type) {
    case DontCare: erg = true;
    break;

    case MoreThan: erg = qgrade > limit;    // sel has higher query count
    break;

    case MoreEqThan: erg = qgrade >= limit;
    break;

    case EqualTo: erg = qgrade == limit;
    break;

    case NotEqual: erg = qgrade != limit;
    break;

    case LessEqThan: erg = qgrade <= limit;   // sel has less count
    break;

    case LessThan: erg = qgrade < limit;
    break;

    default:
      ;
   }
   return erg;
}


bool QueryManager::compareBad (CompType type, int bcount, int limit)
{
   bool erg = true;
   switch (type) {
    case DontCare: erg = true;
    break;

    case MoreThan: erg = bcount > limit;    // sel has higher bad count
    break;

    case MoreEqThan: erg = bcount >= limit;
    break;

    case EqualTo: erg = bcount == limit;
    break;

    case NotEqual: erg = bcount != limit;
    break;

    case LessEqThan: erg = bcount <= limit;   // sel has less count
    break;

    case LessThan: erg = bcount < limit;
    break;

    default:
      ;
   }
   return erg;
}


bool QueryManager::compareGrade (CompType type, grade_t qgrade, grade_t limit)
{
   bool erg = true;
   switch (type) {
    case DontCare: erg = true;
    break;

    case WorseThan: erg = qgrade < limit;    // sel has worse grade
    break;

    case WorseEqThan: erg = qgrade <= limit;
    break;

    case EqualTo: erg = qgrade == limit;
    break;

    case NotEqual: erg = qgrade != limit;
    break;

    case BetterEqThan: erg = qgrade >= limit;   // sel has better grade
    break;

    case BetterThan: erg = qgrade > limit;
    break;

    default:
      ;
   }
   return erg;
}


bool QueryManager::compareType (CompType type, const TQString & exprtype, const TQString & limit)
{
   bool erg = true;
   switch (type) {
    case DontCare: erg = true;
    break;

    case EqualTo: erg = getMainType(exprtype) == getMainType(limit);       // type is same
    break;

    case NotEqual: erg = getMainType(exprtype) != getMainType(limit);      // other type
    break;

    default:
      ;
   }
   return erg;
}


bool QueryManager::compareLesson (CompType type, int less, const vector<int> &limit, int current)
{
   bool erg = true;
   switch (type) {
    case DontCare:
      erg = true;
    break;

    case OneOf:
    {
      erg = false;
      for (int i = 0; !erg && i < (int) limit.size(); i++)
        if (limit[i] == less)
          erg = true;
    }
    break;

    case NotOneOf:
    {
      erg = true;
      for (int i = 0; erg && i < (int) limit.size(); i++)
        if (limit[i] == less)
          erg = false;
    }
    break;

    case Current:
      erg = less == current;
    break;

    case NotAssigned:
      erg = less == 0;
    break;

    default:
      ;
   }
   return erg;
}


void QueryManager::setTypeNames (vector<TQString> names)
{
  userTypes = names;
}


void QueryManager::setLessonItemStr (const TQString & indices)
{
  int pos;
  TQString indices_copy = indices;
  lessonitems.clear();
  while ((pos = indices_copy.find(' ')) >= 0) {
     TQString s = indices_copy.left(pos);
     indices_copy.remove(0, pos+1);
     lessonitems.push_back(s.toInt());
  }
  if (indices_copy.length() != 0) {
    lessonitems.push_back(indices_copy.toInt());
  }
}


TQString QueryManager::lessonItemStr() const
{
   TQString s, ret;
   for (int i = 0; i < (int) lessonitems.size(); i++) {
     s.setNum (lessonitems[i]);
     if (i != 0)
       ret += ' ';
     ret += s;
   }
   return ret;
}

