#!/usr/bin/env python
"""
EVENNIA SERVER STARTUP SCRIPT

This is the start point for running Evennia.

Sets the appropriate environmental variables and launches the server
and portal through the evennia_runner. Run without arguments to get a
menu. Run the script with the -h flag to see usage information.

"""
import os
import sys
import signal
import shutil
import importlib
from argparse import ArgumentParser
from subprocess import Popen, check_output, call, CalledProcessError, STDOUT
import django

# Signal processing
SIG = signal.SIGINT

# Set up the main python paths to Evennia
EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")

import evennia
EVENNIA_LIB = os.path.join(os.path.dirname(os.path.abspath(evennia.__file__)))
EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "evennia_runner.py")
EVENNIA_TEMPLATE = os.path.join(EVENNIA_LIB, "game_template")
EVENNIA_BINTESTING = os.path.join(EVENNIA_BIN, "testing")
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_BINTESTING, "dummyrunner.py")

TWISTED_BINARY = "twistd"

# Game directory structure
SETTINGFILE = "settings.py"
SERVERDIR = "server"
CONFDIR = os.path.join(SERVERDIR, "conf")
SETTINGS_PATH = os.path.join(CONFDIR, SETTINGFILE)
SETTINGS_DOTPATH = "server.conf.settings"
CURRENT_DIR = os.getcwd()
GAMEDIR = CURRENT_DIR

# Operational setup
SERVER_LOGFILE = None
PORTAL_LOGFILE = None
HTTP_LOGFILE = None
SERVER_PIDFILE = None
PORTAL_PIDFILE = None
SERVER_RESTART = None
PORTAL_RESTART = None
SERVER_PY_FILE = None
PORTAL_PY_FILE = None

PYTHON_MIN = '2.7'
TWISTED_MIN = '12.0'
DJANGO_MIN = '1.7'
DJANGO_REC = '1.7'

# add Evennia root and bin dir to PYTHONPATH
# note that we want to remove bin/ (path[0]) from
# pythonpath since there is otherwise a clash over
# the name evennia
sys.path[0] = EVENNIA_ROOT

#------------------------------------------------------------
#
# Messages
#
#------------------------------------------------------------

CREATED_NEW_GAMEDIR = \
    """
    Welcome to Evennia!
    Created a new Evennia game directory '{gamedir}'.

    You can now optionally edit your new settings file
    at {settings_path}. If you don't, the defaults
    will work out of the box. When ready to continue, 'cd' to your
    game directory and run:

       evennia migrate

    This initializes the database. To start the server for the first
    time, run:

       evennia -i start

    Make sure to create a superuser when asked for it. You should now
    be able to (by default) connect to your server on server
    'localhost', port 4000 using a telnet/mud client or
    http://localhost:8000 using your web browser. If things don't
    work, check so those ports are open.

    """

ERROR_NO_GAMEDIR = \
    """
    No Evennia settings file was found. You must run this command from
    inside a valid game directory first created with --init.
    """

WARNING_RUNSERVER = \
    """
    WARNING: There is no need to run the Django development
    webserver to test out Evennia web features (the web client
    will in fact not work since the Django test server knows
    nothing about MUDs).  Instead, just start Evennia with the
    webserver component active (this is the default).
    """

ERROR_SETTINGS = \
    """
    There was an error importing Evennia's config file {settingspath}. There is usually
    one of three reasons for this:
        1) You are not running this command from your game directory.
           Change directory to your game directory and try again (or
           create a new game directory using evennia --init <dirname>)
        2) The settings file contains a syntax error. If you see a
           traceback above, review it, resolve the problem and try again.
        3) Django is not correctly installed. This usually shows as
           errors mentioning 'DJANGO_SETTINGS_MODULE'. If you run a
           virtual machine, it might be worth to restart it to see if
           this resolves the issue.
    """.format(settingsfile=SETTINGFILE, settingspath=SETTINGS_PATH)

ERROR_DATABASE = \
    """
    Your database does not seem to be set up correctly.
    (error was '{traceback}')

    Standing in your game directory, try to run

       evennia migrate

    to initialize/update the database according to your settings.
    """

