# Main script
import importlib
import logging
import os
import signal
import sys

from diskcache import Cache

from core.cleanup import CleanupTimer
from data.solar_conditions import SolarConditions
from core.config import config, WEB_SERVER_PORT, SERVER_OWNER_CALLSIGN
from core.constants import SOFTWARE_NAME, SOFTWARE_VERSION
from core.lookup_helper import lookup_helper
from core.status_reporter import StatusReporter
from server.webserver import WebServer

# Globals
spots = Cache('cache/spots_cache')
alerts = Cache('cache/alerts_cache')
solar_conditions = SolarConditions()
web_server = None
status_data = {}
spot_providers = []
alert_providers = []
solar_condition_providers = []
cleanup_timer = None
run = True


def shutdown(sig, frame):
    """Shutdown function"""

    global run

    logging.info("Stopping program...")
    web_server.stop()
    for sp in spot_providers:
        if sp.enabled:
            sp.stop()
    for ap in alert_providers:
        if ap.enabled:
            ap.stop()
    for scp in solar_condition_providers:
        if scp.enabled:
            scp.stop()
    cleanup_timer.stop()
    lookup_helper.stop()
    spots.close()
    alerts.close()
    os._exit(0)


def get_spot_provider_from_config(config_providers_entry):
    """Utility method to get a spot provider based on the class specified in its config entry."""

    module = importlib.import_module('spotproviders.' + config_providers_entry["class"].lower())
    provider_class = getattr(module, config_providers_entry["class"])
    return provider_class(config_providers_entry)


def get_alert_provider_from_config(config_providers_entry):
    """Utility method to get an alert provider based on the class specified in its config entry."""

    module = importlib.import_module('alertproviders.' + config_providers_entry["class"].lower())
    provider_class = getattr(module, config_providers_entry["class"])
    return provider_class(config_providers_entry)


def get_solar_conditions_provider_from_config(config_providers_entry):
    """Utility method to get a solar conditions provider based on the class specified in its config entry."""

    module = importlib.import_module('solarconditionsproviders.' + config_providers_entry["class"].lower())
    provider_class = getattr(module, config_providers_entry["class"])
    return provider_class(config_providers_entry)


# Main function
if __name__ == '__main__':
    # Set up logging
    root = logging.getLogger()
    root.setLevel(logging.INFO)
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter("%(message)s")
    handler.setFormatter(formatter)
    root.addHandler(handler)

    logging.info("Starting...")
    logging.info(
        "This is " + SOFTWARE_NAME + " version " + SOFTWARE_VERSION + ". This instance is run by " + SERVER_OWNER_CALLSIGN + ".")

    # Shut down gracefully on SIGINT
    signal.signal(signal.SIGINT, shutdown)

    # Set up lookup helper
    lookup_helper.start()

    # Set up web server
    web_server = WebServer(spots=spots, alerts=alerts, solar_conditions=solar_conditions, status_data=status_data, port=WEB_SERVER_PORT)

    # Fetch, set up and start spot providers
    for entry in config["spot-providers"]:
        spot_providers.append(get_spot_provider_from_config(entry))
    for p in spot_providers:
        p.setup(spots=spots, web_server=web_server)
        if p.enabled:
            p.start()

    # Fetch, set up and start alert providers
    for entry in config["alert-providers"]:
        alert_providers.append(get_alert_provider_from_config(entry))
    for p in alert_providers:
        p.setup(alerts=alerts, web_server=web_server)
        if p.enabled:
            p.start()

    # Fetch, set up and start solar conditions providers
    for entry in config.get("solar-condition-providers", []):
        solar_condition_providers.append(get_solar_conditions_provider_from_config(entry))
    for p in solar_condition_providers:
        p.setup(solar_conditions=solar_conditions)
        if p.enabled:
            p.start()

    # Set up timer to clear spot list of old data
    cleanup_timer = CleanupTimer(spots=spots, alerts=alerts, web_server=web_server, cleanup_interval=60)
    cleanup_timer.start()

    # Set up status reporter
    status_reporter = StatusReporter(status_data=status_data, spots=spots, alerts=alerts, web_server=web_server,
                                     cleanup_timer=cleanup_timer, spot_providers=spot_providers,
                                     alert_providers=alert_providers,
                                     solar_condition_providers=solar_condition_providers, run_interval=5)
    status_reporter.start()

    logging.info("Startup complete.")

    # Run the web server. This is the blocking call that keeps the application running in the main thread, so this must
    # be the last thing we do. web_server.stop() triggers an await condition in the web server which finishes the main
    # thread.
    web_server.start()
