#!/usr/bin/env python3

import sys
import argparse
import asyncio
import yaml
import logging
import signal
from functools import partial

import postfix_mta_sts_resolver.utils as utils
import postfix_mta_sts_resolver.defaults as defaults
from postfix_mta_sts_resolver.responder import STSSocketmapResponder


def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("-v", "--verbosity",
                        help="logging verbosity",
                        type=utils.LogLevel.__getitem__,
                        choices=list(utils.LogLevel),
                        default=utils.LogLevel.info)
    parser.add_argument("-c", "--config",
                        help="config file location",
                        metavar="FILE",
                        default=defaults.CONFIG_LOCATION)
    parser.add_argument("-l", "--logfile",
                        help="log file location",
                        metavar="FILE")
    parser.add_argument("--disable-uvloop",
                        help="do not use uvloop even if it is available",
                        action="store_true")

    return parser.parse_args()


def populate_cfg_defaults(cfg):
    if not cfg:
        cfg = {}

    cfg['host'] = cfg.get('host', defaults.HOST)
    cfg['port'] = cfg.get('port', defaults.PORT)

    if 'cache' not in cfg:
        cfg['cache'] = {}

    cfg['cache']['type'] = cfg['cache'].get('type', defaults.CACHE_BACKEND)

    if cfg['cache']['type'] == 'internal':
        if 'options' not in cfg['cache']:
            cfg['cache']['options'] = {}

        cfg['cache']['options']['cache_size'] = cfg['cache']['options'].get('cache_size', defaults.INTERNAL_CACHE_SIZE)

    def populate_zone(zone):
        zone['timeout'] = zone.get('timeout', defaults.TIMEOUT)
        zone['strict_testing'] = zone.get('strict_testing', defaults.STRICT_TESTING)
        return zone

    if 'default_zone' not in cfg:
        cfg['default_zone'] = {}

    populate_zone(cfg['default_zone'])

    if 'zones' not in cfg:
        cfg['zones'] = {}

    for zone in cfg['zones'].values():
        populate_zone(zone)

    return cfg


def exit_handler(exit_event, signum, frame):
    logger = logging.getLogger('MAIN')
    if exit_event.is_set():
        logger.warning("Got second exit signal! Terminating hard.")
        os._exit(1)
    else:
        logger.warning("Got first exit signal! Terminating gracefully.")
        exit_event.set()


async def heartbeat():
    """ Hacky coroutine which keeps event loop spinning with some interval 
    even if no events are coming. This is required to handle Futures and 
    Events state change when no events are occuring."""
    while True:
        await asyncio.sleep(.5)


async def amain(cfg, loop):
    logger = logging.getLogger("MAIN")
    # Construct request handler instance
    responder = STSSocketmapResponder(cfg, loop)

    await responder.start()
    logger.info("Server started.")

    exit_event = asyncio.Event()
    beat = asyncio.ensure_future(heartbeat())
    sig_handler = partial(exit_handler, exit_event)
    signal.signal(signal.SIGTERM, sig_handler)
    signal.signal(signal.SIGINT, sig_handler)
    await exit_event.wait()
    logger.debug("Eventloop interrupted. Shutting down server...")
    beat.cancel()
    await responder.stop()


def main():
    # Parse command line arguments and setup basic logging
    args = parse_args()
    mainLogger = utils.setup_logger('MAIN', args.verbosity, args.logfile )
    utils.setup_logger('STS', args.verbosity, args.logfile)
    mainLogger.info("MTA-STS daemon starting...")

    # Read config and populate with defaults
    with open(args.config, 'rb') as cfg_file:
        cfg = yaml.safe_load(cfg_file)
    cfg = populate_cfg_defaults(cfg)

    # Construct event loop
    mainLogger.info("Starting eventloop...")
    if not args.disable_uvloop:
        if utils.enable_uvloop():
            mainLogger.info("uvloop enabled.")
        else:
            mainLogger.info("uvloop is not available. "
                            "Falling back to built-in event loop.")
    evloop = asyncio.get_event_loop()
    mainLogger.info("Eventloop started.")


    evloop.run_until_complete(amain(cfg, evloop))
    evloop.close()
    mainLogger.info("Server finished its work.")


if __name__ == '__main__':
    main()