ERROR_WINDOWS_WIN32API = \
    """
    ERROR: Unable to import win32api, which Twisted requires to run.
    You may download it from:

    http://sourceforge.net/projects/pywin32
      or
    http://starship.python.net/crew/mhammond/win32/Downloads.html
    """

INFO_WINDOWS_BATFILE = \
    """
    INFO: Since you are running Windows, a file 'twistd.bat' was
    created for you. This is a simple batch file that tries to call
    the twisted executable. Evennia determined this to be:

       %(twistd_path)s

    If you run into errors at startup you might need to edit
    twistd.bat to point to the actual location of the Twisted
    executable (usually called twistd.py) on your machine.

    This procedure is only done once. Run evennia.py again when you
    are ready to start the server.
    """

CMDLINE_HELP = \
    """
    Starts or operates the Evennia MU* server. Also allows for
    initializing a new game directory and managing the game's
    database. You can also pass django manage.py arguments through
    this launcher. If you need manage.py --options, use djangoadmin
    directly instead.
    """


VERSION_INFO = \
    """
    {about}
    Evennia {version}
    OS: {os}
    Python: {python}
    Twisted: {twisted}
    Django: {django}
    """

ABOUT_INFO= \
    """
    Evennia MUD/MUX/MU* development system

    Licence: BSD 3-Clause Licence
    Web: http://www.evennia.com
    Irc: #evennia on FreeNode
    Forum: http://www.evennia.com/discussions
    Maintainer (2010-):   Griatch (griatch AT gmail DOT com)
    Maintainer (2006-10): Greg Taylor

    Use -h for command line options.
    """

HELP_ENTRY = \
    """
    Enter 'evennia -h' for command-line options.

    Use option (1) in a production environment.  During development (2) is
    usually enough, portal debugging is usually only useful if you are
    adding new protocols or are debugging Evennia itself.

    Reload with (5) to update the server with your changes without
    disconnecting any players.

    Note: Reload and stop are sometimes poorly supported in Windows. If you have
    issues, log into the game to stop or restart the server instead.
    """

MENU = \
    """
    +----Evennia Launcher-------------------------------------------+
    |                                                               |
    +--- Starting --------------------------------------------------+
    |                                                               |
    |  1) (normal):       All output to logfiles                    |
    |  2) (server devel): Server logs to terminal (-i option)       |
    |  3) (portal devel): Portal logs to terminal                   |
    |  4) (full devel):   Both Server and Portal logs to terminal   |
    |                                                               |
    +--- Restarting ------------------------------------------------+
    |                                                               |
    |  5) Reload the Server                                         |
    |  6) Reload the Portal (only works with portal/full debug)     |
    |                                                               |
    +--- Stopping --------------------------------------------------+
    |                                                               |
    |  7) Stopping both Portal and Server                           |
    |  8) Stopping only Server                                      |
    |  9) Stopping only Portal                                      |
    |                                                               |
    +---------------------------------------------------------------+
    |  h) Help              i) About info               q) Abort    |
    +---------------------------------------------------------------+
    """

ERROR_PYTHON_VERSION = \
    """
    ERROR: Python {pversion} used. Evennia requires version
    {python_min} or higher (but not 3.x).
    """

WARNING_TWISTED_VERSION = \
    """
    WARNING: Twisted {tversion} found. Evennia recommends
    v{twisted_min} or higher."
    """

ERROR_NOTWISTED = \
    """
    ERROR: Twisted does not seem to be installed.
    """

ERROR_DJANGO_MIN = \
    """
    ERROR: Django {dversion} found. Evennia requires version
    {django_min} or higher.
    """

NOTE_DJANGO_MIN = \
    """
    NOTE: Django {dversion} found. This will work, but v{django_rec}
    is recommended for production.
    """

NOTE_DJANGO_NEW = \
    """
    NOTE: Django {dversion} found. This is newer than Evennia's
    recommended version (v{django_rec}). It will probably work, but
    may be new enough not to be fully tested yet. Report any issues."
    """

ERROR_NODJANGO = \
    """
    ERROR: Django does not seem to be installed.
    """

#------------------------------------------------------------
#
# Functions
#
#------------------------------------------------------------

