/* This file is part of cardwords
   (c) 1998 1999 Tobias Peters
   see file COPYING for the copyright terms.
   
   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// cardwords_dicbot.cc

/* DicBot is a part of Krizel, a crossword game for your computer.
   DicBot will be started as a child process from the CardWords server.
   It implements dictionary, robot player and move-counting facilities.
   These are in detail:
   - dictionary facilities:
     Chosing dictionary files, handling words that are rejected by the
     users or proposed by them. Checking words.
   - robot player facility:
     Chosing the best possible move for a given set of available cards.
   - move-counting:
     Counting the score for a move and checking all words
*/

/* The call of dicbot must have an argv[1] command line argument.
   This argument must be the filename of a CardWords_Char-set.
   This file has the following format:
   <number> <equivalent_chars 1> ... <equivalent_chars <number>>
   Number is the number of how many different Face Values of cards
   exist in these game. Equivalent chars is a sequence of Machine-
   Chars that will be considered to be represented by the same card,
   an example for such a sequence is Aa . The file can not and must
   not define a wildcard char. This is always defined automatically
   and tied to the Space (' ') Machine_Char. This does *not* mean
   that you always have wildcard cards in your game.
   See File cardwords_pile.?? for info on how the actual card-set for
   your game is chosen. This has nothing to do with DicBot.
*/


/* DicBot understands the following messages:

   NEW TABLE <table description>
     This message causes an old table representation, if one existed, to
     be erased. A new table representation will be created from the
     description. see cardwords_dicbot_cardtable.hh for an explanation of the
     format of a table description.

   CLEAR TABLE
     All cards on the table will be erased.
     Errors: "ERROR: NO TABLE"
     
   ADD CARD <table location> <card description>
     Add a card to a special cell on the table. The format of table location
     is described in cardwords_cardtablelocation.hh, of card description in
     cardwords_cardbase.hh.
     Errors: "ERROR: NO TABLE"

   REMOVE CARD <table location>
     removes a card from that location.
     Errors: "ERROR: NO TABLE"
     
   ADD WORD <word>
     Adds a word to the dictionary. If no dictionary exists, a new one will
     be created and used. word is a sequence of Machine_Chars that can be
     converted to CardWords_Chars. Newline('\n'))  marks the end of this
     sequence.

   REMOVE WORD <word>
     If word is contained in the dictionary, it will be removed. If the
     dictionary is empty thereafter, it will be destroyed. word is the
     same as above.

   CONTAINS WORD <word>
     Is a question if word is contained in the dictionary.
     Possible answers are:
       YES
       NO

   ADD FILE <filename>
     Adds all words contained in file filename to the dictionary. If no
     dictionary exists, a new one will be created and used.

   REMOVE FILE <filename>
     Removes all words contained in file filename from the dictionary. If
     the dictionary is empty thereafter, it will be destroyed.

   CLEAR DICTIONARY
     The dictionary will be destroyed.
     
   CHECK MOVE <move>
   COUNT MOVE <move>
   ADD MOVE <move>
     These messages cause feedback from DicBot. move is a move description
     in a format described in file cardwords_move.hh
     Possible answers to CHECK MOVE:
       INVALID MOVE
       INVALID WORD <word>
       INVALID SIDEWORD <card_index> <word>
       MOVE OK
     Possible answers to COUNT MOVE:
       INVALID MOVE
       INVALID WORD <word>
       INVALID SIDEWORD <card_index> <word>
       MOVE COUNTS <score>
     Answers to ADD MOVE are the same as the ones to COUNT MOVE. If the move
     has no invalidity, it is added to the table representation in DicBot.
     Errors: "ERROR: NO TABLE"

   GET BEST <card selection>
     This message causes feedback.
     Tries to find the most counting move that is possible with these cards.
     The format of card selection is described in the file
     cardwords_cardselection.hh.
     Possible Answers to GET BEST:
       BEST MOVE <move>
       NO MOVE
     Errors: "ERROR: NO TABLE"
     If there is no dictionary, or no word from the dictionary can be layed,
     response is NO MOVE.

   HOWMANY WORDS
     How many words are currently in the dictionary:
     The answer is:
       <count> WORDS
       
   EXIT DICBOT
     DicBot exits.

   DicBot will answer all commands and will not remain quiet.
   For the commands where no possible answers are described (but perhaps
   error messages) DicBot will answer "OK" if there was no error.
   
   every command contains a space character by intention. The commands will
   be read by a function that reads until the second white space character
   occurs. The second white space character will not be part of the command.
   If the first white space character is a white space character different
   from space, this difference will be ignored.
*/

