#!/usr/bin/env python3

"""
D-Genies main binary

- Launch D-Genies server
- manage crons
- clear jobs and data
"""

import os
import argparse
import webbrowser
import threading
import json
from glob import glob
import time
import datetime
from dgenies.config_reader import AppConfigReader
from dgenies.bin.clean_jobs import parse_data_folders, parse_database, parse_upload_folders
import requests
from requests.exceptions import ConnectionError

runned = False

config = AppConfigReader()


def parse_args():
    """
    Parse arguments given from the user

    :return:

        * [0] action to do (run, clear, gallery, ...)
        * [1] arguments object
    :rtype: (str, argparse.Namespace)
    """
    parser = argparse.ArgumentParser(description="Manage dgenies application")
    subparsers = parser.add_subparsers(dest="subparser_name")

    # Run the app:
    run = subparsers.add_parser("run", help="Run the application")
    run.add_argument("-m", "--mode", type=str, choices=["standalone", "webserver"], default="standalone",
                            help="Mode into run the application")
    run.add_argument("-d", "--debug", help="Run in debug mode", type=bool, const=True, nargs="?", required=False,
                     default=False)
    run.add_argument("-o", "--host", help="Host into run the application", type=str, required=False,
                     default="127.0.0.1")
    run.add_argument("-p", "--port", help="Port into run the application", type=int, required=False, default=5000)
    run.add_argument("--no-crons", help="Do not run crons (for debug only, ignored for standalone mode)", type=bool,
                     const=True, nargs="?", required=False, default=False)
    run.add_argument("--no-browser", type=bool, const=True, nargs="?", required=False, default=False,
                     help="Do not start the browser (Start of browser is always disabled in debug mode)")

    # Clear data or jobs:
    clear = subparsers.add_parser("clear", help="Clear data")
    clear.add_argument("-c", "--crons", help="Clear crons", type=bool, const=True, nargs="?", required=False,
                       default=False)
    clear.add_argument("-l", "--logs", help="Clear logs", type=bool, const=True, nargs="?", required=False,
                       default=False)
    clear.add_argument("-j", "--jobs", help="Clear jobs", type=bool, const=True, nargs="?", required=False,
                       default=False)
    clear.add_argument("-a", "--analytics", help="Clear analytics", type=bool, const=True, nargs="?", required=False,
                       default=False)
    clear.add_argument("-m", "--max-age", help="Max age for job to delete (0 for all)", type=int, required=False,
                       default=0)
    clear.add_argument("-w", "--web", help="Add this option with -j option, if you use the webserver mode", type=bool,
                       const=True, nargs="?", required=False, default=False)

    # Gallery:
    gallery = subparsers.add_parser("gallery", help="Manage gallery")
    subparsers_gallery = gallery.add_subparsers()

    # Gallery add:
    gallery_add = subparsers_gallery.add_parser("add", help="Add new job to the gallery")
    gallery_add.set_defaults(which="add")
    gallery_add.add_argument("-i", "--id-job", type=str, required=True,
                             help="Id (name) of the job to add to the gallery")
    gallery_add.add_argument("-n", "--name", type=str, required=True, help="Name to show in the gallery for the job")
    gallery_add.add_argument("-q", "--query", type=str, required=True, help="Name of the query")
    gallery_add.add_argument("-t", "--target", type=str, required=True, help="Name of the target")
    gallery_add.add_argument("-p", "--pict", type=str, required=True, help="Name of the file that illustrate the job")

    # Gallery del:
    gallery_del = subparsers_gallery.add_parser("del", help="Delete a job from the gallery")
    gallery_del.set_defaults(which="del")
    gallery_del.add_argument("-i", "--id-job", type=str, required=False,
                             help="Id (name) of the job to delete from the gallery")
    gallery_del.add_argument("-n", "--name", type=str, required=False,
                             help="Name of the job shown in the gallery")
    gallery_del.add_argument("--remove-pict", help="Remove picture file", action='store_const',
                              const=True, default=False)

    # Run message
    inforun = subparsers.add_parser("inforun", help="Set a message at the top of the run page")
    inforun.set_defaults(which="inforun")
    inforun.add_argument("-m", "--message", type=str, help="Message to add")
    inforun.add_argument("-t", "--type", type=str, choices=["info", "warn", "critical", "success"],
                         help="Type of message")
    inforun.add_argument("-c", "--clear", help="Remove message", action='store_const',
                         const=True, default=False)

    args = parser.parse_args()

    if args.subparser_name == "run":
        return "run", [args.mode, args.debug, args.host, args.port, args.no_crons, args.no_browser]
    if args.subparser_name == "clear":
        if args.crons is False and args.logs is False and args.jobs is False and args.analytics is False:
            print("Nothing to do.")
            return "clear", None
        return "clear", args
    if args.subparser_name == "gallery":
        return "gallery_" + args.which, args
    if args.subparser_name == "inforun":
        return "inforun", args
    else:
        parser.print_help()
        print("\nYou must specify the command to launch")
        return None, None