def evennia_version():
    """
    Get the Evennia version info from the main package.
    """
    version = "Unknown"
    try:
        import evennia
        version = evennia.__version__
    except ImportError:
        pass
    try:
        version = "%s (rev %s)" % (version, check_output("git rev-parse --short HEAD", shell=True, cwd=EVENNIA_ROOT, stderr=STDOUT).strip())
    except (IOError, CalledProcessError):
        pass
    return version

EVENNIA_VERSION = evennia_version()


def check_main_evennia_dependencies():
    """
    Checks and imports the Evennia dependencies. This must be done
    already before the paths are set up.
    """
    error = False

    # Python
    pversion = ".".join(str(num) for num in sys.version_info if type(num) == int)
    if pversion < PYTHON_MIN:
        print ERROR_PYTHON_VERSION.format(pversion=pversion, python_min=PYTHON_MIN)
        error = True
    # Twisted
    try:
        import twisted
        tversion = twisted.version.short()
        if tversion < TWISTED_MIN:
            print WARNING_TWISTED_VERSION.format(tversion=tversion, twisted_min=TWISTED_MIN)
    except ImportError:
        print ERROR_NOTWISTED
        error = True
    # Django
    try:
        dversion = ".".join(str(num) for num in django.VERSION if type(num) == int)
        # only the main version (1.5, not 1.5.4.0)
        dversion_main = ".".join(dversion.split(".")[:2])
        if dversion < DJANGO_MIN:
            print ERROR_DJANGO_MIN.format(dversion=dversion_main, django_min=DJANGO_MIN)
            error = True
        elif DJANGO_MIN <= dversion < DJANGO_REC:
            print NOTE_DJANGO_MIN.format(dversion=dversion_main, django_rec=DJANGO_REC)
        elif DJANGO_REC < dversion_main:
            print NOTE_DJANGO_NEW.format(dversion=dversion_main, django_rec=DJANGO_REC)
    except ImportError:
        print ERROR_NODJANGO
        error = True
    if error:
        sys.exit()


def set_gamedir(path):
    """
    Set GAMEDIR based on path, by figuring out where the setting file
    is inside the directory tree.
    """

    global GAMEDIR
    if os.path.exists(os.path.join(path, SETTINGS_PATH)):
        # path at root of game dir
        GAMEDIR = os.path.abspath(path)
    elif os.path.exists(os.path.join(path, os.path.pardir, SETTINGS_PATH)):
        # path given to somewhere one level down
        GAMEDIR = os.path.dirname(path)
    elif os.path.exists(os.path.join(path, os.path.pardir, os.path.pardir, SETTINGS_PATH)):
        # path given to somwhere two levels down
        GAMEDIR = os.path.dirname(os.path.dirname(path))
    elif os.path.exists(os.path.join(path, os.path.pardir, os.path. pardir, os.path.pardir, SETTINGS_PATH)):
        # path given to somewhere three levels down (custom directories)
        GAMEDIR = os.path.dirname(os.path.dirname(os.path.dirname(path)))
    else:
        # we don't look further down than this ...
        print ERROR_NO_GAMEDIR
        sys.exit()


def create_secret_key():
    """
    Randomly create the secret key for the settings file
    """
    import random
    import string
    secret_key = list((string.letters +
        string.digits + string.punctuation).replace("\\", "").replace("'", '"'))
    random.shuffle(secret_key)
    secret_key = "".join(secret_key[:40])
    return secret_key


def create_settings_file():
    """
    Uses the template settings file to build a working
    settings file.
    """
    settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py")
    with open(settings_path, 'r') as f:
        settings_string = f.read()

    # tweak the settings
    setting_dict = {"settings_default": os.path.join(EVENNIA_LIB, "settings_default.py"),
                    "servername":"\"%s\"" % GAMEDIR.rsplit(os.path.sep, 1)[1].capitalize(),
                    "game_dir":"\"%s\"" % GAMEDIR,
                    "secret_key":"\'%s\'" % create_secret_key()}

    # modify the settings
    settings_string = settings_string.format(**setting_dict)

    with open(settings_path, 'w') as f:
        f.write(settings_string)


