#!/usr/share/python/titus-isolate/bin/python
import logging
from threading import Thread

import click
import docker

from titus_isolate import log
from titus_isolate.api.status import app
from titus_isolate.cgroup.file_cgroup_manager import FileCgroupManager
from titus_isolate.config.constants import RESTART_PROPERTIES
from titus_isolate.config.restart_property_watcher import RestartPropertyWatcher
from titus_isolate.constants import FAILURE_EXIT_CODE
from titus_isolate.event.create_event_handler import CreateEventHandler
from titus_isolate.event.event_manager import EventManager
from titus_isolate.event.free_event_handler import FreeEventHandler
from titus_isolate.event.rebalance_event_handler import RebalanceEventHandler
from titus_isolate.event.reconcile_event_handler import ReconcileEventHandler
from titus_isolate.event.utils import get_current_workloads
from titus_isolate.isolate.reconciler import Reconciler
from titus_isolate.isolate.utils import get_allocator
from titus_isolate.isolate.workload_manager import WorkloadManager
from titus_isolate.metrics.metrics_manager import MetricsManager
from titus_isolate.model.processor.config import get_cpu_from_env
from titus_isolate.monitor.workload_monitor_manager import WorkloadMonitorManager
from titus_isolate.predict.cpu_usage_predictor_manager import CpuUsagePredictorManager
from titus_isolate.real_exit_handler import RealExitHandler
from titus_isolate.utils import start_periodic_scheduling, get_config_manager, set_workload_manager, \
    set_workload_monitor_manager, set_event_manager, set_cpu_usage_predictor_manager


@click.command()
@click.option('--admin-port', default=5000, help="The port for the HTTP server to listen on (default: 5000)")
def main(admin_port):
    # Set the schedule library's logging level higher so it doesn't spam messages every time it schedules a task
    logging.getLogger('schedule').setLevel(logging.WARN)

    exit_handler = RealExitHandler()

    log.info("Watching property changes for restart...")
    RestartPropertyWatcher(get_config_manager(), exit_handler, RESTART_PROPERTIES)

    log.info("Modeling the CPU...")
    cpu = get_cpu_from_env()

    # Start period scheduling
    log.info("Starting periodic event scheduling...")
    start_periodic_scheduling()

    # Start the cpu usage predictor manager
    log.info("Setting up the cpu usage predictor manager...")
    cpu_predictor_manager = CpuUsagePredictorManager()
    set_cpu_usage_predictor_manager(cpu_predictor_manager)

    # Start performance monitoring
    log.info("Starting performance monitoring...")
    workload_monitor_manager = WorkloadMonitorManager()
    set_workload_monitor_manager(workload_monitor_manager)

    # Setup the workload manager
    log.info("Setting up the workload manager...")
    cgroup_manager = FileCgroupManager()
    cpu_allocator = get_allocator(get_config_manager())
    log.info("Created Fallback CPU allocator with primary: '{}' and secondary: '{}".format(
        cpu_allocator.get_primary_allocator().__class__.__name__,
        cpu_allocator.get_secondary_allocator().__class__.__name__))
    workload_manager = WorkloadManager(
        cpu=cpu,
        cgroup_manager=cgroup_manager,
        cpu_allocator=cpu_allocator)
    set_workload_manager(workload_manager)

    # Setup the event handlers
    log.info("Setting up the Docker event handlers...")
    create_event_handler = CreateEventHandler(workload_manager)
    free_event_handler = FreeEventHandler(workload_manager)
    rebalance_event_handler = RebalanceEventHandler(workload_manager)
    reconcile_event_handler = ReconcileEventHandler(Reconciler(cgroup_manager, RealExitHandler()))
    event_handlers = [create_event_handler, free_event_handler, rebalance_event_handler, reconcile_event_handler]

    # Start event processing
    log.info("Starting Docker event handling...")
    event_manager = EventManager(docker.from_env().events(), event_handlers)
    set_event_manager(event_manager)

    # Report metrics
    log.info("Starting metrics reporting...")
    MetricsManager([workload_manager, event_manager, workload_monitor_manager])

    # Initialize currently running containers as workloads
    log.info("Isolating currently running workloads...")
    for workload in get_current_workloads(docker.from_env()):
        try:
            workload_manager.add_workload(workload)
        except:
            log.exception("Failed to add currently running workload: '{}', maybe it exited.".format(workload.get_id()))

    # Start processing events after adding running workloads to avoid processing a die event before we add a workload
    event_manager.start_processing_events()

    # Starting the HTTP server blocks exit forever
    log.info("Starting HTTP server")
    __start_http_server(admin_port)

    log.info("Startup complete, waiting for events...")
    event_manager.join()

    log.error("The workload manager should never exit, yet here we are...")
    exit_handler.exit(FAILURE_EXIT_CODE)


def __start_http_server(admin_port):
    # Starting the HTTP server blocks exit forever
    def __run_http_server():
        app.run(host="0.0.0.0", debug=False, port=admin_port)

    http_server_thread = Thread(target=__run_http_server)
    http_server_thread.daemon = True
    http_server_thread.start()


if __name__ == "__main__":
    main()