/* SYNOPSIS:
   cardwords_dicbot [-h <hash size>] [-f <hash function parameter>] \
   [-w <minimum word length>] [-W <maximum word length>]          \
   <charset definition file>

   OPTIONS:
    h <num> : size of hash tables, default is 6007
    f <num> : factor for hash function. default is 4, reasonable values are\
    \         from 2 to 5 I think.
    w <num> : minimum word length of words in dictionary
    W <num> : maximum word length of words in dictionary
    b <filename> : The file filename contains a card-table description as
                   described in cardwords_dicbot_cardtable.hh. Use it.
    d <filename> : The file filename is a dictionary. Load it.
    D <filename> : The file filename is not only a dictionary to load. Words
                   that are added during the game are saved in this file.
    B <filename> : The file that contains the bonustable.

   REQUIRED PARAMETER:
   <charset definition file> Name of a file that contains the \
   \                         charset to be used.
   // see doc/cardwords_charset* for a description of the format of that file.
*/
#include <limits.h>
#include "cardwords_iostream.hh"
#include <string>
#include <map>
#include <string.h>
#include <assert.h>
#include "cardwords_dicbot_cardtable.hh"
#include "cardwords_dictionary.hh"
#include "cardwords_exceptions.hh"
#include "cardwords_gettext.hh"
#include <string> // Get rid of "cardwords_machinestring.hh"
#include "cardwords_direction.hh"
#include "cardwords_dicbot_bestordergenerator.hh"
#include "cardwords_dicbot.hh"
#include <unistd.h> // for getopt()
#include <stdlib.h> // for strtoul()
#include <stdio.h>
#include "cardwords_stringconverters.hh"

CardWords_DicBot_Global dicbot_global;

struct CardWords_DicBot_Commands {
  string word[2];
  CardWords_DicBot_Commands (const string & word1,
                             const string & word2)
    {
      word[0] = word1;
      word[1] = word2;
    };
  bool operator < (const CardWords_DicBot_Commands &other) const
    {
      
      return (word[0] < other.word[0]
              || (word[0] == other.word[0] && word[1] < other.word[1])
              );
    };
};

#define CALLBACK_DECLARATION(name) void name(istream &,               \
                                             ostream &,               \
                                             CardWords_DicBot_CardTable *&, \
                                             CardWords_Dictionary *&)   
static CALLBACK_DECLARATION (new_table);
static CALLBACK_DECLARATION (clear_table);
static CALLBACK_DECLARATION (add_card);
static CALLBACK_DECLARATION (remove_card);
static CALLBACK_DECLARATION (add_word);
static CALLBACK_DECLARATION (remove_word);
static CALLBACK_DECLARATION (contains_word);
static CALLBACK_DECLARATION (add_file);
static CALLBACK_DECLARATION (remove_file);
static CALLBACK_DECLARATION (clear_dictionary);
static CALLBACK_DECLARATION (check_move);
static CALLBACK_DECLARATION (count_move);
static CALLBACK_DECLARATION (add_move);
static CALLBACK_DECLARATION (howmany_words);
static CALLBACK_DECLARATION (get_best);

typedef CALLBACK_DECLARATION((*CardWords_DicBot_CallBack));

class CardWords_DicBot_CallBack_Map :
  public map<CardWords_DicBot_Commands, CardWords_DicBot_CallBack>
{
public:
  // adding a command/callback pair:
  pair<iterator,bool>
  add_entry( const string & word1,
             const string & word2,
             CardWords_DicBot_CallBack cb) {
    return insert(value_type(CardWords_DicBot_Commands(word1, word2), cb));
  };
  
  // getting the callback for the command given in the istream:
  CardWords_DicBot_CallBack
  get_callBack(istream &) const;
};