def create_game_directory(dirname):
    """
    Initialize a new game directory named dirname
    at the current path. This means copying the
    template directory from evennia's root.
    """
    global GAMEDIR
    GAMEDIR = os.path.abspath(os.path.join(CURRENT_DIR, dirname))
    if os.path.exists(GAMEDIR):
        print "Cannot create new Evennia game dir: '%s' already exists." % dirname
        sys.exit()
    # copy template directory
    shutil.copytree(EVENNIA_TEMPLATE, GAMEDIR)
    # pre-build settings file in the new GAMEDIR
    create_settings_file()


def create_superuser():
    print "\nCreate a superuser below. The superuser is Player #1, the 'owner' account of the server.\n"
    django.core.management.call_command("createsuperuser", interactive=True)


def check_database(exit_on_error=False):
    """
    Check database exists
    """
    # Check so a database exists and is accessible
    from django.db import connection
    tables = connection.introspection.get_table_list(connection.cursor())
    if tables and u'players_playerdb' in tables:
        # database exists and seems set up. Initialize evennia.
        import evennia
        evennia.init()
    else:
        if exit_on_error:
            print ERROR_DATABASE.format(traceback=e)
            sys.exit()
        return False
    return True
    # Try to get Player#1
    from evennia.players.models import PlayerDB
    try:
        PlayerDB.objects.get(id=1)
    except PlayerDB.DoesNotExist:
        # no superuser yet. We need to create it.
        create_superuser()
    return True


def getenv():
    """
    Get current environment and add PYTHONPATH
    """
    sep = ";" if os.name == 'nt' else ":"
    env = os.environ.copy()
    env['PYTHONPATH'] = sep.join(sys.path)
    return env


def get_pid(pidfile):
    """
    Get the PID (Process ID) by trying to access
    an PID file.
    """
    pid = None
    if os.path.exists(pidfile):
        f = open(pidfile, 'r')
        pid = f.read()
    return pid


def del_pid(pidfile):
    """
    The pidfile should normally be removed after a process has
    finished, but when sending certain signals they remain, so we need
    to clean them manually.
    """
    if os.path.exists(pidfile):
        os.remove(pidfile)


def kill(pidfile, signal=SIG, succmsg="", errmsg="", restart_file=SERVER_RESTART, restart=False):
    """
    Send a kill signal to a process based on PID. A customized
    success/error message will be returned. If clean=True, the system
    will attempt to manually remove the pid file.
    """
    pid = get_pid(pidfile)
    if pid:
        if os.name == 'nt':
            os.remove(pidfile)
        # set restart/norestart flag
        if restart:
            django.core.management.call_command('collectstatic', interactive=False, verbosity=0)
            with open(restart_file, 'w') as f:
                f.write("reload")
        else:
            with open(restart_file, 'w') as f:
                f.write("shutdown")
        try:
            os.kill(int(pid), signal)
        except OSError:
            print "Process %(pid)s cannot be stopped. "\
                  "The PID file 'server/%(pidfile)s' seems stale. "\
                  "Try removing it." % {'pid': pid, 'pidfile': pidfile}
            return
        print "Evennia:", succmsg
        return
    print "Evennia:", errmsg


def show_version_info(about=False):
    """
    Display version info
    """
    import os, sys
    import twisted
    import django

    return VERSION_INFO.format(version=EVENNIA_VERSION,
                             about=ABOUT_INFO if about else "",
                             os=os.name, python=sys.version.split()[0],
                             twisted=twisted.version.short(),
                             django=django.get_version())


