__version__ = "0.1.4"

import os
import pathlib
import re
import shutil
import sys

from docopt import docopt, DocoptExit
from schema import Schema, And, Or, Use, SchemaError

from wopmars.SQLManager import SQLManager
from wopmars.WorkflowManager import WorkflowManager
from wopmars.utils.DictUtils import DictUtils
from wopmars.utils.Logger import Logger
from wopmars.utils.OptionManager import OptionManager
from wopmars.utils.PathManager import PathManager
from wopmars.utils.WopMarsException import WopMarsException
from wopmars.utils.various import get_mtime, get_current_time


__doc__ = """wopmars %s

Usage:
  wopmars --version | (-D DATABASE) (-w DEFINITION_FILE) [-n] [-F] [-v...] [-d DIR] [-g FILE] [-L FILE] [-S RULE | -U RULE] [-c] [-t]
  wopmars tool TOOLWRAPPER [-i DICT] [-o DICT] [-P DICT] [-F] [-D DATABASE] [-v...] [-d DIR] [-L FILE] [-g FILE] [-c] [-t]
  wopmars example [-d DIR]

Arguments:
  DATABASE         Path to the sqlite database file (Required)
  DEFINITION_FILE  Path to the definition file of the workflow (Required)
  FILE             Path to a file.
  DIR              Path to a directory.
  RULE             Name of a rule in the workflow definition file.
  TOOLWRAPPER      Path the the tool_python_path
  DICT             String formatted like a dictionary. Ex: "{'input1': 'path/to/input1', 'input2': 'path/to/input2'}"

Options:
  --version                    Show version and exists
  -D --database=DATABASE       REQUIRED: Set the path to the database, e.g -D sqlite:///db.sqlite
  -F --forceall                Force the execution of the workflow, without checking for previous executions.
  -L FILE --log=FILE           Write logs in FILE file.
  -P --params=DICT             Set the parameters of the tool_python_path you want to use in the dictionary format.
  -S RULE --since=RULE         Execute the workflow since the given RULE.
  -U RULE --until=RULE         Execute the workflow until the given RULE.
  -c --cleanup-metadata        Clear WoMars history. Should be used in case of bug which seem to be related to the history. Be carefull, clearing history will result in a re-execution of the whole workflow.
  -d --directory=DIR           Specify working directory (relative paths in the wopfile will use this as their origin). [default: $CWD].
  -g FILE --dot=FILE           Write dot representing the workflow in the FILE file (with .dot extension). This option needs to install WopMars with pygraphviz (pip install wopmars[pygraphviz])
  -h --help                    Show this help.
  -i --input=DICT              Set the input of the tool_python_path you want to use in the dictionary format.
  -n --dry-run                 Only display what would have been done.
  -o --output=DICT             Set the output of the tool_python_path you want to use in the dictionary format.
  -t --touch                   Only display what would have been done.
  -u --update                  Should be used when a file supposedly generated by the workflow already exists and should be used as it. (Not implemented)
  -v                           Set verbosity level, eg -v, -vv or -vvv
  -w --wopfile=DEFINITION_FILE REQUIRED: Set the path to the definition file.

Example:
    wopmars example
    cd example
    pip install .
    wopmars -w Wopfile.yml -D "sqlite:///db.sqlite" -v
    
Description:
    WopMars - Workflow Python Manager for Reproducible Science
    Gonzalez A., Giffon L., Spinelli L. 2016-2020
    https://wopmars.readthedocs.io
    https://github.com/aitgon/wopmars
"""%__version__


