#!/usr/bin/env python
# -*- coding: utf-8 -*-

# filename   : runserver
# created at : 2013-04-04 21:27:12
# author     : Jianing Yang <jianingy.yang AT gmail DOT com>

__author__ = 'Jianing Yang <jianingy.yang AT gmail DOT com>'

try:
    from twisted.internet import epollreactor as current_reactor
    reactor = 'epoll'
except:
    try:
        from twisted.internet import kqreactor as current_reactor
        reactor = 'kqueue'
    except:
        from twisted.internet import selectreactor as current_reactor
        reactor = 'select'

current_reactor.install()

from os import environ
from sys import argv as ARGV, executable, exit
from socket import AF_INET
from copy import deepcopy

from twisted.internet import reactor, defer
from twisted.internet import protocol
from twisted.python import usage

from almar import _init as almar_init
from almar.debug import out

CHILD_PROCESSES = list()

def signal_handler(signum, frame):
    from signal import SIGINT
    print "Receivied exit signal. Quiting now."
    map(lambda x: x.signalProcess(SIGINT), CHILD_PROCESSES)
    if reactor.running:
        reactor.stop()

class ProcessProtocol(protocol.ProcessProtocol):

    def __init__(self, environ_, args):
        self.environ = deepcopy(environ_)
        self.worker_id = self.environ['ALMAR_WORKER_ID']
        self.arguments = args

    def connectionMade(self):
        from almar.global_config import GlobalConfig, MODE_PROXY
        g = GlobalConfig()
        mode = ('worker', 'proxy')[g.server_mode == MODE_PROXY]
        out("worker #%s started in mode %s" % (self.worker_id, mode))

    def processEnded(self, status):
        print "worker %s exited. restarting is in progress" % (self.worker_id)
        socket_fd = int(self.environ['ALMAR_SOCKET_FD'])
        fds = {0: 0, 1: 1, 2: 2, socket_fd: socket_fd}

        environ['ALMAR_SOCKET_FD'] = str(socket_fd)
        environ['ALMAR_WORKER_ID'] = str(self.worker_id)

        reactor.spawnProcess(ProcessProtocol(environ, self.arguments),
                             executable,
                             self.arguments,
                             childFDs=fds,
                             env=environ)


class Options(usage.Options):
    optParameters = [
        ['config', 'c', 'etc/production.yaml', 'path to configuration.'],
        ['bind', 'b', '0.0.0.0', 'listening address'],
        ['port', 'p', None, 'listening port'],
        ['process', 'n', '4', '# of processes'],
    ]

    optFlags = [
        ["proxy", "x", "Start in proxy mode"],
    ]


def main(options):

    # get site and default port
    port_no, site = almar_init(options['config'],
                               mode=('normal', 'proxy')[options['proxy']])

    # override port use user specified option
    if options['port']:
        port_no = int(options['port'])

    if 'ALMAR_SOCKET_FD' not in environ or environ['ALMAR_SOCKET_FD'] is None:
        # Create a new listening port and several other processes to help out.
        out('using reactor: %s' % reactor)
        from procname import setprocname
        setprocname('almar %s master\x00' % ('searcher', 'proxy')[options['proxy']])

        port = reactor.listenTCP(port_no, site, interface=options['bind'])
        for i in range(int(options['process'])):
            environ['ALMAR_SOCKET_FD'] = str(port.fileno())
            environ['ALMAR_WORKER_ID'] = str(i)
            new_args = [executable]
            new_args.extend(ARGV)
            fds = {0: 0, 1: 1, 2: 2, port.fileno(): port.fileno()}
            worker = reactor.spawnProcess(ProcessProtocol(environ, new_args),
                                          executable,
                                          new_args,
                                          childFDs=fds,
                                          env=environ)
            CHILD_PROCESSES.append(worker)
        # master won't accept clients
        reactor.removeReader(port)
    else:
        worker_id = int(environ['ALMAR_WORKER_ID'])
        # Another process created the port, just start listening on it.
        from procname import setprocname
        setprocname('almar %s #%s\x00' % (('searcher', 'proxy')[options['proxy']],
                                          environ['ALMAR_WORKER_ID']))

        def _start_service(conn):
            fd = int(environ['ALMAR_SOCKET_FD'])
            out('adopting parent fd')
            port = reactor.adoptStreamPort(fd, AF_INET, site)
            site.resource.port_instance = port

        if not options['proxy']:
            # first, start database connection
            from almar.backend.postgresql import PostgreSQLBackend as Backend
            b = Backend()
            out('connecting to backend database')
            d = b.start(initdb=(False, True)[worker_id == 0])
        else:
            d = defer.succeed(True)
        # start service after database ready
        d.addCallback(_start_service)

    reactor.run()
    exit(0)


if __name__ == '__main__':
    import sys
    from twisted.python import log
    log.startLogging(sys.stdout)

    # register signal handler
    from signal import signal, SIGTERM, SIGINT
    signal(SIGTERM, signal_handler)
    signal(SIGINT, signal_handler)

    options = Options()
    options.parseOptions()
    main(options)
