#===----------------------------------------------------------------------===#
#
#         STAIRLab -- STructural Artificial Intelligence Laboratory
#
#===----------------------------------------------------------------------===#
#
import sys
import json
from xcsi.csi import collect_outlines, find_rows
from xcsi.job import Job
from xcsi._io._json import DepthLimitedEncoder

class CommandLineError(Exception): pass

from xcsi._io import csiread



def main():
    read_file = None
    save_file = None
    operation = "View"
    strict_mode = None
    object_mode = "Model"
    interact = False
    Options = {
        "Execute": {
            "Case": None
        }
    }
    
    Verbose = 0
    show = set()
    objects = []
    fmt = "txt"

    i  = 0
    argi = iter(sys.argv[1:])
    for arg in argi:
        if arg == "-s":
            object_mode = "Section"
        elif arg == "-t":
            object_mode = next(argi)
            if object_mode not in ["Model", "Section", "Pattern", "Step"]:
                raise CommandLineError(f"Unknown object mode {object_mode}")

        elif arg == "-o":
            try:
                save_file = next(argi)
            except StopIteration:
                raise CommandLineError("Flag -o missing required argument <file>")

        elif arg == "-i":
            interact = True

        elif arg == "-m":
            try:
                strict_mode = next(argi)
            except StopIteration:
                raise CommandLineError("Flag -m missing required argument <mode>")

        elif arg == "--show":
            try:
                show.add(next(argi))
            except StopIteration:
                raise ValueError("Flag --show missing required argument <name>")
        
        elif arg == "--case":
            try:
                Options["Execute"]["Case"] = next(argi)
            except StopIteration:
                raise CommandLineError("Flag --case missing required argument <name>")
            try:
                Options["Execute"]["Case"] = int(Options["Execute"]["Case"])
            except ValueError:
                pass

        elif arg == "--verbose":
            try:
                Verbose = int(next(argi))
            except StopIteration:
                Verbose += 1
    
        elif arg == "--log":
            operation = "Log"
        elif arg[0] == "-":
            key = arg[1].lower()
            operation = {
                "p": "Print",
                "a": "Analyze",
                "c": "Export",
                "x": "Execute",
                "v": "Visualize",
                "r": "Visualize" # new; free up -v for verbose
            }[key]

        elif read_file is None:
            read_file = arg
        else:
            objects.append(arg.split("="))

        i += 1

    if read_file is None:
        raise ValueError("No file specified")

    csi = csiread(read_file)


    if operation == "Export":
        # Convert
        job = Job(csi)
        if object_mode == "Section":
            for k, v in objects:
                section = job.find(frame_section=v)
                if section is None:
                    raise ValueError(f"Section {v} not found")
                print(json.dumps(section._create_model().to_dict(), indent=2))
                sys.exit()


        if "tcl" in sys.argv[1]:
            import xara
            model = xara.Model(ndm=3, ndf=6, echo_file=sys.stdout)
            job.instance(model=model)

        elif "csi" in sys.argv[1]:
            if save_file is not None:
                with open(save_file, "w") as f:
                    json.dump(csi, f, cls=DepthLimitedEncoder, max_depth=2, indent=2)
            else:
                print(json.dumps(csi, cls=DepthLimitedEncoder, max_depth=2, indent=2))

        else:
            model = job.instance().model
            if save_file is not None:
                model.print("-json", f"{save_file}")
            else:
                import os
                if os.name == "nt":
                    stdout = "CON"
                else:
                    stdout = "/dev/stdout"
                model.print("-json", stdout)
        sys.exit()


    if operation == "Print":
        job = Job(csi)
        if object_mode == "Pattern":
            for pattern in job.patterns():
                print(pattern)
        elif object_mode == "Step":
            for step in job.steps():
                print(step)
        elif len(objects) == 0:
            for table in csi:
                print(f"{table}: {len(csi[table])}")
        else:
            for key, value in objects:
                for table in csi:
                    filt = {key: value}
                    if (row:= find_rows(csi[table], **filt)):
                        print(table)
                        for r in row:
                            for k, v in r.items():
                                if k == key:
                                    continue
                                print(f"  {k:<10}      {v}")
                            print()
                            continue
        sys.exit()



    if operation == "Execute":
        job = Job(csi)

        ins = job.instance(verbose=Verbose)

        if Options["Execute"]["Case"] is not None: 
            case = job.find_case(name=Options["Execute"]["Case"])
            if case is None:
                raise ValueError(f"Case {Options['Execute']['Case']} not found")
            case.run(ins)

        else:
            for step in job.steps():
                print(step)
                step.run(ins)
                ins.model.print()
            # break

        if interact:
            # Start an interactive console
            from opensees.repl.ptkshell import OpenSeesREPL
            repl = OpenSeesREPL(ins.model._openseespy._interp)
            repl.repl()

    elif operation == "Log":
        job = Job(csi)

        instance = job.instance(verbose=Verbose)
        instance._print_log()

    elif operation == "Visualize":
        job = Job(csi)

        # Visualize
        import veux
        instance = job.instance(verbose=Verbose)
        model = instance.model
        outlines = collect_outlines(csi, instance.names._library["frame_tags"])
        artist = veux.create_artist(model, canvas="gltf", vertical=3,
                    model_config={
                        "frame_outlines": outlines
                    }
        )
        # artist.draw_nodes()
        artist.draw_outlines()
        # artist.draw_surfaces()
        artist.draw_sections(outline=False)
        artist.draw_axes()
        artist.draw_origin(extrude=True)


        if save_file is not None:
            artist.save(save_file)
        else:
            veux.serve(artist)

    elif sys.argv[1] == "-Vn":
        # Visualize
        from scipy.linalg import null_space
        model.constraints("Transformation")
        model.analysis("Static")
        K = model.getTangent().T
        v = null_space(K)[:,0] #, rcond=1e-8)


        u = {
            tag: [1000*v[dof-1] for dof in model.nodeDOFs(tag)]
            for tag in model.getNodeTags()
        }

        import veux
        veux.serve(veux.render(model, u, canvas="gltf", vertical=3))

    elif sys.argv[1] == "-Q":
        # Quiet conversion
        pass
    else:
        raise ValueError(f"Unknown operation {sys.argv[1]}")


if __name__ == "__main__":
    main()
