import os
import sys
import argparse
try:
    import deepks
except ImportError as e:
    sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../")
from deepks.utils import load_yaml, deep_update


def main_cli(args=None):
    parser = argparse.ArgumentParser(
                prog="deepks",
                description="A program to generate accurate energy functionals.")
    parser.add_argument("command", 
                        help="specify the sub-command to run, possible choices: "
                             "train, test, scf, stats, iterate")
    parser.add_argument("args", nargs=argparse.REMAINDER,
                        help="arguments to be passed to the sub-command")

    args = parser.parse_args(args)

    # sepatate all sub_cli to make them useable independently 
    if args.command.upper() == "TRAIN":
        sub_cli = train_cli
    elif args.command.upper() == "TEST":
        sub_cli = test_cli
    elif args.command.upper() == "SCF":
        sub_cli = scf_cli
    elif args.command.upper() == "STATS":
        sub_cli = stats_cli
    elif args.command.upper().startswith("ITER"):
        sub_cli = iter_cli
    else:
        return ValueError(f"unsupported sub-command: {args.command}")
    
    sub_cli(args.args)


def train_cli(args=None):
    parser = argparse.ArgumentParser(
                prog="deepks train",
                description="Train a model according to given input.",
                argument_default=argparse.SUPPRESS)
    parser.add_argument('input', type=str, nargs="?",
                        help='the input yaml file for args')
    parser.add_argument('-r', '--restart',
                        help='the restart file to load model from, would ignore model_args if given')
    parser.add_argument('-d', '--train-paths', nargs="*",
                        help='paths to the folders of training data')
    parser.add_argument('-t', '--test-paths', nargs="*",
                        help='paths to the folders of testing data')
    parser.add_argument('-o', '--ckpt-file',
                        help='file to save the model parameters, default: model.pth')
    parser.add_argument("-P", "--proj_basis",
                        help="basis set used to project density matrix")   
    parser.add_argument('-S', '--seed', type=int,
                        help='use specified seed in initialization and training')
    parser.add_argument("-D", "--device",
                        help="device name used in training the model")    
    args = parser.parse_args(args)
    
    if hasattr(args, "input"):
        argdict = load_yaml(args.input)
        del args.input
        argdict.update(vars(args))
    else:
        argdict = vars(args)

    from deepks.model.train import main
    main(**argdict)


def test_cli(args=None):
    parser = argparse.ArgumentParser(
                prog="deepks test",
                description="Test a model with given data (Not SCF).",
                argument_default=argparse.SUPPRESS)
    parser.add_argument("input", nargs="?",
                        help='the input yaml file used for training')
    parser.add_argument("-d", "--data-paths", type=str, nargs='+',
                        help="the paths to data folders containing .npy files for test")
    parser.add_argument("-m", "--model-file", type=str, nargs='+',
                        help="the dumped model file to test")
    parser.add_argument("-o", "--output-prefix", type=str,
                        help=r"the prefix of output file, would wite into file %%prefix.%%sysidx.out")
    parser.add_argument("-E", "--e-name", type=str,
                        help="the name of energy file to be read (no .npy extension)")
    parser.add_argument("-D", "--d-name", type=str, nargs="+",
                        help="the name of descriptor file(s) to be read (no .npy extension)")
    parser.add_argument("-G", "--group", action='store_true',
                        help="group test results for all systems")
    args = parser.parse_args(args)

    if hasattr(args, "input"):
        rawdict = load_yaml(args.input)
        del args.input
        argdict = {}
        if "ckpt_file" in rawdict["train_args"]:
            argdict["model_file"] = rawdict["train_args"]["ckpt_file"]
        if "e_name" in rawdict["data_args"]:
            argdict["e_name"] = rawdict["data_args"]["e_name"]
        if "d_name" in rawdict["data_args"]:
            argdict["d_name"] = rawdict["data_args"]["d_name"]
        if "test_paths" in rawdict:
            argdict["data_paths"] = rawdict["test_paths"]
        argdict.update(vars(args))
    else:
        argdict = vars(args)

    from deepks.model.test import main
    main(**argdict)


