#!/usr/bin/python2
# Copyright (C) 2013 Johnny Vestergaard <jkv@unixcluster.dk>
#
# 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 gevent
import gevent.monkey

gevent.monkey.patch_all()

import logging
import logging.handlers
import json
from argparse import ArgumentParser
import sys
import os

from gevent import Greenlet

import beeswarm
from beeswarm.drones.drone import Drone
from beeswarm.server.server import Server
from beeswarm.shared.asciify import asciify
from beeswarm.shared.helpers import is_url, extract_config_from_api


logger = logging.getLogger()


def stop_if_root():
    if os.getuid() == 0:
        logger.error('Beeswarm should not be started in this mode as root, please restart as a normal user.')
        sys.exit(1)


def start_drone(workdir, config, config_file, local_socket):
    if config:
        drone = Drone(args.workdir, config, local_pull_socket=local_socket)
        drone_greenlet = Greenlet.spawn(drone.start)
    else:
        drone = None
        drone_greenlet = None
        logger.info('Started in drone mode without configuration - waiting for url dropper!')
    while True:
        dropped_config_url_file = os.path.join(args.workdir, 'API_CONFIG_URL')
        if os.path.isfile(dropped_config_url_file):
            with open(dropped_config_url_file, 'r') as _file:
                config_url = _file.read().splitlines()[0]
            logger.info('Found dropped api config url in {0}, with content: {1}.'.format(workdir, config_url))
            os.remove(dropped_config_url_file)
            config = extract_config_from_api(config_url, config_file)
            if config:
                if drone:
                    drone.stop()
                if drone_greenlet:
                    drone_greenlet.kill()
                drone = Drone(workdir, config, local_pull_socket=local_socket)
                drone_greenlet = Greenlet.spawn(drone.start)
            else:
                logger.warning('Error while trying to extract config from URL, waiting for new file.')
        gevent.sleep(1)


def setup_logging(logfile, verbose):
    """
        Sets up logging to the logfiles/console.
    :param logfile: Path of the file to write logs to.
    :param verbose: If True, enables verbose logging.
    """
    root_logger = logging.getLogger()

    default_formatter = logging.Formatter('%(asctime)-15s (%(name)s) %(message)s')

    if verbose:
        loglevel = logging.DEBUG
    else:
        loglevel = logging.INFO
    root_logger.setLevel(loglevel)

    console_log = logging.StreamHandler()
    console_log.addFilter(LogFilter())
    console_log.setLevel(loglevel)
    console_log.setFormatter(default_formatter)
    root_logger.addHandler(console_log)

    if logfile in ('/dev/log', '/dev/syslog', '/var/run/syslog', '/var/run/log'):
        file_log = logging.handlers.SysLogHandler(address=logfile, facility='local1')
        syslog_formatter = logging.Formatter('beeswarm[%(process)d]: %(message)s')
        file_log.setFormatter(syslog_formatter)
    else:
        file_log = logging.FileHandler(logfile)
        file_log.setFormatter(default_formatter)
    file_log.setLevel(loglevel)
    root_logger.addHandler(file_log)


class LogFilter(logging.Filter):
    def filter(self, rec):
        if rec.name == 'paramiko.transport':
            return False
        else:
            return True


if __name__ == '__main__':
    parser = ArgumentParser(description='Beeswarm')

    group = parser.add_argument_group()
    group.add_argument('-se', '--server', action='store_true', help='Starts beeswarm in server mode.')

    parser.add_argument('--config', dest='configurl', default='', help='Configuration URL to the server service.')
    parser.add_argument('--waitingdrone', action='store_true', default=False, help='Waiting drone mode - expert mode!')
    parser.add_argument('--local_socket', dest='local_socket', default=None)
    parser.add_argument('--workdir', dest='workdir', default=os.getcwd())
    parser.add_argument('--max_sessions', dest='max_sessions', type=int, default=None,
                        help='Maximum number of sessions to store.')
    parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Logs debug messages.')
    parser.add_argument('--customize', action='store_true', default=False,
                        help='Asks for specific network and certificate information on the first run.')
    parser.add_argument('--clearsessions', action='store_true', default=False,
                        help='Deletes all sessions on startup.')
    parser.add_argument('--resetpw', action='store_true', default=False,
                        help='Deletes all sessions on startup.')
    parser.add_argument('--no_webui', action='store_true', default=False,
                        help='Do not start the web ui.')
    parser.add_argument('-l', '--logfile', dest='logfile', default='beeswarm.log', help='Beeswarm log file..')
    args = parser.parse_args()

    if not os.path.isabs(args.workdir):
        error_text = 'Work directory must be as a absolute path: {0}'.format(args.workdir)
        sys.exit(error_text)

    if not os.path.isabs(args.logfile):
        args.logfile = os.path.join(args.workdir, args.logfile)
    setup_logging(args.logfile, args.verbose)

    logger.info('Initializing BeeSwarm version {0}'.format(beeswarm.version))

    config_file = os.path.join(args.workdir, 'beeswarmcfg.json')

    if is_url(args.configurl):
        # meh, MiTM problem here... Acceptable? Workaround?
        # maybe print fingerprint on the web ui and let user verify manually?
        config_extracted = extract_config_from_api(args.configurl, config_file)
        if not config_extracted:
            logger.error('Error while extracting configuration from {0}, please make sure that the correct url was '
                         'provided.'.format(args.configurl))
            sys.exit(1)

    is_server = False
    mode = None
    config = None
    if os.path.isfile(config_file):
        with open(config_file, 'r') as _file:
            config = json.load(_file, object_hook=asciify)
            if 'general' in config:
                mode = config['general']['mode']
                if mode is not "server":
                    mode = 'drone'
    if args.server or mode == 'server':
        stop_if_root()
        server = Server(args.workdir, config, customize=args.customize, clear_db=args.clearsessions,
                        reset_password=args.resetpw, max_sessions=args.max_sessions, start_webui=not args.no_webui)
        server_greenlet = gevent.spawn(server.start)
        gevent.joinall([server_greenlet])
    elif args.waitingdrone or mode == 'drone':
        start_drone(args.workdir, config, config_file, args.local_socket)
    else:
        parser.print_help()