CardWords_DicBot_CallBack
CardWords_DicBot_CallBack_Map::get_callBack (istream & istream) const
{
  string string1, string2;
  istream >> string1 >> string2;
  const_iterator iter =
    find(CardWords_DicBot_Commands(string1, string2));
  if (iter == end()) {
    cerr << "received invalid command:" << string1 << ' ' << string2 << endl; 
    throw CardWords_XBadInput(gettext("DicBot command"),
                              gettext("valid DicBot command"));
  }
  return iter->second;
}


int main (int argc, char ** argv)
{
  // Refuse to run as root:
  if (getuid() == 0 || geteuid() == 0) {
    cerr << "This program is part of a game. Do not run this program as root."
         << endl;
    return 1;
  }

  setlocale (LC_ALL, "");
  bindtextdomain ("cardwords", LOCALEDIR_STR);
  textdomain ("cardwords");

  string exiting_reason;

  CardWords_DicBot_CallBack_Map callBacks;
  vector <string> dict_files;

  string cardtable_file;
  
  callBacks.add_entry("NEW","TABLE", &new_table);
  callBacks.add_entry("CLEAR","TABLE", &clear_table);
  callBacks.add_entry("ADD","CARD", &add_card);
  callBacks.add_entry("REMOVE","CARD", &remove_card);
  callBacks.add_entry("ADD","WORD", &add_word);
  callBacks.add_entry("REMOVE","WORD", &remove_word);
  callBacks.add_entry("CONTAINS","WORD", &contains_word);
  callBacks.add_entry("ADD","FILE", &add_file);
  callBacks.add_entry("REMOVE","FILE", &remove_file);
  callBacks.add_entry("CLEAR","DICTIONARY", &clear_dictionary);
  callBacks.add_entry("CHECK","MOVE", &check_move);
  callBacks.add_entry("COUNT","MOVE", &count_move);
  callBacks.add_entry("ADD","MOVE", &add_move);
  callBacks.add_entry("GET","BEST", &get_best);
  callBacks.add_entry("HOWMANY","WORDS", &howmany_words);
  callBacks.add_entry("EXIT","DICBOT", 0);

  CardWords_DicBot_CardTable *table = 0;
  CardWords_Dictionary   *dictionary = 0;
  CardWords_DicBot_CallBack callBack;

  // read the command line options
  int opt_char;
  while ( (opt_char = getopt(argc, argv,"h:f:w:W:b:d:D:B:")) != EOF) {
    // recognized options:
    // h <num> : size of hash tables, default is 6007
    // f <num> : factor for hash function. default is 4, reasonable values are
    //           from 2 to 5 I think.
    // w <num> : minimum word length of words in dictionary
    // W <num> : maximum word length of words in dictionary

    // b <filename> : The file filename contains a table description as
    //                described in cardwords_dicbot_cardtable.hh. Use it.
    // d <filename> : The file filename is a dictionary. Load it.
    // D <filename> : The file filename is not only a dictionary to load.
    //                Words that are added during the game are saved in
    //                this file.
    // B <filename> : The file that contains the bonustable.    
    switch (opt_char) {
    case 'h': dicbot_global.hash_table_size = strtoul (optarg, 0, 0);
      break;
    case 'f': dicbot_global.hashing_diff_factor = strtoul (optarg, 0, 0);
      break;
    case 'w': dicbot_global.min_word_length = strtoul (optarg, 0, 0);
      break;
    case 'W': dicbot_global.max_word_length = strtoul (optarg, 0, 0);
      break;
    case 'b':
      cardtable_file = optarg;
      break;
    case 'D':
      dicbot_global.append_dict = optarg;
      // fall through because -D includes all of -d :
    case 'd':
      // Read the dictfiles after the charset file:
      dict_files.push_back(optarg);
      break;
    case 'B':
      CardWords_BonusTable * bonus_table;
      try {
        bonus_table = new CardWords_BonusTable(optarg);
      } catch (CardWords_XAccessingFile xaf) {
        cerr << gettext("Bonusfile ") << flush;
        perror (optarg);
        break;
      } catch (CardWords_XBadInput xbi) {
        cerr << gettext("Bonusfile ") << optarg << ':' << xbi << endl;
        break;
      }
      catch (CardWords_Exception & x) {
        cerr << gettext("!!!! Exception caught in cardwords_dicbot:")
             << endl
             << x
             << endl;
        return 1;
      }
      if (dicbot_global.bonus_table != 0) {
        delete dicbot_global.bonus_table;
      }
      dicbot_global.bonus_table = bonus_table;
      break;
    default:
      // Call with an invalid command line argument may indicate an
      // interactive call
      exiting_reason =
        gettext(" because of invalid command line argument.");
      goto INTERACTIVE_FAIL ;
    }
  }
                
  if (optind >= argc) {
    // no charset definitions file is given
    // may indicate an interactive call
    exiting_reason =
      gettext(" because no charset definition file is given.");
    goto INTERACTIVE_FAIL;
  }
  try {
    CardWords_Char::define_from_file(argv[optind]);
  }
  catch (CardWords_XBadInput xbi)
    {
      cout << gettext("EingabeFehler: ") << endl
           << xbi << endl;
      exiting_reason =
        gettext(" because no charset definition file is given.");
      goto CHARSET_FAIL;
    }

  CardWords_FastHashKey::init(dicbot_global.hash_table_size,
                            dicbot_global.hashing_diff_factor);

  // Now add the dict files:
  dictionary = new CardWords_Dictionary(dicbot_global.min_word_length,
                                dicbot_global.max_word_length,
                                dicbot_global.hash_table_size);
  vector <string>::iterator dict_files_iterator;
  for (dict_files_iterator = dict_files.begin();
       dict_files_iterator != dict_files.end();
       ++dict_files_iterator) {
    dictionary->insert_file((*dict_files_iterator).c_str());
  }
  if (dictionary->word_count() == 0) {
    delete dictionary;
    dictionary = 0;
  }

  if (dicbot_global.bonus_table == 0) {
    /* It's not that dramatic:
    exiting_reason =
      gettext(" because no bonus file is given (option -B).");
    goto INTERACTIVE_FAIL;
    */
  }
  
  // now read the table file:
  if (cardtable_file.length() != 0) {
    ifstream tablefile(cardtable_file.c_str());
    if (tablefile.good()) {
      if (table != 0) {
        delete table;
        table = 0;
      }
      try {
        table = read_card_table (tablefile);
      } catch (CardWords_XBadInput xbi) {
        cerr << gettext("Input error in table description file ")
             << cardtable_file << ":" << endl
             << xbi << endl;
      }
      table->set_bonus_table (dicbot_global.bonus_table);
    }
  }
  
  {
    string answer_length;
  
    if (cin.good()) do {
      ostrstream output;
      try {
        callBack = callBacks.get_callBack(cin);
        if (callBack != 0) {
          (*callBack)(cin, output, table, dictionary);
        }
        else {
          output << "EXITING" << endl;
        }
      }
      catch (CardWords_XBadInput xbi)
        {
          cerr << gettext("EingabeFehler: ") << endl
               << xbi << endl;
          return(ER_INVALID_INPUT); 
        }
      try {
        answer_length = unsigned2string(output.pcount(),
                                        CSC_LEADING_ZEROES + CSC_STRICT_SIZE,
                                        DICBOT_ANSWER_DIGITS);
      }
      catch(CardWords_XOutOfRange xoor) {
        cerr << gettext("DicBot: INTERNAL ERROR: DICBOT_ANSWER_DIGITS are not"\
                        "sufficient enough:")
             << endl
             << xoor
             << endl;
        // should not happen, as I start with 8 digits. Thats enough for 95.3 MB
        // answers. But answers are unlikely to exceed a hundred bytes.
        return ER_INTERNAL_ERROR;
      }
      output << ends;
      output.freeze(true);
      cout << answer_length << output.str() << flush;
      output.freeze(false);
    } while (cin.good() && callBack != 0);
  }
  return 0;
  
 INTERACTIVE_FAIL:
  // If jumped here, we assume that an invalid condition indicates that the
  // program has been called from the command line, so give the gpl
  // warranty/distribution info:
      cerr <<
        gettext("This is ") <<
        dicbot_global.version <<
        gettext(", or short DicBot,\n"
                "the dictionary and robot module for the cardwords game,\n"
                "(c) 1998 1999 Tobias Peters.\n"
                "CardWords_DicBot is free software; you can redistribute it\n"
                "and/or modify it under the terms of the GNU General Public\n"
                "License as published by the Free Software Foundation; "
                "either\n"
                "version 2 of the License, or (at your option) any "
                "later version.\n"
                "\n"
                "This program is distributed "
                "in the hope that it will be useful,\n"
                "but WITHOUT ANY WARRANTY; "
                "without even the implied warranty of\n"
                "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. "
                " See the\n"
                "GNU General Public License for more details.\n\n"

                "You should have received a copy of the GNU General Public\n"
                "License along with this program; if not, write to the Free\n"
                "Software Foundation, Inc., 59 Temple Place, Suite 330,\n"
                "Boston, MA  02111-1307  USA\n\n"

                "DicBot is not designed to be called directly by you, "
                "instead\n"
                "it is usually called by the cardwords server, "
                "which in turn is\n"
                "usually started by your cardwords client.\n");
        
      cout << "EXITING" << exiting_reason << endl;
      return 1;

 CHARSET_FAIL:
  cerr << "DicBot: "
       << gettext("Error reading charset definitions file ")
       << argv[optind];
  cerr << endl;
  cout << "EXITING" <<  exiting_reason << endl;
  return 1;
}