def scf_cli(args=None):
    parser = argparse.ArgumentParser(
                prog="deepks scf",
                description="Calculate and save SCF results using given model.",
                argument_default=argparse.SUPPRESS)
    parser.add_argument("input", nargs="?",
                        help='the input yaml file for args')
    parser.add_argument("-s", "--systems", nargs="*",
                        help="input molecule systems, can be xyz files or folders with npy data")
    parser.add_argument("-m", "--model-file",
                        help="file of the trained model")
    parser.add_argument("-d", "--dump-dir",
                        help="dir of dumped files")
    parser.add_argument("-v", "--verbose", type=int, choices=range(0,6),
                        help="output level of calculation information")
    parser.add_argument("-F", "--dump-fields", nargs="*",
                        help="fields to be dumped into the folder") 
    parser.add_argument("-B", "--basis",
                        help="basis set used to solve the model") 
    parser.add_argument("-P", "--proj_basis",
                        help="basis set used to project dm, must match with model")   
    parser.add_argument("-D", "--device",
                        help="device name used in nn model inference")               
    group0 = parser.add_mutually_exclusive_group()   
    group0.add_argument("-G", "--group", action='store_true', dest="group",
                        help="group results for all systems, only works for same number of atoms")
    group0.add_argument("-NG", "--no-group", action='store_false', dest="group",
                        help="Do not group results for different systems (default behavior)")
    parser.add_argument("-X", "--scf-xc",
                        help="base xc functional used in scf equation, default is HF")        
    parser.add_argument("--scf-conv-tol", type=float,
                        help="converge threshold of scf iteration")
    parser.add_argument("--scf-conv-tol-grad", type=float,
                        help="gradient converge threshold of scf iteration")
    parser.add_argument("--scf-max-cycle", type=int,
                        help="max number of scf iteration cycles")
    parser.add_argument("--scf-diis-space", type=int,
                        help="subspace dimension used in diis mixing")
    parser.add_argument("--scf-level-shift", type=float,
                        help="level shift used in scf calculation")

    args = parser.parse_args(args)

    scf_args={}
    for k, v in vars(args).copy().items():
        if k.startswith("scf_"):
            scf_args[k[4:]] = v
            delattr(args, k)

    if hasattr(args, "input"):
        argdict = load_yaml(args.input)
        del args.input
        argdict.update(vars(args))
        argdict["scf_args"].update(scf_args)
    else:
        argdict = vars(args)
        argdict["scf_args"] = scf_args

    from deepks.scf.run import main
    main(**argdict)


def stats_cli(args=None):
    parser = argparse.ArgumentParser(
                prog="deepks stats",
                description="Print the stats of SCF results.",
                argument_default=argparse.SUPPRESS)
    parser.add_argument("input", nargs="?",
                        help='the input yaml file used for SCF calculation')
    parser.add_argument("-s", "--systems", nargs="*",
                        help='system paths used as training set (i.e. calculate shift)')
    parser.add_argument("-d", "--dump-dir",
                        help="directory used to save SCF results of training systems")
    parser.add_argument("-ts", "--test-sys", nargs="*",
                        help='system paths used as testing set (i.e. not calculate shift)')
    parser.add_argument("-td", "--test-dump",
                        help="directory used to save SCF results of testing systems")
    parser.add_argument("-G", "--group", action='store_true',
                        help="if set, assume results are grouped")
    parser.add_argument("-NC", action="store_false", dest="with_conv",
                        help="do not print convergence results")
    parser.add_argument("-NE", action="store_false", dest="with_e",
                        help="do not print energy results")
    parser.add_argument("-NF", action="store_false", dest="with_f",
                        help="do not print force results")
    parser.add_argument("--e-name",
                        help="name of the energy file (no extension)")
    parser.add_argument("--f-name",
                        help="name of the force file (no extension)")
    args = parser.parse_args(args)

    if hasattr(args, "input"):
        rawdict = load_yaml(args.input)
        del args.input
        argdict = {fd: rawdict[fd]
                     for fd in ("systems", "dump_dir", "group")
                     if fd in rawdict}
        argdict.update(vars(args))
    else:
        argdict = vars(args)

    from deepks.scf.stats import print_stats
    print_stats(**argdict)


def iter_cli(args=None):
    parser = argparse.ArgumentParser(
                prog="deepks iterate",
                description="Run the iteration procedure to train a SCF model.",
                argument_default=argparse.SUPPRESS)
    parser.add_argument("argfile", nargs="*", default=[],
                        help='the input yaml file for args, '
                             'if more than one, the latter has higher priority')
    parser.add_argument("-s", "--systems-train", nargs="*",
                        help='systems for training, '
                             'can be xyz files or folders with npy data')
    parser.add_argument("-t", "--systems-test", nargs="*",
                        help='systems for training, '
                             'can be xyz files or folders with npy data')
    parser.add_argument("-n", "--n-iter", type=int,
                        help='the number of iterations to run')
    parser.add_argument("--workdir",
                        help='working directory, default is current directory')
    parser.add_argument("--share-folder", 
                        help='folder to store share files, default is "share"')
    parser.add_argument("--cleanup", action="store_true", dest="cleanup",
                        help='if set, clean up files used for job dispatching')
    parser.add_argument("--no-strict", action="store_false", dest="strict",
                        help='if set, allow other arguments to be passed to task')
    # allow cli specified argument files
    sub_names = ["scf-input", "scf-machine", "train-input", "train-machine",
                 "init-model", "init-scf", "init-train"]
    for name in sub_names:
        parser.add_argument(f"--{name}",
            help='if specified, subsitude the original arguments with given file')
    
    args = parser.parse_args(args)
    argdict = {}
    for fl in args.argfile:
        argdict = deep_update(argdict, load_yaml(fl))
    del args.argfile
    argdict.update(vars(args))

    from deepks.iterate.iterate import main
    main(**argdict)


if __name__ == "__main__":
    main_cli()