"""Command line parsing"""

import os
from pathlib import Path
import signal
import sys
import json
from typing import List
import fasteners
from experimaestro.notifications import progress, report_eoj
from .core.types import ObjectType
from experimaestro.utils import logger
from experimaestro.core.objects import ConfigInformation
import experimaestro.taskglobals as taskglobals
import atexit


def parse_commandline(argv=None):
    """Called when executing a task"""
    if argv is None:
        argv = sys.argv[1:]
    taskid, params = argv
    tasktype = ObjectType.REGISTERED[taskid]
    with open(params, "r") as fp:
        params = json.load(fp)
        ConfigInformation.LOADING = True
        task = tasktype(**params)
        ConfigInformation.LOADING = False
        task.execute()


def run(parameters: Path):
    with open(parameters, "r") as fp:
        params = json.load(fp)
        env = taskglobals.Env.instance()

        env.wspath = Path(params["workspace"])
        env.taskpath = parameters.parent

        task = ConfigInformation.fromParameters(params["objects"])
        task.__taskdir__ = Path.cwd()

        # Set the tags
        task.__tags__ = params["tags"]

        # Notify that the task has started
        progress(0)

        # Execute the task
        task.execute()


def rmfile(path: Path):
    if path.is_file():
        logger.debug("Removing file %s", path)
        path.unlink()


class TaskRunner:
    """Runs a task, after locking"""

    def __init__(self, scriptpath: str, lockfiles: List[str]):
        # Sets the working directory
        self.scriptpath = Path(scriptpath)
        self.pidfile = self.scriptpath.with_suffix(".pid")
        self.lockfiles = lockfiles
        self.donepath = self.scriptpath.with_suffix(".done")
        self.failedpath = self.scriptpath.with_suffix(".failed")
        self.started = False
        self.locks = []
        env = taskglobals.Env.instance()
        env.taskpath = self.scriptpath.parent

        self.cleaned = False

    def cleanup(self):
        if not self.cleaned:
            self.cleaned = True
            logger.info("Cleaning up")
            rmfile(self.pidfile)
            for lock in self.locks:
                try:
                    if lock.acquired:
                        logger.info("Releasing lock")
                        lock.release()
                        logger.info("Released lock")
                except Exception:
                    logger.error("Error while releasing lock %s", lock)

            if self.started:
                report_eoj()
            logger.info("Finished cleanup")

    def handle_error(self, code, frame_type):
        logger.info("Error handler: finished with code %d", code)
        self.failedpath.write_text(str(code))
        self.cleanup()
        logger.info("Exiting")
        sys.exit(1)

    def run(self):
        atexit.register(self.cleanup)
        sigterm_handler = signal.signal(signal.SIGTERM, self.handle_error)
        sigint_handler = signal.signal(signal.SIGINT, self.handle_error)

        def remove_signal_handlers(remove_cleanup=True):
            """Removes cleanup in forked processes"""
            signal.signal(signal.SIGTERM, sigterm_handler)
            signal.signal(signal.SIGINT, sigint_handler)
            atexit.unregister(self.cleanup)

        if sys.platform != "win32":
            os.register_at_fork(after_in_child=remove_signal_handlers)

        try:
            workdir = self.scriptpath.parent
            os.chdir(workdir)
            os.getpid()
            logger.info("Working in directory %s", workdir)

            for lockfile in self.lockfiles:
                fullpath = str(Path(lockfile).resolve())
                logger.info("Locking %s", fullpath)
                lock = fasteners.InterProcessLock(fullpath)
                # MAYBE: should have a clever way to lock
                # Problem = slurm would have a job doing nothing...
                # Fix = maybe with two files
                if not lock.acquire(blocking=True):
                    logger.info("Could not lock %s", lockfile)
                    raise AssertionError("Could not lock %s", lockfile)
                self.locks.append(lock)

            # Check if failed/done have been generated by another job
            if self.donepath.is_file():
                logger.info("Job already completed")
            else:
                logger.info("Running task")
                rmfile(self.failedpath)
                self.started = True
                run(workdir / "params.json")

                # ... remove the handlers
                logger.info("Task ended successfully")
                remove_signal_handlers(remove_cleanup=False)

                # Everything went OK
                sys.exit(0)
        except Exception:
            logger.exception("Got exception while running")
            self.handle_error(1, None)

        except SystemExit as e:
            if e.code == 0:
                # Normal exit, just create the ".done" file
                self.donepath.touch()

                # ... and finish the exit process
                logger.info("This is the end (TODO: remove this line)")
                raise
            else:
                self.handle_error(e.code, None)