#define CALLBACK_DEFINITION(name) void name (istream & istream,            \
                                             ostream & ostream,            \
                                             CardWords_DicBot_CardTable *& \
                                                                     table,\
                                             CardWords_Dictionary *& dict)

CALLBACK_DEFINITION (new_table)
{
  if (table != 0) {
    delete table;
    table = 0;
  }
  table = read_card_table (istream);
  ostream << "OK" << endl << flush;
}

CALLBACK_DEFINITION (clear_table)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
  }
  else {
    CardWords_CardTableLocation loc;
    for (loc.first = 0; loc.first < table->get_width(); loc.first++)
      for (loc.second = 0; loc.second < table->get_height(); loc.second++)
        table->remove_card(loc);
    ostream << "OK" << endl << flush;
  }
}

CALLBACK_DEFINITION (add_card)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
  }
  else {
    CardWords_CardTableLocation loc;
    istream >> loc;
    CardWords_CardDescription desc(istream);

    // No check if loc is inside the table and desc has a meaning. This should
    // have been done by the server. For debugging, there are assert
    // statements later.
    
    table->add_card(loc, desc);
    ostream << "OK" << endl << flush;
  }
}

CALLBACK_DEFINITION (remove_card)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
  }
  else {
    CardWords_CardTableLocation loc;
    istream >> loc;

    // No check if loc is inside the table. This should  have been done by
    // the server. For debugging, there are assert statements later.
    
    table->remove_card(loc);
    ostream << "OK" << endl << flush;
  }
}