class WopMars:

    @staticmethod
    def run(argv):

        """
        Entry-point of the program
        """

        # if the command line is malformed, docopt interrupt the software.
        try:
            if argv[1:] == []: # If not arguments, run the help
                argv.append('-h')
            OptionManager.instance().update(docopt(__doc__, argv=argv[1:]))

        except DocoptExit as SE:
            print("Bad argument in the command line: \n\t" + " ".join(argv) + "\n" + str(SE))
            sys.exit(2)

        try:
            schema_option = Schema({
                '--wopfile': Or("Wopfile.yml", str),
                '--database': Use(PathManager.check_database_valid_url),
                '-v': Or(0, And(int, lambda n: 1 <= n <= 2)),
                '--dot': Or(None, And(Use(PathManager.check_valid_path), Use(PathManager.check_pygraphviz))),
                "--log": Use(PathManager.check_valid_path),
                # '--printtools': Use(bool),
                "--since": Or(None, str),
                "--until": Or(None, str),
                "--forceall": Use(bool),
                "--dry-run": Use(bool),
                "--touch": Use(bool),
                "--directory": Use(lambda path: pathlib.Path(path).mkdir(parents=True, exist_ok=True)),
                "--input": Use(DictUtils.str_to_dict),
                "--output": Use(DictUtils.str_to_dict),
                "--params": Use(DictUtils.str_to_dict),
                "TOOLWRAPPER": Or(None, Use(PathManager.is_in_python_path)),
                "tool": Use(bool),
                "example": Use(bool),
                "--version": Use(bool),
                "--cleanup-metadata": Use(bool),
            })
            # The option values are validated using schema library
            OptionManager.instance().validate(schema_option)
            os.chdir(OptionManager.instance()["--directory"])

        except SchemaError as schema_msg:
            Logger.instance().debug("\nCommand line Args:" + str(OptionManager.instance()))
            # regex for the different possible error messages.
            match_open_def = re.match(r"^open\('(.[^\)]+)'\)", str(schema_msg))
            match_dot_def = re.match(r"^check_valid_path\(('.[^\)]+')\)", str(schema_msg))
            match_wrong_key = re.match(r"^Wrong keys ('.[^\)]+')", str(schema_msg))
            match_pygraphviz = re.match(r".*dot.*", str(schema_msg))
            print(match_pygraphviz)
            # Check the different regex..
            if match_open_def:
                Logger.instance().error("The file " + match_open_def.group(1) + " cannot be opened. It may not exist.")
            elif match_dot_def:
                Logger.instance().error("The path " + match_dot_def.group(1) + " is not valid.")
            elif match_wrong_key:
                # Normally never reach
                Logger.instance().error("The option key " + match_wrong_key.group(1) + " is not known.")
            elif match_pygraphviz:
                Logger.instance().error("The dot file path is not valid or the pygraphviz module is not installed. In the second case, install wopmars with pygraphviz: pip install wopmars[pygraphviz]")
            else:
                # Normally never reach
                Logger.instance().error("An unknown error has occured. Message: " + str(schema_msg))
            sys.exit(2)

        Logger.instance().debug("\nCommand line Args:" + str(OptionManager.instance()))

        ############################################################################################
        #
        # Print version to stdout and exists
        #
        ############################################################################################

        if OptionManager.instance()["--version"]:
            print("wopmars {}".format(__version__), file=sys.stdout)
            sys.exit(0)

        ############################################################################################
        #
        # Recursively writes quickstart example and exists
        #
        ############################################################################################

        if OptionManager.instance()["example"]:
            # ExampleBuilder().build()

            source_path = os.path.join(PathManager.get_package_path(), "data/example")
            destination_path = os.path.join("example")

            shutil.rmtree(destination_path, ignore_errors=True)
            shutil.copytree(source_path, destination_path)

            sys.exit(0)

        ############################################################################################
        #
        # Initiates new WorkflowManager instance
        #
        ############################################################################################

        workflow_manager = WorkflowManager()

        ############################################################################################
        #
        # Cleans up non fully terminated executions
        #
        ############################################################################################

        SQLManager.instance().clean_up_unexecuted_tool_wrappers()

        ############################################################################################
        #
        # --cleanup-metadata (clear history and exit)
        #
        ############################################################################################

        if OptionManager.instance()["--cleanup-metadata"]:
            Logger.instance().info("Deleting Wopmars history...")
            # Check if sqlite db path exists
            if pathlib.Path(SQLManager.instance().d_database_config['db_database']).is_file():
                SQLManager.instance().clear_wopmars_history()
            if OptionManager.instance()["--cleanup-metadata"]:
                sys.exit(0)

        try:
            workflow_manager.run()
        except WopMarsException as WE:
            Logger.instance().error(str(WE))
            try:
                timestamp_epoch_millis, timestamp_human = get_current_time()
                Logger.instance().error("The workflow has encountered an error at: {}".format(timestamp_human))
                workflow_manager.set_finishing_informations(timestamp_human, "ERROR")
            except AttributeError:
                SQLManager.instance().get_session().rollback()
                Logger.instance().error("The execution has not even begun. No informations will be stored in the database.")
            except Exception as e:
                Logger.instance().error("An error occurred during the rollback of the changement of the database which can be now unstable:" +
                                        str(e))
            sys.exit(1)
        except Exception as e:
            Logger.instance().error("An unknown error has occurred:\n" + str(e))
            sys.exit(1)


def run():

    sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/toolwrappers/")
    sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/base/")

    WopMars().run(sys.argv)

if __name__ == "__main__":
    run()