def error_check_python_modules():
    """
    Import settings modules in settings. This will raise exceptions on
    pure python-syntax issues which are hard to catch gracefully
    with exceptions in the engine (since they are formatting errors in
    the python source files themselves). Best they fail already here
    before we get any further.
    """
    from django.conf import settings
    def imp(path, split=True):
        mod, fromlist = path, "None"
        if split:
            mod, fromlist = path.rsplit('.', 1)
        __import__(mod, fromlist=[fromlist])

    # core modules
    imp(settings.COMMAND_PARSER)
    imp(settings.SEARCH_AT_RESULT)
    imp(settings.SEARCH_AT_MULTIMATCH_INPUT)
    imp(settings.CONNECTION_SCREEN_MODULE)
    #imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False)
    for path in settings.LOCK_FUNC_MODULES:
        imp(path, split=False)
    # cmdsets

    deprstring = "settings.%s should be renamed to %s. If defaults are used, " \
                 "their path/classname must be updated (see evennia/settings_default.py)."
    if hasattr(settings, "CMDSET_DEFAULT"):
        raise DeprecationWarning(deprstring % ("CMDSET_DEFAULT", "CMDSET_CHARACTER"))
    if hasattr(settings, "CMDSET_OOC"):
        raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_PLAYER"))
    if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple):
        raise DeprecationWarning("settings.WEBSERVER_PORTS must be on the form [(proxyport, serverport), ...]")
    if hasattr(settings, "BASE_COMM_TYPECLASS"):
        raise DeprecationWarning(deprstring % ("BASE_COMM_TYPECLASS", "BASE_CHANNEL_TYPECLASS"))
    if hasattr(settings, "COMM_TYPECLASS_PATHS"):
        raise DeprecationWarning(deprstring % ("COMM_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS"))
    if hasattr(settings, "CHARACTER_DEFAULT_HOME"):
        raise DeprecationWarning("settings.CHARACTER_DEFAULT_HOME should be renamed to DEFAULT_HOME. " \
                "See also settings.START_LOCATION (see evennia/settings_default.py).")

    from evennia.commands import cmdsethandler
    if not cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None): print "Warning: CMDSET_UNLOGGED failed to load!"
    if not cmdsethandler.import_cmdset(settings.CMDSET_CHARACTER, None): print "Warning: CMDSET_CHARACTER failed to load"
    if not cmdsethandler.import_cmdset(settings.CMDSET_PLAYER, None): print "Warning: CMDSET_PLAYER failed to load"
    # typeclasses
    imp(settings.BASE_PLAYER_TYPECLASS)
    imp(settings.BASE_OBJECT_TYPECLASS)
    imp(settings.BASE_CHARACTER_TYPECLASS)
    imp(settings.BASE_ROOM_TYPECLASS)
    imp(settings.BASE_EXIT_TYPECLASS)
    imp(settings.BASE_SCRIPT_TYPECLASS)


def init_game_directory(path, check_db=True):
    """
    Try to analyze the given path to find settings.py - this defines
    the game directory and also sets PYTHONPATH as well as the
    django path.
    """
    # set the GAMEDIR path
    set_gamedir(path)

    # Add gamedir to python path
    sys.path.insert(1, GAMEDIR)

    # Prepare django; set the settings location
    os.environ['DJANGO_SETTINGS_MODULE'] = SETTINGS_DOTPATH

    # required since django1.7
    django.setup()

    # test existence of the settings module
    try:
        from django.conf import settings
    except Exception, ex:
        if not str(ex).startswith("No module named"):
            import traceback
            print traceback.format_exc().strip()
        print ERROR_SETTINGS
        sys.exit()

    # this will both check the database and initialize the evennia dir.
    if check_db:
        check_database()

    # set up the Evennia executables and log file locations
    global SERVER_PY_FILE, PORTAL_PY_FILE
    global SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE
    global SERVER_PIDFILE, PORTAL_PIDFILE
    global SERVER_RESTART, PORTAL_RESTART
    global EVENNIA_VERSION

    SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
    PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "portal", "portal", "portal.py")

    SERVER_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "server.pid")
    PORTAL_PIDFILE = os.path.join(GAMEDIR, SERVERDIR, "portal.pid")

    SERVER_RESTART = os.path.join(GAMEDIR, SERVERDIR, "server.restart")
    PORTAL_RESTART = os.path.join(GAMEDIR, SERVERDIR, "portal.restart")

    SERVER_LOGFILE = settings.SERVER_LOG_FILE
    PORTAL_LOGFILE = settings.PORTAL_LOG_FILE
    HTTP_LOGFILE = settings.HTTP_LOG_FILE

    if os.name == 'nt':
        # We need to handle Windows twisted separately. We create a
        # batchfile in game/server, linking to the actual binary

        global TWISTED_BINARY
        TWISTED_BINARY = "twistd.bat"

        # add path so system can find the batfile
        sys.path.insert(1, os.path.join(GAMEDIR, SERVERDIR))

        try:
            importlib.import_module("win32api")
        except ImportError:
            print ERROR_WINDOWS_WIN32API
            sys.exit()

        if not os.path.exists(os.path.join(EVENNIA_BIN, TWISTED_BINARY)):
            # Test for executable twisted batch file. This calls the
            # twistd.py executable that is usually not found on the
            # path in Windows.  It's not enough to locate
            # scripts.twistd, what we want is the executable script
            # C:\PythonXX/Scripts/twistd.py. Alas we cannot hardcode
            # this location since we don't know if user has Python in
            # a non-standard location. So we try to figure it out.
            twistd = importlib.import_module("twisted.scripts.twistd")
            twistd_dir = os.path.dirname(twistd.__file__)

            # note that we hope the twistd package won't change here, since we
            # try to get to the executable by relative path.
            twistd_path = os.path.abspath(os.path.join(twistd_dir,
                            os.pardir, os.pardir, os.pardir, os.pardir,
                            'scripts', 'twistd.py'))

            with open('twistd.bat', 'w') as bat_file:
                # build a custom bat file for windows
                bat_file.write("@\"%s\" \"%s\" %%*" % (sys.executable, twistd_path))

            print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path)