CALLBACK_DEFINITION (add_word)
{
  CardWords_String s;
  istream >> s;

  if (dict != 0 && dict->contains_word(s)) {
    ;// Don't add it, it is already there.
  }
  else {
    if (dict == 0) {
      dict = new CardWords_Dictionary(dicbot_global.min_word_length,
                                    dicbot_global.max_word_length,
                                    dicbot_global.hash_table_size);
    }
    dict->insert_word(s);
    // Append this word to the dictfile specified with the -D command line
    // opt:
    if (dicbot_global.append_dict.length() != 0) {
      ofstream dictfile(dicbot_global.append_dict.c_str(),
                        ios::out | ios::app);
      if (dictfile.good()) {
        dictfile << s << endl;
      }
    }
  }
  ostream << "OK" << endl << flush;
}

CALLBACK_DEFINITION (remove_word)
{
  if (dict != 0) {
    CardWords_String s;
    istream >> s;
    dict->erase_word(s);
    if (dict->word_count() == 0) {
      delete dict;
      dict = 0;
    }
  }
  ostream << "OK" << endl << flush;
}

CALLBACK_DEFINITION (contains_word)
{
  if (dict != 0) {
    CardWords_String s;
    istream >> s;
    if (dict->contains_word(s)) {
      ostream << "YES" << endl << flush;
      return;
    }
  }
  ostream << "NO" << endl << flush;
}

