#! /usr/bin/env python
#
# Copyright (c) 2014 VMware, Inc. All rights reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
#

from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import socket
import sys

from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service

from congress.common import config
from congress.db import api as db_api
# FIXME It has to initialize distributed_architecture flag basing on the
# config file before the python interpreter imports python file which has
# if-statement for deepsix. Since the default value of the flag is False
# in current implementation, so it will import dse.deepsix as deepsix
# even if you set it to True in congress.conf.
# After changing the default to True, remove following one line.
# This appears in main() too.  Removing either instance breaks something.
config.init(sys.argv[1:])
from congress.common import eventlet_server
from congress import encryption
from congress import harness

LOG = logging.getLogger(__name__)


class ServerWrapper(object):
    """Wraps an eventlet_server with some launching info & capabilities."""

    def __init__(self, server, workers):
        self.server = server
        self.workers = workers

    def launch_with(self, launcher):
        if hasattr(self.server, 'listen'):
            self.server.listen()
        if self.workers > 1:
            # Use multi-process launcher
            launcher.launch_service(self.server, self.workers)
        else:
            # Use single process launcher
            launcher.launch_service(self.server)


def serve(*servers):
    if max([server[1].workers for server in servers]) > 1:
        # TODO(arosen) - need to provide way to communicate with DSE services
        launcher = service.ProcessLauncher(cfg.CONF, restart_method='mutate')
    else:
        launcher = service.ServiceLauncher(cfg.CONF, restart_method='mutate')

    for name, server in servers:
        try:
            server.launch_with(launcher)
        except socket.error:
            LOG.exception(_('Failed to start the %s server'), name)
            raise

    try:
        launcher.wait()
    except KeyboardInterrupt:
        LOG.info("Congress server stopped by interrupt.")


def create_api_server(conf_path, node_id, host, port, workers, policy_engine,
                      datasources):
    congress_api_server = eventlet_server.APIServer(
        conf_path, node_id, host=host, port=port,
        keepalive=cfg.CONF.tcp_keepalive,
        keepidle=cfg.CONF.tcp_keepidle,
        policy_engine=policy_engine,
        api=True,
        datasources=datasources,
        bus_id=cfg.CONF.dse.bus_id)
    # TODO(thinrichs): there's some sort of magic happening for the api
    #   server.  We call eventlet_server, which on start() calls
    #   service.congress_app_factory, which uses harness to create the
    #   API service, which the magic seems to need to do the right thing.
    #   That's why we're not just calling harness.create2 here; instead,
    #   it's buried inside the congress_app_factory.
    return node_id, ServerWrapper(congress_api_server, workers)


def create_nonapi_server(node_id, policy_engine, datasources, workers):
    congress_server = eventlet_server.Server(
        node_id, bus_id=cfg.CONF.dse.bus_id)
    harness.create2(existing_node=congress_server.node, api=False,
                    policy_engine=policy_engine,
                    datasources=datasources)
    return node_id, ServerWrapper(congress_server, workers)


def launch_servers(node_id, api, policy, data):
    servers = []
    if api:
        LOG.info("Starting congress API server on port %d", cfg.CONF.bind_port)
        # API resource runtime encapsulation:
        # event loop -> wsgi server -> webapp -> resource manager
        paste_config = config.find_paste_config()
        config.set_config_defaults()
        servers.append(create_api_server(paste_config,
                                         node_id,
                                         cfg.CONF.bind_host,
                                         cfg.CONF.bind_port,
                                         cfg.CONF.api_workers,
                                         policy_engine=policy,
                                         datasources=data))
    else:
        LOG.info("Starting congress server on node %s", node_id)
        servers.append(create_nonapi_server(node_id, policy, data,
                                            cfg.CONF.api_workers))

    return servers


def main():
    args = sys.argv[1:]

    # TODO(thinrichs): find the right way to do deployment configuration.
    # For some reason we need to config.init(args) in 2 places; here and
    # at the top of the file.  Remove either one, and things break.
    # Note: config.init() will delete the deploy args, so grab them before.
    config.init(args)
    if not cfg.CONF.config_file:
        sys.exit("ERROR: Unable to find configuration file via default "
                 "search paths ~/.congress/, ~/, /etc/congress/, /etc/) and "
                 "the '--config-file' option!")
    if cfg.CONF.replicated_policy_engine and not (
            db_api.is_mysql() or db_api.is_postgres()):
        if db_api.is_sqlite():
            LOG.warning("Deploying replicated policy engine with SQLite "
                        "backend is not officially supported. Unexpected "
                        "behavior may occur. Officially supported backends "
                        "are MySQL and PostgresSQL.")
        else:
            sys.exit("ERROR: replicated_policy_engine option can be used only "
                     "with MySQL or PostgreSQL database backends. Please set "
                     "the connection option in [database] section of "
                     "congress.conf to use a supported backend.")
    config.setup_logging()

    if not (cfg.CONF.api or cfg.CONF.policy_engine or cfg.CONF.datasources):
        # No flags provided, start all services
        cfg.CONF.api = True
        cfg.CONF.policy_engine = True
        cfg.CONF.datasources = True

    # initialize encryption key if datasource services enabled in this instance
    if cfg.CONF.datasources:
        encryption.initialize_key()
        LOG.debug("Initialized encryption key on datasource node")

    # Construct requested deployment
    servers = launch_servers(cfg.CONF.node_id, cfg.CONF.api,
                             cfg.CONF.policy_engine, cfg.CONF.datasources)

    serve(*servers)


if __name__ == '__main__':
    main()