def run_dummyrunner(number_of_dummies):
    """
    Start an instance of the dummyrunner

    The dummy players' behavior can be customized by adding a
    dummyrunner_settings.py config file in the game's conf directory.
    """
    number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1
    cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies]
    config_file  = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py")
    if os.path.exists(config_file):
        cmdstr.extend(["--config", config_file])
    try:
        call(cmdstr, env=getenv())
    except KeyboardInterrupt:
        pass

def run_menu():
    """
    This launches an interactive menu.
    """
    while True:
        # menu loop

        print MENU
        inp = raw_input(" option > ")

        # quitting and help
        if inp.lower() == 'q':
            return
        elif inp.lower() == 'h':
            print HELP_ENTRY
            raw_input("press <return> to continue ...")
            continue
        elif inp.lower() in ('v', 'i', 'a'):
            print show_version_info(about=True)
            raw_input("press <return> to continue ...")
            continue

        # options
        try:
            inp = int(inp)
        except ValueError:
            print "Not a valid option."
            continue
        if inp == 1:
            # start everything, log to log files
            server_operation("start", "all", False, False)
        elif inp == 2:
            # start everything, server interactive start
            server_operation("start", "all", True, False)
        elif inp == 3:
            # start everything, portal interactive start
            server_operation("start", "server", False, False)
            server_operation("start", "portal", True, False)
        elif inp == 4:
            # start both server and portal interactively
            server_operation("start", "server", True, False)
            server_operation("start", "portal", True, False)
        elif inp == 5:
            # reload the server
            server_operation("reload", "server", None, None)
        elif inp == 6:
            # reload the portal
            server_operation("reload", "portal", None, None)
        elif inp == 7:
            # stop server and portal
            server_operation("stop", "all", None, None)
        elif inp == 8:
            # stop server
            server_operation("stop", "server", None, None)
        elif inp == 9:
            # stop portal
            server_operation("stop", "portal", None, None)
        else:
            print "Not a valid option."
            continue
        return