def start_browser(host, port, app):
    """
    Start browser when the server is runned

    :param host: host on which the server is launched
    :type host: str
    :param port: port on which the server is launched
    :type port: int
    :param app: Flask app object
    :type app: Flask
    """
    web_url = "http://{0}:{1}".format(host, port)
    status_code = -1
    tries = 0
    while status_code != 200 and tries < 60:
        try:
            status_code = requests.get(web_url).status_code
        except ConnectionError:
            print("pass")
            status_code = 500
        if status_code != 200:
            time.sleep(1)
            tries += 1
    if app.got_first_request:
        print("Starting browser...")
        webbrowser.open(web_url)
    else:
        print("App has not started. Cancel run of browser")


def run(mode="standalone", debug=False, host="127.0.0.1", port=5000, no_crons=False, no_browser=False):
    """
    Run server

    :param mode: webserver or standalone
    :type mode: str
    :param debug: True to enable debug mode
    :type debug: bool
    :param host: host on which run rhe server
    :type host: str
    :param port: port on which run the server
    :type port: int
    :param no_crons: True to disable crons
    :type no_crons: bool
    :param no_browser: True to don't launch the browser after server starts
    :type no_browser: bool
    """
    os.environ['DISABLE_CRONS'] = "True" if no_crons else "False"
    if debug:
        os.environ['LOGS'] = "True"
    from dgenies import launch
    app = launch(mode=mode, debug=debug)
    if not debug and not no_browser:
        thread = threading.Timer(1, start_browser, kwargs={
            "host": host,
            "port": port,
            "app": app
        })
        thread.start()
    app.run(host=host, port=port, debug=debug, threaded=True)


def clear_crons():
    """
    Clear crons
    """
    from dgenies.lib.crons import Crons
    crons = Crons(None, True)
    crons.clear()


def clear_logs():
    """
    Clear logs
    """
    if hasattr(config, "log_dir"):
        log_files = glob(os.path.join(config.log_dir, "*.log"))
        for file in log_files:
            os.remove(file)

    else:
        print("No log dir defined!")


def clear_jobs(max_data_age=7, web=False):
    """
    Clear jobs
    :param max_data_age: max age for jobs before removing them
    :param web: True if webserver mode
    """
    upload_folder = config.upload_folder
    app_data = config.app_data
    now = time.time()

    max_age = {
        "uploads": 0,
        "error": 0,
        "data": max_data_age,
        "fasta_sorted": 0
    }

    print("#########################")
    print("# Parsing Upload folder #")
    print("#########################")
    print("")
    parse_upload_folders(
        upload_folder=upload_folder,
        now=now,
        max_age=max_age
    )
    print("")

    if web:
        print("######################")
        print("# Parsing Jobs in DB #")
        print("######################")
        print("")
        gallery_jobs = parse_database(
            app_data=app_data,
            max_age=max_age
        )
        print("")
    else:
        gallery_jobs = []

    print("#######################")
    print("# Parsing Data folder #")
    print("#######################")
    print("")
    parse_data_folders(
        app_data=app_data,
        now=now,
        max_age=max_age,
        gallery_jobs=gallery_jobs
    )
    print("")