CALLBACK_DEFINITION (add_file)
{
  string filename;
  istream >> filename;

  if (dict == 0) {
    dict = new CardWords_Dictionary(dicbot_global.min_word_length,
                                    dicbot_global.max_word_length,
                                    dicbot_global.hash_table_size);
    if (dict == 0) {
      throw CardWords_XLowResources(gettext("Low memory"));
    }
  }
  dict->insert_file(filename.c_str());

  if (dict->word_count() == 0) {
    delete dict;
    dict = 0;
  }
  ostream << "OK" << endl << flush;
}


CALLBACK_DEFINITION (remove_file)
{
  if (dict != 0) {
    string filename;
    istream >> filename;

    dict->remove_file(filename.c_str());

    if (dict->word_count() == 0) {
      delete dict;
      dict = 0;
    }
  }
  ostream << "OK" << endl << flush;
}

CALLBACK_DEFINITION (clear_dictionary)
{
  if (dict != 0) {
    delete dict;
    dict = 0;
  }
  ostream << "OK" << endl << flush;
}
    
CALLBACK_DEFINITION (check_move)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
    return;
  }
  CardWords_Move move;
  istream >> move;
  CardWords_String false_word;

  int validity = table->check_move(move, dict, &false_word);

  if (IS_MOVE_INVALID(validity))
    {
      if (validity == MOVE_INVALID) {
        if (false_word.length() == 0) {
          ostream << "INVALID MOVE" << endl << flush;
        }
        else {
          ostream << "INVALID WORD " << false_word << endl << flush;
        }
      }
      else {
        ostream << "INVALID SIDEWORD " << validity
                << " " << false_word << endl << flush;
      }
      return;
    }
  
  else {
    ostream << "MOVE OK" << endl << flush;
  }
}

CALLBACK_DEFINITION (count_move)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
    return;
  }
  int score, validity;
  CardWords_Move move;
  istream >> move;

  CardWords_String false_word;

  validity  = table->count_move(move, &score, dict, &false_word);
  
  if (IS_MOVE_INVALID(validity))
    {
      if (validity == MOVE_INVALID) {
        if (false_word.length() == 0) {
          ostream << "INVALID MOVE" << endl << flush;
        }
        else {
          ostream << "INVALID WORD " << false_word << endl << flush;
        }
      }
      else {
        ostream << "INVALID SIDEWORD " << validity
                << " " << false_word << endl << flush;
      }
      return;
    }
  ostream << "MOVE COUNTS " << score << endl << flush;
}

CALLBACK_DEFINITION (add_move)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
    return;
  }
  int score, validity;
  CardWords_Move move;
  istream >> move;

  CardWords_String false_word;
  
  validity = table->add_move(move, &score, dict, &false_word);
  if(IS_MOVE_INVALID(validity))
    {
      if (validity == MOVE_INVALID) {
        if (false_word.length() == 0) {
          ostream << "INVALID MOVE" << endl << flush;
        }
        else {
          ostream << "INVALID WORD " << false_word << endl << flush;
        }
      }
      else {
        ostream << "INVALID SIDEWORD " << validity
                << " " << false_word << endl << flush;
      }
      return;
    }
  ostream << "MOVE COUNTS " << score << endl << flush;
}


class ScoreOcc : public pair<size_t,size_t> {
  size_t & score(void) {return first;};
  size_t & occurrence(void) {return second;};
  ScoreOcc(size_t s, size_t o) : pair<size_t,size_t>(s,o){};
};

