#!/usr/bin/env python

# Copyright (C) 2012-2013 Science and Technology Facilities Council.
# Copyright (C) 2016-2021 East Asian Observatory.
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

import atexit
import cherrypy
from cherrypy.process.plugins import Daemonizer
import logging
from optparse import OptionParser
import os
import sys

from crab.notify import CrabNotify
from crab.service.clean import CrabCleanService
from crab.service.monitor import CrabMonitor
from crab.service.notify import CrabNotifyService
# crab.web.rss imports the optional PyRSS2Gen requirement
try:
    from crab.web.rss import CrabRSS
except ImportError:
    CrabRSS = None
from crab.server import CrabServer
from crab.server.config import read_crabd_config, \
    construct_log_handler, construct_store
from crab.util.bus import CrabPlugin, priority
from crab.util.filter import CrabEventFilter
from crab.util.pid import pidfile_write, pidfile_running, pidfile_delete
from crab.web.web import CrabWeb


class CrabFacilities:
    def __init__(self, bus, config, pidfile=None):
        self.bus = bus
        self.config = config
        self.pidfile = pidfile

    def subscribe(self):
        self.bus.subscribe('start', self.start)

    @priority(70)
    def start(self):
        if self.pidfile is not None:
            self.bus.log('Writing Crab PID file: {}'.format(self.pidfile))
            pidfile_write(self.pidfile, os.getpid())
            atexit.register(pidfile_delete, self.pidfile)

        self.bus.log('Starting Crab facilities')

        store = self.get_store()
        self.bus.publish('crab-store', store)

        # Pass whole configuration to CrabNotify to allow it to
        # construct notification method objects.
        notifier = self.get_notifier(store)
        self.bus.publish('crab-notify', notifier)

    def get_store(self):
        if 'outputstore' in self.config:
            outputstore = construct_store(self.config['outputstore'])
        else:
            outputstore = None

        return construct_store(self.config['store'], outputstore)

    def get_notifier(self, store):
        return CrabNotify(self.config, store)


def main():
    # Handle command line arguments.
    parser = OptionParser()
    parser.add_option(
        '--pidfile',
        type='string', dest='pidfile',
        help='use PIDFILE to avoid re-running crabd', metavar='PIDFILE')
    parser.add_option(
        '--accesslog',
        type='string', dest='accesslog',
        help='Log file for HTTP requests', metavar='FILE')
    parser.add_option(
        '--errorlog',
        type='string', dest='errorlog',
        help='Log file for general messages', metavar='FILE')
    parser.add_option(
        '--import',
        type='string', dest='import_',
        help='import jobs and settings from file', metavar='JSONFILE')
    parser.add_option(
        '--export',
        type='string', dest='export',
        help='export jobs and settings to file', metavar='JSONFILE')
    parser.add_option(
        '--daemon',
        action='store_true', dest='daemon',
        help='Run in daemon mode')
    parser.add_option(
        '--passive',
        action='store_true', dest='passive',
        help='Run passive server (passive monitor, no notifications)')

    (options, args) = parser.parse_args()
    if len(args) != 0:
        parser.error('no arguments required')

    # Read configuration file.
    config = read_crabd_config()

    # Check for a pidfile if requested.
    pidfile = None
    if options.pidfile:
        pidfile = options.pidfile
    if pidfile is not None:
        if pidfile_running(pidfile):
            return

    facilities = CrabFacilities(
        cherrypy.engine, config=config, pidfile=pidfile)

    # Perform import/export operations if requested.
    if options.import_ or options.export:
        from crab.server.io import import_config, export_config
        store = facilities.get_store()

        if options.import_ and options.export:
            parser.error('import and export operations both requested')

        if options.import_:
            if options.import_ == '-':
                import_config(store=store, file_=sys.stdin)
            else:
                with open(options.import_, 'r') as file_:
                    import_config(store=store, file_=file_)

        elif options.export:
            if options.export == '-':
                export_config(store=store, file_=sys.stdout)
            else:
                with open(options.export, 'w') as file_:
                    export_config(store=store, file_=file_)
        return

    # Set up logging based on which log files are requested.
    cherrypy.log.screen = False

    if options.accesslog:
        handler = construct_log_handler(
            options.accesslog, config['access_log'])
    else:
        # Replicate previous CherryPy "screen" behavior.
        handler = logging.StreamHandler(sys.stdout)

    cherrypy.log.access_log.propagate = False
    cherrypy.log.access_log.addHandler(handler)

    if options.errorlog:
        handler = construct_log_handler(
            options.errorlog, config['error_log'])
    else:
        # Replicate previous CherryPy "screen" behavior.
        handler = logging.StreamHandler(sys.stderr)

    # Set handler on root logger and let CherryPy error_log to propagate to it.
    # Note: this duplicates the time in log messages from CherryPy but without
    # it non-CherryPy log messages would not show the time.
    handler.setFormatter(logging.Formatter(
        '%(asctime)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%S'))

    root_logger = logging.getLogger()
    root_logger.addHandler(handler)
    root_logger.setLevel(logging.DEBUG)

    # Set up CherryPy Daemonizer if requested.
    if options.daemon:
        Daemonizer(cherrypy.engine).subscribe()

    facilities.subscribe()

    # Set a default timezone: applies to times shown in
    # notifications and on the web interface.
    CrabEventFilter.set_default_timezone(config['notify']['timezone'])

    CrabPlugin(
        cherrypy.engine, 'Monitor', CrabMonitor,
        passive=options.passive).subscribe()

    if not options.passive:
        CrabPlugin(
            cherrypy.engine, 'Notification', CrabNotifyService,
            config=config['notify'], notify=None).subscribe()

    # Construct cleaning service if requested.
    if ('clean' in config) and not options.passive:
        CrabPlugin(
            cherrypy.engine, 'Clean', CrabCleanService,
            config=config['clean']).subscribe()

    cherrypy.config.update(config)

    web = CrabWeb(
        config['crab']['home'], {
            'rss_enabled': (CrabRSS is not None),
        })
    web.subscribe()
    cherrypy.tree.mount(web, '/', config)

    server = CrabServer(cherrypy.engine)
    server.subscribe()
    cherrypy.tree.mount(server, '/api/0', {})

    if CrabRSS is not None:
        rss = CrabRSS(cherrypy.engine, config['crab']['base_url'])
        rss.subscribe()
        cherrypy.tree.mount(rss, '/rss', {})

    cherrypy.engine.start()
    cherrypy.engine.block()

if __name__ == "__main__":
    main()
