/* This file is part of cardwords
   (c) 1999 Tobias Peters
   
   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_server.cc

#include "cardwords_server.hh"
#include "cardwords_serversocket.hh"
#include "cardwords_game.hh"
#include "cardwords_cardtable.hh"
#include "cardwords_pile.hh"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

CardWords_Server_Global server_global;

int main (int argc, const 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;
  try {
    // catch all exceptions and print their info
  
    // read the command line options
    int opt_char;
    while ( (opt_char = getopt(argc,
                               (char **) argv,
                               "h:f:w:W:d:c:b:t:l:D:B:p:r:P:RH:o:")) != EOF) {
      switch (opt_char) {
      case '?':
        cout << gettext ("This is ")
             << server_global.version << "," << endl
             << "the server module of the cardwords game." << endl;
        cout <<
          gettext
          (
           "cardwords_server 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"

           "cardwords_server is usually not called directly by you, instead\n"
           "it is usually started by your cardwords client.\n");
        cout << gettext("\n"
"Recognized options:\n"
"    \n"
"Forwarded to cardwords_dicbot:\n"
"h <num> : size of hash tables, default is 6007\n"
"f <num> : factor for hash function. default is 4, reasonable values are\n"
"->        from 2 to 5 I think.\n"
"w <num> : minimum word length of words in dictionary\n"
"W <num> : maximum word length of words in dictionary\n"
"d <filename>: tell dicbot to load that dictionary.\n"
"D <filename>: tell dicbot to load that dictionary and save additional\n"
"->            words here.\n"
"\n"
"These are forwarded and used here:\n"
"c <filename>: filename that contains the charset. Forwarded without the \"-c\"\n"
"b <filename>: filename that contains a card-table description as described in\n"
"->            cardwords_dicbot_cardtable.hh\n"
"B <filename>: the file containing the bonus points.\n"
"\n"
"not forwarded:\n"
"t <filename>: filename that contains all cards.\n"
"p <num>: the number of players to accept. Default is 1.\n"
"r <num>: the number of robot players to emulate. Default is 1.\n"
"o <num>: the number of watchers allowed (*o*bservers). Default is 0.\n"
"P <num>: the port number to listen to. default is ")
             << PORT_NUMBER << gettext(".\n"
"R      : do not reserve a player place for the owner which is otherwise\n"
"->       done by default.\n"
"H <dirname>: use the directory <dirname> as the home directory.\n");
        goto INVALID_OPTION;
      case 'h':
        server_global.dicbot.add_argument ("-h");
        server_global.dicbot.add_argument (optarg);
        break;
      case 'f':
        server_global.dicbot.add_argument ("-f");
        server_global.dicbot.add_argument (optarg);
        break;
      case 'w':
        server_global.dicbot.add_argument ("-w");
        server_global.dicbot.add_argument (optarg);
        break;
      case 'W':
        server_global.dicbot.add_argument ("-h");
        server_global.dicbot.add_argument (optarg);
        break;
      case 'd':
        server_global.dicbot.add_argument ("-d");
        server_global.dicbot.add_argument (optarg);
        break;
      case 'D':
        server_global.dicbot.add_argument ("-D");
        server_global.dicbot.add_argument (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;
        }
        if (server_global.bonus_table != 0) {
          delete server_global.bonus_table;
        }
        server_global.bonus_table = bonus_table;
        server_global.dicbot.add_argument ("-B");
        server_global.dicbot.add_argument (optarg);
        break;
      }
      case 'c':
        server_global.charset_file = optarg;
        break;
      case 'b':
        server_global.table_file = optarg;
        server_global.dicbot.add_argument ("-b");
        server_global.dicbot.add_argument (optarg);
        break;
      case 't':
        server_global.card_file = optarg;
        break;
      case 'l':
        server_global.load_file = optarg;
        break;
      case 'H':
        server_global.home_directory = optarg;
        break;
      case 'P':
        {
          char * endpointer = optarg;
          long int port_no = strtol(optarg, &endpointer, 0);
          if (optarg < endpointer) {
            server_global.port_number = port_no;
            break;
          }
          else {
            exiting_reason =
              gettext (" because no port number given after -P option");
            goto FAIL;
          }
        }
      case 'o':
        {
          char * endpointer = optarg;
          unsigned long int watchers_no = strtoul(optarg, &endpointer, 0);
          if (optarg < endpointer) {
            server_global.no_of_watchers_planned = watchers_no;
            break;
          }
          else {
            exiting_reason =
              gettext (" because no watchers number given after -o option");
            goto FAIL;
          }
        }
      case 'p':
        {
          char * endpointer = optarg;
          unsigned long int players_no = strtoul(optarg, &endpointer, 0);
          if (optarg < endpointer) {
            server_global.no_of_players_planned = players_no;
            break;
          }
          else {
            exiting_reason =
              gettext (" because no players number given after -p option");
            goto FAIL;
          }
        }
      case 'r':
        {
          char * endpointer = optarg;
          unsigned long int robots_no = strtoul(optarg, &endpointer, 0);
          if (optarg < endpointer) {
            server_global.no_of_robots_planned = robots_no;
            break;
          }
          else {
            exiting_reason =
              gettext (" because no robots number given after -r option");
            goto FAIL;
          }
        }
      case 'R':
        server_global.reserve_player_place_for_owner = false;
        break;
      default:
      INVALID_OPTION:
        exiting_reason =
          gettext(" because of invalid command line argument.");
        goto FAIL;
      }
    }
    if (server_global.load_file.length() != 0) {
      // LOAD The game *****************************************************
      //          A remark to loading and saving games: This has a very low
      //          priority for me in this server, so it will probably never
      //          be implemented.
    }
    if (server_global.charset_file.length() == 0) {
      exiting_reason =
        gettext(" because no charset file is given");
      goto FAIL;
    }
    // read the **charset** file:
    try {
      CardWords_Char::define_from_file(server_global.charset_file);
    }
    catch (CardWords_XAccessingFile xaf) {
      cerr << xaf << endl << flush;
      exiting_reason =
        gettext(" because the given "\
                "charset file is unaccessible");
      goto FAIL;
    }
    catch (CardWords_XBadInput xbi) {
      cerr << "CardWords_Server: main(): "
           << gettext("While reading charset description file ")
           << server_global.charset_file << ":"
           << xbi << endl << flush;
      exiting_reason =
        gettext(" because of invalid data in the "\
                "given charset description file");
      goto FAIL;
    }
    
    server_global.dicbot.add_argument(server_global.charset_file);
  
    if (server_global.card_file.length() == 0) {
      exiting_reason =
        gettext(" because no card file is given");
      goto FAIL;
    }
    // read the ** card file **
    else {
      ifstream cardSpec (server_global.card_file.c_str());
      if (cardSpec.bad()) {
        exiting_reason =
          gettext(" because the given card description file is unaccessible");
        goto FAIL;
      }
      try {
        server_global.pile = new CardWords_Pile (cardSpec);
      }
      catch (CardWords_XBadInput xbi) {
        cerr << "CardWords_Server: main(): "
             << gettext("While reading the card description file ")
             << server_global.card_file << ":"
             << xbi << endl << flush;
        exiting_reason =
          gettext(" because of invalid data in the "\
                  "given card description file");
        goto FAIL;
      }
    }
  
    // ** now start DicBot: **
    server_global.dicbot.start();

    {
      // ** create the server socket: **
      CardWords_ServerSocket
        serversocket(&server_global.selector,
                     server_global.port_number);  
      // ** read the table: **
      if (server_global.table_file.length() == 0) {
        exiting_reason =
          gettext(" because no table description file is given");
        goto FAIL;
      }
      ifstream tablestream (server_global.table_file.c_str());
      if (tablestream.bad()) {
        exiting_reason =
          gettext(" because the given table description file "\
                  "is unaccessible");
        goto FAIL;
      }
      try {
        server_global.table = read_card_table(tablestream);
      }
      catch (CardWords_XBadInput xbi) {
        cerr << "CardWords_Server: main(): "
             << gettext("While reading table description file ")
             << server_global.table_file << ":"
             << xbi << endl << flush;
        exiting_reason =
          gettext(" because of invalid data in the "\
                  "given table description file");
        goto FAIL;
      }
      
      // Write the owner's password into the file
      // ~/.cardwords/owner_password
      // which is only readable by the owner of this process.
      struct stat home_dir_stat;
      struct stat cardwords_dir_stat;
      // unused: struct stat password_file_stat;
      uid_t  process_uid = getuid();
      // unused: int old_umask = umask (0);
    
      if (server_global.home_directory.length() == 0) {
        
#ifdef DEFAULT_HOMEDIR
        if ((DEFAULT_HOMEDIR) != 0) {
          server_global.home_directory = DEFAULT_HOMEDIR;
        }
#else // DEFAULT_HOMEDIR
        if (getenv ("HOME") != 0) {
          server_global.home_directory = getenv ("HOME");
        }
#endif  // DEFAULT_HOMEDIR
        if (server_global.home_directory.length() == 0) {
          exiting_reason =
            gettext(" because no home directory is given in the "\
                    "environment. "\
                    "You can specify a directory to be used as home "\
                    "directory with the -H command line option.");
          goto FAIL;
        }
      }
      if (stat(server_global.home_directory.c_str(),
               & home_dir_stat)
          == -1) {
        perror(server_global.home_directory.c_str());
        fflush(stderr);
        exiting_reason =
          gettext(" because the home directory could not be accessed.");
        goto FAIL;
      }
      if (home_dir_stat.st_uid != process_uid) {
        exiting_reason =
          gettext(" because your home directory does not belong to you. "\
                  "You can specify another directory to be used as your "\
                  "home directory on the command line using the -H option.");
        goto FAIL;
      };
      if ((home_dir_stat.st_mode & S_IFDIR) == 0) {
        exiting_reason =
          gettext(" because your home directory is not a directory. "\
                  "You can specify another directory to be used as your "\
                  "home directory on the command line using the -H option.");
        goto FAIL;
      }
      if ((home_dir_stat.st_mode & S_IRUSR) == 0) {
        exiting_reason =
          gettext(" because your home directory is not readable for you. "\
                  "You can specify another directory to be used as your "\
                  "home directory on the command line using the -H option.");
        goto FAIL;
      }
      if ((home_dir_stat.st_mode & S_IXUSR) == 0) {
        exiting_reason =
          gettext(" because your home directory is not executable for you. "\
                  "You can specify another directory to be used as your "\
                  "home directory on the command line using the -H option.");
        goto FAIL;
      }
      if (chdir(server_global.home_directory.c_str()) == -1) {
        perror(server_global.home_directory.c_str());
        fflush(stderr);
        exiting_reason =
          gettext(" because unable to chdir to the home directory.");
        goto FAIL;
      }
      if (stat("./.cardwords", & cardwords_dir_stat) == -1) {
        cerr << "The .cardwords directory does not yet exist, creating it.\n";
        if (mkdir ("./.cardwords", 0755) == -1) {
          perror(gettext("creating the directory .cardwords "\
                         "inside the home dir"));
          fflush(stderr);
          exiting_reason =
            gettext(" because unable create the  directory `.cardwords' "\
                    "inside the home directory");
          goto FAIL;
        }
        stat("./.cardwords", & cardwords_dir_stat);
      }
      if ((cardwords_dir_stat.st_mode & S_IFDIR) == 0) {
        exiting_reason =
          gettext(" because .cardwords in your home directory "\
                  "is not a directory. "\
                  "You can specify another directory to be used as your "\
                  "home directory on the command line using the -H option.");
        goto FAIL;
      }
      if (cardwords_dir_stat.st_uid != process_uid) {
        exiting_reason =
          gettext(" because the .cardwords directory "\
                  "does not belong to you. ");
        goto FAIL;
      };
      if ((cardwords_dir_stat.st_mode & S_IRUSR) == 0) {
        exiting_reason =
          gettext(" because the .cardwords directory "\
                  "is not readable for you.");
        goto FAIL;
      }
      if ((cardwords_dir_stat.st_mode & S_IXUSR) == 0) {
        exiting_reason =
          gettext(" because the .cardwords directory "\
                  "is not executable for you.");
        goto FAIL;
      }
      if ((cardwords_dir_stat.st_mode & S_IWUSR) == 0) {
        exiting_reason =
          gettext(" because the .cardwords directory "\
                  "is not writeable for you.");
        goto FAIL;
      }
      if ((cardwords_dir_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
        cerr << gettext("WARNING: The `.cardwords' directory is writeable "
                        "for others. Better chmod it.") << endl;
      }
      if (stat("./.cardwords/owner_password", & cardwords_dir_stat) == 0) {
        // file exists
        if (unlink("./.cardwords/owner_password") == -1) {
          perror(gettext("unlinking ./.cardwords/owner_password"));
          fflush (stderr);
          exiting_reason =
            gettext(" because unable to delete old file "\
                    "./.cardwords/owner_password .");
          goto FAIL;
        }
      }
      {
        ofstream of("./.cardwords/owner_password",
                    ios::out | ios::noreplace,
                    0600);
        of << server_global.owner_password;
        if (of.bad()) {
          exiting_reason =
            gettext(" because unable to write file "\
                    "./.cardwords/owner_password .");
          goto FAIL;
        }
          
      }
      // password is now written.
    
      //create the game engin:
      if ( (server_global.no_of_players_planned
            + server_global.no_of_robots_planned)
           * server_global.bonus_table->get_hand_capacity()
           <= server_global.pile->get_cardCount()
           ) {
        server_global.engin =
          new CardWords_Game (server_global.no_of_players_planned,
                              server_global.no_of_robots_planned,
                              server_global.no_of_watchers_planned,
                              server_global.table,
                              server_global.pile,
                              server_global.bonus_table,
                              server_global.reserve_player_place_for_owner);
      }
      else { // not enough cards for all participants:
        ostrstream er;
        er << gettext(" because ")
           << server_global.no_of_players_planned
           << gettext(" players and ")
           << server_global.no_of_robots_planned
           << gettext(" robots would use more cards"\
                      " than there are in the pile (")
           << server_global.pile->get_cardCount()
           << gettext(").") << ends;
        er.freeze(true);
        exiting_reason = er.str();
        er.freeze(false);
        goto FAIL;
      }
      
      server_global.selector.run();
    
      return 0;
    }
  } // global try block ends here
  catch (CardWords_XLowResources xlr) {
    cerr << gettext ("Catched CardWords_XLowResources: ") << endl;
    cerr << xlr << endl;
  }
  catch (CardWords_XInternal_Error xie) {
    cerr << gettext ("Catched CardWords_XInternal_Error: ") << endl;
    cerr << xie << endl;
  }
  catch (CardWords_XOutOfRange xoor) {
    cerr << gettext ("Catched CardWords_XOutOfRange: ") << endl
         << xoor << endl;
  }
  catch (CardWords_XAccessingFile xaf) {
    cerr << gettext ("Catched CardWords_XAccessingFile: ") << endl
         << xaf << endl;
  }
  catch (CardWords_XBadInput xbi) {
    cerr << gettext ("Catched CardWords_XBadInput: ") << endl
         << xbi << endl;
  }
  catch (CardWords_Exception & x) {
    cerr << gettext("!!!! Exception caught in cardwords_server:")
         << endl
         << x
         << endl;
    return 1;
  }

 FAIL:
  cerr << "EXITING" <<  exiting_reason << endl;
  return 1;
}
