/* Copyright (C) 2003 MySQL AB

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

#include <ndb_global.h>
#include <ndb_opts.h>

#include "MgmtSrvr.hpp"
#include "EventLogger.hpp"
#include <Config.hpp>
#include "InitConfigFileParser.hpp"
#include <SocketServer.hpp>
#include "Services.hpp"
#include <version.h>
#include <kernel_types.h>
#include <Properties.hpp>
#include <NdbOut.hpp>
#include <NdbMain.h>
#include <NdbDaemon.h>
#include <NdbConfig.h>
#include <NdbHost.h>
#include <ndb_version.h>
#include <ConfigRetriever.hpp>
#include <mgmapi_config_parameters.h>

#include <NdbAutoPtr.hpp>

#if defined NDB_OSE || defined NDB_SOFTOSE
#include <efs.h>
#else
#include "CommandInterpreter.hpp"
#endif

#undef DEBUG
#define DEBUG(x) ndbout << x << endl;

const char progname[] = "mgmtsrvr";


/**
 * @struct  MgmGlobals
 * @brief   Global Variables used in the management server
 ******************************************************************************/
struct MgmGlobals {
  MgmGlobals();
  ~MgmGlobals();
  
  /** Command line arguments  */
  int daemon;   // NOT bool, bool need not be int
  int non_interactive;
  int interactive;
  const char * config_filename;
  const char * local_config_filename;
  
  /** Stuff found in environment or in local config  */
  NodeId localNodeId;
  bool use_specific_ip;
  char * interface_name;
  int port;
  
  /** The configuration of the cluster */
  Config * cluster_config;
  
  /** The Mgmt Server */
  MgmtSrvr * mgmObject;
  
  /** The Socket Server */
  SocketServer * socketServer;
};

int g_no_nodeid_checks= 0;
static MgmGlobals glob;


/******************************************************************************
 * Function prototypes
 ******************************************************************************/
static bool readLocalConfig();
static bool readGlobalConfig();

/**
 * Global variables
 */
bool g_StopServer;
extern EventLogger g_EventLogger;

extern int global_mgmt_server_check;
static char *opt_connect_str= 0;

