#!/usr/bin/env python

#
# Base web service class.
#

####################################################################
import sys
import cherrypy
from cherrypy.process import plugins
from cherrypy import server

from dm.common.constants import dmStatus
from dm.common.utility import configurationManager
from dm.common.utility import loggingManager
from dm.common.utility import dmModuleManager

####################################################################

class DmRestWebService:

    DEFAULT_N_SERVER_REQUEST_THREADS = 10
    DEFAULT_SERVER_SOCKET_TIMEOUT = 30

    class SignalHandler:
        def __init__(self, signal, oldSignalHandler):
            self._signal = signal
            self._oldSignalHandler = oldSignalHandler
            self._logger = loggingManager.getInstance().getLogger(self.__class__.__name__)

        def signalHandler(self):
            self._logger.debug('%s signal handler called' % self._signal)
            dmModuleManager.getInstance().stopModules()
            self._oldSignalHandler()

    def __init__(self, routeMapper):
        self._configurationManager = configurationManager.getInstance()
        self._routeMapper = routeMapper
        self._options = None
        self._args = None
        self._logger = loggingManager.getInstance().getLogger(self.__class__.__name__)
        self._logger.info('Initializing service')

    def prepareOptions(self):
        from optparse import OptionParser
        p = OptionParser()
        p.add_option('-d', '--daemon', action="store_true",
            dest='daemonFlag', default=False, 
            help="Run as a daemon.")
        p.add_option('-p', '--pidfile',
            dest='pidfile', default=None,
            help="Store the process id in the given file.")
        p.add_option('-P', '--port',
            dest='serverPort', default=self.getDefaultServerPort(),
            help="Server port.")
        p.add_option('-H', '--host',
            dest='serverHost', default=self.getDefaultServerHost(),
            help="Server host.")
        p.add_option('-C', '--ssl-ca-cert', 
            dest='sslCaCert', default=None,
            help='SSL CA certificate path (used for client SSL certificate verification). Requires --ssl-key and --ssl-cert.')
        p.add_option('-c', '--ssl-cert', 
            dest='sslCert', default=None,
            help='SSL certificate path. SSL operation requires both --ssl-key and --ssl-cert. Client SSL certificate verification also requires --ssl-ca-cert.')
        p.add_option('-k', '--ssl-key', 
            dest='sslKey', default=None, 
            help='SSL key path. SSL operation requires both --ssl-key and --ssl-cert. Client SSL certificate verification also requires --ssl-ca-cert.')
        p.add_option('', '--n-server-threads', 
        dest='nServerThreads', default=DmRestWebService.DEFAULT_N_SERVER_REQUEST_THREADS,
            help='Number of service request handler threads (defaut: %s).' % DmRestWebService.DEFAULT_N_SERVER_REQUEST_THREADS)
        return p

    def initDmModules(self):
        pass 

    def getDefaultServerHost(self):
        return None

    def getDefaultServerPort(self):
        return None

    # Instantiate modified signal handler that stops dm modules first, 
    # and then does the default action.
    def modifySignalHandlers(self, engine):
        pluginsSignalHandler = plugins.SignalHandler(engine)
        handlers = pluginsSignalHandler.handlers

        # Add handler for interrupt
        handlers['SIGINT'] = engine.exit

        # Modify all signal handlers
        for signal in handlers.keys():
            self._logger.debug('Modifying signal: %s' % signal)
            oldSignalHandler = handlers[signal]
            self._logger.debug('Old signal handler: %s' % oldSignalHandler)
            signalHandler = DmRestWebService.SignalHandler(signal, oldSignalHandler)
            self._logger.debug('Setting signal handler to: %s' % signalHandler.signalHandler)
            handlers[signal] = signalHandler.signalHandler
            pluginsSignalHandler.subscribe()

    def initServerLog(self):
        cherrypyLogLevel = self._configurationManager.getCherrypyLogLevel()
        cherrypy.log.error_log.setLevel(cherrypyLogLevel)
        cherrypy.log.error_file = self._configurationManager.getCherrypyLogFile()
        cherrypy.log.error_log.propagate = False
        cherrypy.log.access_log.setLevel(cherrypyLogLevel)
        cherrypy.log.access_file = self._configurationManager.getCherrypyAccessFile()
        cherrypy.log.access_log.propagate = False

    def updateServerConfig(self):
        configDict = {
            'server.socket_host' : self._options.serverHost,
            'server.socket_port' : int(self._options.serverPort),
            'server.thread_pool' : int(self._options.nServerThreads),
            'log.screen' : (self._options.daemonFlag != True),
        }
        cherrypy.config.update(configDict)

    def prepareServer(self):
        self._logger.debug('Preparing service configuration')
        try:
            optionParser = self.prepareOptions()
            (self._options, self._args) = optionParser.parse_args()
            dispatch = self._routeMapper.setupRoutes()
            self._logger.debug('Using route dispatch: %s' % dispatch)

            config = {
                '/' : {
                    'request.dispatch' : dispatch,
                },
            }
                   
            # No root controller as we provided our own.
            cherrypy.tree.mount(root=None, config=config)
            self.initServerLog()
            self.updateServerConfig()

            self._logger.info('Using host %s' % self._options.serverHost)
            self._logger.info('Using port %s' % self._options.serverPort)
            self._logger.debug('Using %s request handler threads' % self._options.nServerThreads)
        except Exception, ex:
            self._logger.exception(ex)
            sys.exit(dmStatus.DM_ERROR)

    # Run server.
    def __runServer(self):
        self._logger.info('Starting service')
        engine = cherrypy.engine

        # Set up Deamonization
        if self._options.daemonFlag:
            plugins.Daemonizer(engine).subscribe()
        self._logger.debug('Daemon mode: %s' % self._options.daemonFlag)

        if self._options.pidfile != None:
            plugins.PIDFile(engine, self._options.pidfile).subscribe()
        self._logger.debug('Using PID file: %s' % self._options.pidfile)

        if self._options.sslCert != None and self._options.sslKey != None:
            server.ssl_ca_certificate = None
            if self._options.sslCaCert != None:
                server.ssl_ca_certificate = self._options.sslCaCert
                self._configurationManager.setSslCaCertFile(self._options.sslCaCert)
                self._logger.info('Using SSL CA cert file: %s' % self._options.sslCaCert)
                server.ssl_certificate = self._options.sslCert
                self._configurationManager.setSslCertFile(self._options.sslCert)
                self._logger.info('Using SSL cert file: %s' % self._options.sslCert)

                server.ssl_private_key = self._options.sslKey
                self._configurationManager.setSslKeyFile(self._options.sslKey)
                self._logger.info('Using SSL key file: %s' % self._options.sslKey)

        server.ssl_module = 'builtin'
        #server.ssl_module = 'pyopenssl'

        # Increase timeout to prevent early SSL connection terminations
        server.socket_timeout = DmRestWebService.DEFAULT_SERVER_SOCKET_TIMEOUT
        
        # Setup the signal handler to stop the application while running.
        if hasattr(engine, 'signal_handler'):
            engine.signal_handler.subscribe()
        if hasattr(engine, 'console_control_handler'):
            engine.console_control_handler.subscribe()
            self.modifySignalHandlers(engine)

        # Turn off autoreloader.
        self._logger.debug('Turning off autoreloader')
        engine.autoreload.unsubscribe()

        # Start the engine.
        try:
            self._logger.debug('Starting engine')
            engine.start()

            # Prepare dm services.
            # Doing this before engine starts may cause issues with existing timers.
            self._logger.debug('Starting modules')
            self.initDmModules()
            dmModuleManager.getInstance().startModules()
        except Exception, ex:
            self._logger.exception('Service exiting: %s' % ex)
            dmModuleManager.getInstance().stopModules()
            return dmStatus.DM_ERROR
        self._logger.info('Service ready')
        engine.block()
        dmModuleManager.getInstance().stopModules()
        self._logger.info('Service done')
        return dmStatus.DM_OK

    # Run server instance.
    def run(self):
        self.prepareOptions()
        self.prepareServer()
        sys.exit(self.__runServer())

####################################################################
# Testing

if __name__ == '__main__':
    pass