#!python
"""
    This file is part of findCPcli project.
    Copyright (C) 2020-2021  Alex Oarga  <718123 at unizar dot es> (University of Zaragoza)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import sys, os
import time
from argparse import ArgumentParser
from dotenv import load_dotenv

load_dotenv()

ENV_ENVIRONMENT = "ENVIRONMENT"
ENV_DEV = "DEV"
ENV_PRO = "PRO"

if os.environ.get(ENV_ENVIRONMENT) == ENV_DEV:
    # import relative path of findCPcore for development purposes
    sys.path.append("../../findCPcore_pkg/findCPcore/")
    from Facade import Facade
    from FacadeUtils import FacadeUtils
else:
    from findCPcore import Facade
    from findCPcore import FacadeUtils

TASK_SPREADSHEET = "TASK_SPREADSHEET"
TASK_SENSIBILITY = "TASK_SENSIBILITY"
TASK_SAVE_WITHOUT_DEM = "TASK_SAVE_WITHOUT_DEM"
TASK_SAVE_WITH_FVA = "TASK_SAVE_WITH_FVA"
TASK_SAVE_WITH_FVA_DEM = "TASK_SAVE_WITH_FVA_DEM"

MSG_SUCCESFULL_SAVE = "File succesfully saved at: {}"
MSG_UNKNOWN_ERROR = "Error, something went wrong: {}"
MSG_FURTHER_INFO = "run findCPcli -h for further information."

PARAM_INPUT_FILE = "<input file>"
PARAM_OUTPUT_FILE = "<output file>"
PARAM_REACTION_ID = "<reaction id>"

LICENSE = """
    findCPcli Copyright (C) 2020-2021  Alex Oarga  <718123 at unizar dot es> (University of Zaragoza)
    This program comes with ABSOLUTELY NO WARRANTY;
    This is free software, and you are welcome to redistribute it
    under certain conditions;
    For details see <https://github.com/findCP/findCPcli/blob/master/LICENSE>
    If not available, see <https://www.gnu.org/licenses/>