CALLBACK_DEFINITION (get_best)
{
  if (table == 0) {
    ostream << "ERROR: NO TABLE" << endl << flush;
    return;
  }
  if (dict == 0) {
    ostream << "NO MOVE" << endl << flush;
    return;
  }

  CardWords_CardSelection cards (istream);
  if (cards.size() == 0) {
    ostream << "NO MOVE" << endl << flush;
    return;
  }

  CardWords_DicBot_CardSelectionContents
    cardSelectionContents (cards);

  CardWords_DicBot_BestOrderGenerator
    bestOrderGenerator;/* FIXME: Let the caller choose the flags. */

  int                   top_score = INT_MIN;
  int                   score, validity;
  CardWords_Move          top_move;
  
  const vector<CardWords_CardSelection> * founds;
  vector<CardWords_CardSelection>::const_iterator founds_iterator;
  vector<CardWords_CardSelection>::const_iterator founds_end;
  
  CardWords_Move            move;
  CardWords_CardTableLocation  &loc = move.location;
  CardWords_Direction      &dir = move.direction;

  dir = horizontal;
  
  do {
    dir = !dir; // during first loop: dir = vertikal,
    //             during last loop: dir = horizontal.

    size_t & fast_var = (dir == horizontal) ? loc.first : loc.second;
    size_t & slow_var = (dir == horizontal) ? loc.second : loc.first;
    
    size_t fast_max =
      (dir == horizontal) ? table->get_width() : table->get_height();
    size_t slow_max =
      (dir == horizontal) ? table->get_height() : table->get_width();

    size_t fast_starting_co = fast_max;
    size_t slow_starting_co = slow_max;
    if (table->is_empty()) {
      if (dir == horizontal) {
        fast_starting_co = table->get_starting_pos().first;
        slow_starting_co = table->get_starting_pos().second;
      } else {
        fast_starting_co = table->get_starting_pos().second;
        slow_starting_co = table->get_starting_pos().first;
      }
    }

    for (slow_var = slow_max - 1;
         slow_var < slow_max;//&& slow_var >= 0  size_t is always unsigned
         slow_var--) {

      if (slow_starting_co < slow_max && slow_starting_co != slow_var) {
        continue;
      }
      // create the table requirements: Will fetch the info from table
      // at each new_cell;
      
      CardWords_DicBot_CardTableRequirements tableRequirements(table,
                                                               dir,
                                                               slow_var);
      
      for (fast_var = fast_max -1;
           fast_var < fast_max;/*&& slow_var >= 0  size_t is always unsigned*/
           fast_var--) {
#ifdef DEBUG
        assert(tableRequirements.new_cell() == loc);
#else
        tableRequirements.new_cell();
#endif
        if (fast_starting_co < fast_var) {
          continue;
        }

        size_t min_word_length = tableRequirements.min_word_length();
        if (min_word_length == 0) continue;

        if (fast_starting_co < fast_max &&
            fast_var + min_word_length <= fast_starting_co) {
          min_word_length = fast_starting_co - fast_var + 1;
        }
        founds =
          dict->find_possible_words (min_word_length, // min-word-length
                                     fast_max - fast_var, //max_word_length
                                     cardSelectionContents,
                                     & tableRequirements,
                                     & bestOrderGenerator);

        founds_end = founds->end();

        for (founds_iterator = founds->begin();
             founds_iterator != founds_end;
             ++founds_iterator) {
          if ((*founds_iterator).empty() == false) {
            move = *founds_iterator;
            validity = table->count_move(move, &score, dict);
            if (IS_MOVE_VALID(validity) && score > top_score) {
              top_score = score;
              top_move = move;
            }
          }
        }
      }
    }
  } while (dir == vertikal);
  if (top_move.empty()) {
    ostream << "NO MOVE" << endl << flush;
  }
  else {
    ostream << "BEST MOVE " << top_move << endl << flush;
  }
}

CALLBACK_DEFINITION (howmany_words)
{
  ostream << ((dict == 0) ? 0U : dict->word_count()) << " WORDS" << endl
          << flush;
}