def server_operation(mode, service, interactive, profiler):
    """
    Handle argument options given on the command line.

    mode - str; start/stop etc
    service - str; server, portal or all
    interactive - bool; use interactive mode or daemon
    profiler - run the service under the profiler
    """

    cmdstr = [sys.executable, EVENNIA_RUNNER]
    errmsg = "The %s does not seem to be running."

    if mode == 'start':

        # launch the error checker. Best to catch the errors already here.
        error_check_python_modules()

        # starting one or many services
        if service == 'server':
            if profiler:
                cmdstr.append('--pserver')
            if interactive:
                cmdstr.append('--iserver')
            cmdstr.append('--noportal')
        elif service == 'portal':
            if profiler:
                cmdstr.append('--pportal')
            if interactive:
                cmdstr.append('--iportal')
            cmdstr.append('--noserver')
            django.core.management.call_command('collectstatic', verbosity=1, interactive=False)
        else:  # all
            # for convenience we don't start logging of
            # portal, only of server with this command.
            if profiler:
                cmdstr.append('--pserver') # this is the common case
            if interactive:
                cmdstr.append('--iserver')
            django.core.management.call_command('collectstatic', verbosity=1, interactive=False)
        cmdstr.extend([GAMEDIR, TWISTED_BINARY, SERVER_LOGFILE, PORTAL_LOGFILE, HTTP_LOGFILE])
        # start the server
        Popen(cmdstr, env=getenv())

    elif mode == 'reload':
        # restarting services
        if os.name == 'nt':
            print "Restarting from command line is not supported under Windows. Log into the game to restart."
            return
        if service == 'server':
            kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % 'Server', SERVER_RESTART, restart=True)
        elif service == 'portal':
            print """
          Note: Portal usually doesnt't need to be reloaded unless you are debugging in interactive mode.
          If Portal was running in default Daemon mode, it cannot be restarted. In that case you have
          to restart it manually with 'evennia.py start portal'
          """
            kill(PORTAL_PIDFILE, SIG, "Portal reloaded (or stopped, if it was in daemon mode).", errmsg % 'Portal', PORTAL_RESTART, restart=True)
        else: # all
            # default mode, only restart server
            kill(SERVER_PIDFILE, SIG, "Server reload.", errmsg % 'Server', SERVER_RESTART, restart=True)

    elif mode == 'stop':
        # stop processes, avoiding reload
        if service == 'server':
            kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', SERVER_RESTART)
        elif service == 'portal':
            kill(PORTAL_PIDFILE, SIG, "Portal stopped.", errmsg % 'Portal', PORTAL_RESTART)
        else:
            kill(PORTAL_PIDFILE, SIG, "Portal stopped.", errmsg % 'Portal', PORTAL_RESTART)
            kill(SERVER_PIDFILE, SIG, "Server stopped.", errmsg % 'Server', SERVER_RESTART)



def main():
    """
    Run the evennia main program.
    """

    # set up argument parser

    parser = ArgumentParser(description=CMDLINE_HELP)
    parser.add_argument('-v', '--version', action='store_true',
                      dest='show_version', default=False,
                      help="Show version info.")
    parser.add_argument('-i', '--interactive', action='store_true',
                      dest='interactive', default=False,
                      help="Start given processes in interactive mode.")
    parser.add_argument('--init', action='store', dest="init", metavar="name",
                        help="Creates a new game directory 'name' at the current location.")
    parser.add_argument('--profiler', action='store_true', dest='profiler', default=False,
                      help="Start given server component under the Python profiler.")
    parser.add_argument('--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N",
                        help="Tests a running server by connecting N dummy players to it.")
    parser.add_argument("mode", metavar="option", nargs='?', default="help",
                        help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).")
    parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all",
                        help="Which server component to operate on. One of server, portal or all (default).")

    args = parser.parse_args()

    # handle arguments

    mode, service = args.mode, args.service

    check_main_evennia_dependencies()

    if args.init:
        create_game_directory(args.init)
        print CREATED_NEW_GAMEDIR.format(gamedir=args.init,
                                         settings_path=os.path.join(args.init, SETTINGS_PATH))
        sys.exit()

    if args.show_version:
        print show_version_info(mode=="help")
        sys.exit()
    if mode == "help" and not args.dummyrunner:
        print ABOUT_INFO
        sys.exit()
    check_db = not mode == "migrate"

    # this must be done first - it sets up all the global properties
    # and initializes django for the game directory
    init_game_directory(CURRENT_DIR, check_db=check_db)

    if args.dummyrunner:
        # launch the dummy runner
        run_dummyrunner(args.dummyrunner[0])
    elif mode == 'menu':
        # launch menu for operation
        run_menu()
    elif mode in ('start', 'reload', 'stop'):
        # operate the server directly
        server_operation(mode, service, args.interactive, args.profiler)
    else:
        # pass-through to django manager
        if mode in ('runserver', 'testserver'):
            print WARNING_RUNSERVER
        django.core.management.call_command(mode)


if __name__ == '__main__':
    # start Evennia from the command line
    main()
