import sys, signal, time, os

from Ft.Server.Server import Ipc

PID_RUNNING = 0
PID_DEAD = 1
PID_STOPPED = 2
PID_NOT_OWNER = 3

# This get mapped to the appropriate platform launcher
# (will be set later)
Launcher = None

def RemovePid(pidFile):
    try:
        if os.path.exists(pidFile):
            os.remove(pidFile)
    except:
        pass
    return

def WaitPid(pidFile, seconds=30):
    timeout = time.time() + seconds
    while os.path.exists(pidFile) and time.time() < timeout:
        time.sleep(0.1)
    return os.path.exists(pidFile)

def _GetExecutable(name):
    """
    This function is for private usage of Ft/Server/Server
    implementation only. It returns the location of the 4ssd program
    which is not in the default search PATH for executable applications
    due to its pre-dependency on 4ss_manager.
    Please do _not_ use it as general purpose function to determine the
    location of other executable applications on your platform.
    """
    from Ft import GetConfigVar
    executable = os.path.join(GetConfigVar('BINDIR'), name)
    if sys.platform == 'win32':
        # Determine if we are running a debug Python
        if sys.executable.endswith('_d.exe'):
            executable += '_d.exe'
        else:
            executable += '.exe'
    return executable

class LauncherBase:

    name = '4ssd'

    def __init__(self, username, password, properties):
        # username, password will provided from the commands authentication
        # properties refer to the configuration used in the command

        self.pidFile = properties['PidFile']
        if not os.path.exists(os.path.dirname(self.pidFile)):
            raise Exception("Unable to use pid file '%s', path is invalid" %
                            self.pidFile)

        logfile = properties['LogFile']
        if not os.path.exists(os.path.dirname(logfile)):
            raise Exception("Unable to use log file '%s', path is invalid" %
                            logfile)

        executable = _GetExecutable(self.name)

        args = [self.name]

        # Create the sub-process environment
        # os.environ is a UserDict, we need a real dictionary
        environ = os.environ.data.copy()

        data = (username, password, properties['CoreId'])
        environ['FTSS_ENVIRONMENT'] = repr(data)
        environ['FTSERVER_CONFIG_FILE'] = properties['ConfigFile']

        self.process = Ipc.Process(executable, args, environ)
        return

    def info(self, msg):
        sys.stdout.write(msg + '\n')
        return

    def error(self, msg):
        msg = '[ERROR] ' + msg
        sys.stderr.write(msg + '\n')
        raise Exception(msg)

    def _remove_pid(self):
        if os.path.exists(self.pidFile):
            try:
                os.remove(self.pidFile)
            except Exception, error:
                sys.stderr.write("Unable to remove pid file '%s': %s\n" %
                                 (self.pidFile, str(error)))
        return

    def stop(self):
        (pid, status) = self.status()
        self._remove_pid()
        if status == PID_RUNNING:
            self.info('Stopping %s' % self.name)
            self._stop(pid)
        elif status == PID_DEAD:
            self.info('%s (pid %d?) not running' % (self.name, pid))
        else:
            self.info('%s (no pid file) is not running' % self.name)
        return 0

    def start(self):
        (pid, status) = self.status()
        if status == PID_RUNNING:
            self.info('%s (pid %s) is already running' % (self.name, pid))
            return
        # Make sure there is a clean slate
        self._remove_pid()
        cwd = os.getcwd()
        exited = 1
        try:
            os.chdir(os.path.dirname(__file__))
            pid = self.process.start()
            exited, code = self.process.wait(0.5)
            while not exited and not os.path.exists(self.pidFile):
                exited, code = self.process.wait(0.5)
        finally:
            os.chdir(cwd)

        if not exited:
            self.info('%s started (pid %s)' % (self.name, pid))
        else:
            self.info('Error starting %s' % self.name)
        return pid

    def restart(self):
        (pid, status) = self.status()
        if status == PID_RUNNING:
            self.info('Restarting %s...' % self.name)
            self._stop(pid)
        elif status == PID_DEAD:
            self._remove_pid()
        if WaitPid(self.pidFile):
            self.error('Unable to stop %s.  If you know it\'s not already running, stop and then start again' % self.name)
            pid = 0
        else:
            pid = self.start()
        return pid

    def reload(self):
        (pid, status) = self.status()
        if status == PID_RUNNING:
            self.info('Reloading %s configuration' % self.name)
            self._reload(pid)
        elif status == PID_DEAD:
            self._remove_pid()
        else:
            self.info('%s not running' % self.name)
        return pid

    def status(self):
        pid, status = 0, PID_STOPPED

        if os.path.exists(self.pidFile):
            try:
                fd = open(self.pidFile)
            except Exception, error:
                sys.stderr.write("Unable to open pid file '%s': %s\n" %
                                 (self.pidFile, str(error)))
                return (pid, status)

            try:
                content = fd.read()
            except Exception, error:
                sys.stderr.write("Unable to read pid file '%s': %s\n" %
                                 (self.pidFile, str(error)))
                try:
                    fd.close()
                except:
                    sys.stderr.write("Error closing file descriptor\n")
                return (pid, status)

            try:
                fd.close()
            except:
                sys.stderr.write("Error closing file descriptor\n")

            try:
                pid = int(content.strip())
            except:
                sys.stderr.write("Invalid pid file '%s'\n" % self.pidFile)
            else:
                status = self._status(pid)
        return (pid, status)

    def _stop(self, pid):
        return

    def _reload(self, pid):
        return

    def _status(self, pid):
        return PID_RUNNING


class PosixLauncher(LauncherBase):

    def _stop(self, pid):
        try:
            os.kill(pid, signal.SIGTERM)
        except:
            pass
        return

    def _reload(self, pid):
        try:
            os.kill(pid, signal.SIGHUP)
        except:
            pass
        return

    def _status(self, pid):
        try:
            os.kill(pid, 0)
        except:
            return PID_DEAD
        return PID_RUNNING


class WindowsLauncher(LauncherBase):

    def _stop(self, pid):
        event = Ipc.Event('ap%dshutdown' % pid)
        event.set()
        return

    def _reload(self, pid):
        event = Ipc.Event('ap%drestart' % pid)
        event.set()
        return

    def _status(self, pid):
        try:
            Ipc.OpenEvent('ap%dshutdown' % pid)
        except:
            return PID_DEAD
        return PID_RUNNING


if os.name == 'nt':
    Launcher = WindowsLauncher

elif os.name == 'posix':
    Launcher = PosixLauncher

else:
    raise SystemExit("I don't know how to start servers this platform!")