static struct my_option my_long_options[] =
{
  NDB_STD_OPTS("ndb_mgm"),
  { "config-file", 'f', "Specify cluster configuration file",
    (gptr*) &glob.config_filename, (gptr*) &glob.config_filename, 0,
    GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
  { "daemon", 'd', "Run ndb_mgmd in daemon mode (default)",
    (gptr*) &glob.daemon, (gptr*) &glob.daemon, 0,
    GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0 },
  { "l", 'l', "Specify configuration file connect string (default Ndb.cfg if available)",
    (gptr*) &glob.local_config_filename, (gptr*) &glob.local_config_filename, 0,
    GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 },
  { "interactive", 256, "Run interactive. Not supported but provided for testing purposes",
    (gptr*) &glob.interactive, (gptr*) &glob.interactive, 0,
    GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 },
  { "no-nodeid-checks", 257, "Do not provide any node id checks", 
    (gptr*) &g_no_nodeid_checks, (gptr*) &g_no_nodeid_checks, 0,
    GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 },
  { "nodaemon", 258, "Don't run as daemon, but don't read from stdin",
    (gptr*) &glob.non_interactive, (gptr*) &glob.non_interactive, 0,
    GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};
static void short_usage_sub(void)
{
  printf("Usage: %s [OPTIONS]\n", my_progname);
}
static void print_version()
{
  printf("MySQL distrib %s, for %s (%s)\n",MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE);
}
static void usage()
{
  short_usage_sub();
  print_version();
  my_print_help(my_long_options);
  my_print_variables(my_long_options);
}
static my_bool
get_one_option(int optid, const struct my_option *opt __attribute__((unused)),
	       char *argument)
{
  switch (optid) {
  case '#':
    DBUG_PUSH(argument ? argument : "d:t:O,/tmp/ndb_mgmd.trace");
    break;
  case 'V':
    print_version();
    exit(0);
  case '?':
    usage();
    exit(0);
  }
  return 0;
}

/*
 *  MAIN 
 */
int main(int argc, char** argv)
{
  NDB_INIT(argv[0]);

  /**
   * OSE specific. Enable shared ownership of file system resources. 
   * This is needed in order to use the cluster log since the events 
   * from the cluster is written from the 'ndb_receive'(NDBAPI) thread/process.
   */
#if defined NDB_OSE || defined NDB_SOFTOSE
  efs_segment_share();
#endif

  global_mgmt_server_check = 1;
  glob.config_filename= "config.ini";

  const char *load_default_groups[]= { "ndb_mgmd",0 };
  load_defaults("my",load_default_groups,&argc,&argv);

  int ho_error;
  if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option)))
    exit(ho_error);

  if (glob.interactive ||
      glob.non_interactive) {
    glob.daemon= 0;
  }

  glob.socketServer = new SocketServer();

  MgmApiService * mapi = new MgmApiService();

  /****************************
   * Read configuration files *
   ****************************/
  LocalConfig local_config;
  if(!local_config.init(0,glob.local_config_filename)){
    local_config.printError();
    goto error_end;
  }
  glob.localNodeId = local_config._ownNodeId;

  if (!readGlobalConfig())
    goto error_end;

  glob.mgmObject = new MgmtSrvr(glob.localNodeId,
				BaseString(glob.config_filename),
				local_config,
				glob.cluster_config);

  chdir(NdbConfig_get_path(0));

  glob.cluster_config = 0;
  glob.localNodeId= glob.mgmObject->getOwnNodeId();

  if (glob.localNodeId == 0) {
    goto error_end;
  }

  glob.port= glob.mgmObject->getPort();

  if (glob.port == 0)
    goto error_end;

  glob.interface_name = 0;
  glob.use_specific_ip = false;

  if(!glob.use_specific_ip){
    int count= 5; // no of retries for tryBind
    while(!glob.socketServer->tryBind(glob.port, glob.interface_name)){
      if (--count > 0) {
	NdbSleep_MilliSleep(1000);
	continue;
      }
      ndbout_c("Unable to setup port: %s:%d!\n"
	       "Please check if the port is already used,\n"
	       "(perhaps a ndb_mgmd is already running),\n"
	       "and if you are executing on the correct computer", 
	       (glob.interface_name ? glob.interface_name : "*"), glob.port);
      goto error_end;
    }
    free(glob.interface_name);
    glob.interface_name = 0;
  }

  if(!glob.socketServer->setup(mapi, glob.port, glob.interface_name)){
    ndbout_c("Unable to setup management port: %d!\n"
	     "Please check if the port is already used,\n"
	     "(perhaps a ndb_mgmd is already running),\n"
	     "and if you are executing on the correct computer", 
	     glob.port);
    delete mapi;
    goto error_end;
  }
  
  if(!glob.mgmObject->check_start()){
    ndbout_c("Unable to check start management server.");
    ndbout_c("Probably caused by illegal initial configuration file.");
    goto error_end;
  }

  if (glob.daemon) {
    // Become a daemon
    char *lockfile= NdbConfig_PidFileName(glob.localNodeId);
    char *logfile=  NdbConfig_StdoutFileName(glob.localNodeId);
    NdbAutoPtr<char> tmp_aptr1(lockfile), tmp_aptr2(logfile);

    if (NdbDaemon_Make(lockfile, logfile, 0) == -1) {
      ndbout << "Cannot become daemon: " << NdbDaemon_ErrorText << endl;
      return 1;
    }
  }

  signal(SIGPIPE, SIG_IGN);
  {
    BaseString error_string;
    if(!glob.mgmObject->start(error_string)){
      ndbout_c("Unable to start management server.");
      ndbout_c("Probably caused by illegal initial configuration file.");
      ndbout_c(error_string.c_str());
      goto error_end;
    }
  }

  //glob.mgmObject->saveConfig();
  mapi->setMgm(glob.mgmObject);

  char msg[256];
  BaseString::snprintf(msg, sizeof(msg),
	   "NDB Cluster Management Server. %s", NDB_VERSION_STRING);
  ndbout_c(msg);
  g_EventLogger.info(msg);

  BaseString::snprintf(msg, 256, "Id: %d, Command port: %d",
	   glob.localNodeId, glob.port);
  ndbout_c(msg);
  g_EventLogger.info(msg);
  
  g_StopServer = false;
  glob.socketServer->startServer();

#if ! defined NDB_OSE && ! defined NDB_SOFTOSE
  if(glob.interactive) {
    CommandInterpreter com(* glob.mgmObject);
    while(com.readAndExecute());
  } else 
#endif
    {
      while(g_StopServer != true)
	NdbSleep_MilliSleep(500);
    }
  
  g_EventLogger.info("Shutting down server...");
  glob.socketServer->stopServer();
  glob.socketServer->stopSessions();
  g_EventLogger.info("Shutdown complete");
  return 0;
 error_end:
  return 1;
}

MgmGlobals::MgmGlobals(){
  // Default values
  port = 0;
  config_filename = NULL;
  local_config_filename = NULL;
  interface_name = 0;
  cluster_config = 0;
  daemon = 1;
  non_interactive = 0;
  interactive = 0;
  socketServer = 0;
  mgmObject = 0;
}

MgmGlobals::~MgmGlobals(){
  if (socketServer)
    delete socketServer;
  if (mgmObject)
    delete mgmObject;
  if (cluster_config) 
    delete cluster_config;
  if (interface_name)
    free(interface_name);
}

/**
 * @fn      readGlobalConfig
 * @param   glob : Global variables
 * @return  true if success, false otherwise.
 */
static bool
readGlobalConfig() {
  if(glob.config_filename == NULL)
    return false;

  /* Use config file */
  InitConfigFileParser parser;
  glob.cluster_config = parser.parseConfig(glob.config_filename);
  if(glob.cluster_config == 0){
    return false;
  }
  return true;
}