"""


def verbose_f(text, args1=False, args2=False):
    if args1:
        print(text)


def run(task, model_path, output_path, verbose, objective=None):
    try:
        if task == TASK_SPREADSHEET:
            print("Task: Save results spreadsheet")
            facade = Facade()
            error = facade.generate_spreadsheet(
                False,
                model_path,
                verbose_f,
                args1=verbose,
                args2=None,
                output_path=None,
                objective=objective,
            )
            if error == "":
                (result_ok, text) = facade.save_spreadsheet(
                    False, output_path, verbose_f
                )
                if result_ok:
                    print(MSG_SUCCESFULL_SAVE.format(text))
                else:
                    print(MSG_UNKNOWN_ERROR.format(text))
            else:
                print(MSG_UNKNOWN_ERROR.format(error))
            print("")

        elif task == TASK_SENSIBILITY:
            print("Task: Save growth dependent chokepoints spreadsheet")
            facade = Facade()
            error = facade.generate_sensibility_spreadsheet(
                False,
                model_path,
                verbose_f,
                args1=verbose,
                args2=None,
                output_path=None,
                objective=objective,
            )
            if error == "":
                (result_ok, text) = facade.save_spreadsheet(
                    False, output_path, verbose_f
                )
                if result_ok:
                    print(MSG_SUCCESFULL_SAVE.format(text))
                else:
                    print(MSG_UNKNOWN_ERROR.format(text))
            else:
                print(MSG_UNKNOWN_ERROR.format(error))
            print("")

        elif task == TASK_SAVE_WITHOUT_DEM:
            print("Task: save model without dead end metabolites")
            facade = Facade()
            facade.find_and_remove_dem(
                False, output_path, verbose_f, model_path, args1=verbose, args2=None
            )
            (result_ok, text) = facade.save_model(output_path, False, verbose_f)
            if result_ok:
                print(MSG_SUCCESFULL_SAVE.format(text))
            else:
                print(MSG_UNKNOWN_ERROR.format(text))
            print("")

        elif task == TASK_SAVE_WITH_FVA:
            print("Task: save model refined with Flux Variability Analysis")
            facade = Facade()
            error = facade.run_fva(
                False,
                output_path,
                verbose_f,
                model_path,
                args1=verbose,
                args2=None,
                objective=objective,
            )
            if error != "":
                print(MSG_UNKNOWN_ERROR.format(error))
            else:
                (result_ok, text) = facade.save_model(output_path, False, verbose_f)
                if result_ok:
                    print(MSG_SUCCESFULL_SAVE.format(text))
                else:
                    print(MSG_UNKNOWN_ERROR.format(text))
            print("")

        elif task == TASK_SAVE_WITH_FVA_DEM:
            print(
                "Task: save model refined with Flux Variability Analysis without dead end metabolites"
            )
            facade = Facade()
            error = facade.run_fva_remove_dem(
                False,
                output_path,
                verbose_f,
                model_path,
                args1=verbose,
                args2=None,
                objective=objective,
            )
            if error != "":
                print(MSG_UNKNOWN_ERROR.format(error))
            else:
                (result_ok, text) = facade.save_model(output_path, False, verbose_f)
                if result_ok:
                    print(MSG_SUCCESFULL_SAVE.format(text))
                else:
                    print(MSG_UNKNOWN_ERROR.format(text))
            print("")

    except Exception as error:
        print("Error: Something went wrong:")
        # Important: the error is not raised but instead it is printed.
        print(str(error))


def read_input():
    input_file = ""
    output = ""
    parser = ArgumentParser(description="")

    parser.add_argument(
        "-v", "--verbose", help="Print feedback while running.", action="store_true"
    )
    parser.add_argument(
        "-l",
        "--license",
        dest="license",
        help="View license info.",
        action="store_true",
    )
    parser.add_argument(
        "-i",
        dest="inputfile",
        required=True,
        metavar=PARAM_INPUT_FILE,
        help="Input metabolic model. Allowed file formats: .xml .json .yml",
    )
    parser.add_argument(
        "-o",
        dest="outputfile",
        metavar=PARAM_OUTPUT_FILE,
        help="Output spreadsheet file with results. Allowed file formats: .xls .xlsx .ods",
    )
    parser.add_argument(
        "-cp",
        dest="cp",
        metavar=PARAM_OUTPUT_FILE,
        help="Output spreadsheet file with growth dependent chokepoints. Allowed file formats: .xls .xlsx .ods",
    )
    parser.add_argument(
        "-swD",
        dest="modelwodem",
        metavar=PARAM_OUTPUT_FILE,
        help="Save output model without Dead End Metabolites. Allowed file formats: .xml .json .yml",
    )
    parser.add_argument(
        "-sF",
        dest="modelfva",
        metavar=PARAM_OUTPUT_FILE,
        help="Save output model with reactions bounds updated with Flux Variability Analysis. Allowed file formats: .xml .json .yml",
    )
    parser.add_argument(
        "-swDF",
        dest="modelfvawodem",
        metavar=PARAM_OUTPUT_FILE,
        help="Save output model with reactions bounds updated with Flux Variability Analysis and without Dead End Metabolites. Allowed file formats: .xml .json .yml",
    )
    parser.add_argument(
        "-objective",
        dest="objective",
        metavar=PARAM_REACTION_ID,
        help="Reaction id to be used as objective function with Flux Balance Analysis",
    )

    args = parser.parse_args()

    input_file = args.inputfile
    output = args.outputfile
    cp = args.cp
    modelwodem = args.modelwodem
    modelfva = args.modelfva
    modelfvawodem = args.modelfvawodem
    objective = args.objective

    return (
        input_file,
        output,
        cp,
        modelwodem,
        modelfva,
        modelfvawodem,
        args.verbose,
        objective,
        args.license,
    )


def __check_model_file(path):
    if path[-4:] != ".xml" and path[-5:] != ".json" and path[-4:] != ".yml":
        return False
    else:
        return True


def __check_spread_file(path):
    if path[-4:] != ".xls" and path[-4:] != ".ods" and path[-5:] != ".xlsx":
        return False
    else:
        return True


def validate_input_model(input_file):
    if not __check_model_file(input_file):
        print("Model file must be either .xml .json .yml")
        print(MSG_FURTHER_INFO)
        exit(1)


# Method for validating the output file path for operation that generate a new model
def validate_output_model(list_of_output_path):
    for output_path in list_of_output_path:
        # if the output path is not None,
        # 	the task will be performed and the file needs validation
        # else if the output path is None,
        # 	the task has not been selected and the file is not checked
        if output_path is not None and not __check_model_file(output_path):
            print("Output model file must be either .xml .json .yml")
            print(MSG_FURTHER_INFO)
            exit(1)


# Method for validating the output file path for operation that generate a new spreadsheet file
def validate_output_spread(list_of_output_path):
    for output_path in list_of_output_path:
        # if the output path is not None,
        # 	the task will be performed and the file needs validation
        # else if the output path is None,
        # 	task has not been selected and the file is not checked
        if output_path is not None and not __check_spread_file(output_path):
            print("Output file must be .xls .xlsx or .ods")
            print(MSG_FURTHER_INFO)
            exit(1)


# Check that at least one operation has been selected
def validate_operation_selected(list_of_possible_operations):
    operation_selected = False
    for operation in list_of_possible_operations:
        if operation is not None:
            operation_selected = True

    if not operation_selected:
        print("Please select at least one operation to perform: ")
        print(
            "[-o <output file>] [-cp <output file>] [-swD <output file>] "
            + "[-sF <output file>] [-swDF <output file>]"
        )
        print(MSG_FURTHER_INFO)
        exit(1)


#
# 	Main cli workflow
# 	 Input is read, validated and each task executed independently
#
def main():
    # Read input
    (
        input_file,
        output,
        cp,
        modelwodem,
        modelfva,
        modelfvawodem,
        verbose,
        objective,
        license,
    ) = read_input()

    # Operations that dont requiere validation
    if license:
        print(LICENSE)

    # Note that the following save the outputh path of each operation
    # If the variable is None, no operation that operation has not been selected
    NEW_MODEL_OPERATIONS = [
        modelwodem,
        modelfva,
        modelfvawodem,
    ]  # Tasks that generate new models
    NEW_SPREADSHEET_OPS = [output, cp]  # Tasks that generate spreadsheet files

    # Check input file formats and task
    validate_input_model(input_file)
    validate_output_model(NEW_MODEL_OPERATIONS)
    validate_output_spread(NEW_SPREADSHEET_OPS)
    validate_operation_selected(NEW_MODEL_OPERATIONS + NEW_SPREADSHEET_OPS)

    # Run task
    if output is not None:
        task = TASK_SPREADSHEET
        out_path = output
        run(task, input_file, out_path, verbose, objective)

    if cp is not None:
        task = TASK_SENSIBILITY
        out_path = cp
        run(task, input_file, out_path, verbose, objective)

    if modelwodem is not None:
        task = TASK_SAVE_WITHOUT_DEM
        out_path = modelwodem
        run(task, input_file, out_path, verbose, objective)

    if modelfva is not None:
        task = TASK_SAVE_WITH_FVA
        out_path = modelfva
        run(task, input_file, out_path, verbose, objective)

    if modelfvawodem is not None:
        task = TASK_SAVE_WITH_FVA_DEM
        out_path = modelfvawodem
        run(task, input_file, out_path, verbose, objective)


if __name__ == "__main__":
    main()