def clear_analytics(max_data_age=390):
    """
    Clear analytics
    :param max_data_age: max age for jobs before removing them
    """
    from dgenies.database import Analytics, Job
    items = Analytics.select().where(Analytics.date_created <= datetime.date.today() - datetime.timedelta(days=max_data_age))
    for item in items:
        item.delete_instance()
    print("Removed number of jobs: %i" % len(items))


def add_to_gallery(id_job, name, picture, query, target):
    """
    Add a job to the gallery

    :param id_job: job id
    :type id_job: str
    :param name: name of the sample
    :type name: str
    :param picture: picture filename
    :type picture: str
    :param query: query name
    :type query: str
    :param target: target name
    :type target: str
    """
    from dgenies.database import Gallery, Job
    from peewee import DoesNotExist
    try:
        job = Job.get(id_job=id_job)
    except DoesNotExist:
        print("Error: job \"%s\" does not exists!" % id_job)
        exit(1)
    else:
        pict_file = os.path.join(config.app_data, "gallery", picture)
        if not os.path.exists(pict_file):
            print("Error: file \"%s\" does not exists!" % pict_file)
            exit(1)
        item = Gallery.create(job=job, name=name, picture=picture, query=query, target=target)
        item.save()


def del_from_gallery_by_id(id_job):
    """
    Remove a job, by id

    :param id_job: id of the job to delete
    :return: list of pictures files to delete
    :rtype: list
    """
    from dgenies.database import Gallery, Job
    items = Gallery.select().join(Job).where(Job.id_job == id_job)
    list_pictures = []
    for item in items:
        list_pictures.append(item.picture)
        item.delete_instance()
    return list_pictures


def del_from_gallery_by_name(name):
    """
    Remove a job, by name

    :param name: name of the job to delete
    :return: list of pictures files to delete
    :rtype: list
    """
    from dgenies.database import Gallery
    items = Gallery.select().where(Gallery.name == name)
    list_pictures = []
    for item in items:
        list_pictures.append(item.picture)
        item.delete_instance()
    return list_pictures


def set_inforun_message(message, type):
    inforun_file = os.path.join(config.config_dir, ".inforun")
    with open(inforun_file, "w") as info:
        info.write(json.dumps({
            "message": message,
            "type": type
        }))


def clear_inforun():
    inforun_file = os.path.join(config.config_dir, ".inforun")
    if os.path.exists(inforun_file):
        os.remove(inforun_file)


if __name__ == "__main__":
    command, args = parse_args()
    if command == "run":
        run(*args)
    elif command == "clear":
        if args:
            if args.crons:
                print("Cleaning crons...")
                clear_crons()
            if args.logs:
                print("Cleaning logs...")
                clear_logs()
            if args.jobs:
                print("Cleaning jobs...")
                clear_jobs(args.max_age, args.web)
            if args.analytics:
                if config.analytics_enabled:
                    print("Cleaning analytics...")
                    clear_analytics(args.max_age)
                else:
                    print("Analytics is disabled, cannot clear it!")
    elif command == "gallery_add":
        add_to_gallery(args.id_job, args.name, args.pict, args.query, args.target)
    elif command == "gallery_del":
        if args.id_job is None and args.name is None:
            print("Error: please give an id or a name for the job!")
            exit(1)
        if args.id_job is not None:
            pictures = del_from_gallery_by_id(args.id_job)
        else:
            pictures = del_from_gallery_by_name(args.name)
        if args.remove_pict:
            for picture in pictures:
                try:
                    os.remove(os.path.join(config.app_data, "gallery", picture))
                except FileNotFoundError:
                    pass
    elif command == "inforun":
        if args.clear:
            clear_inforun()
        else:
            if args.message is None:
                print("-m/--message option is required if -c/--clear not given")
                exit(1)
            if args.type is None:
                print("-t/--type option is required if -c/--clear not given")
                exit(1)
            set_inforun_message(args.message, args.type)
