from xcsi.csi import find_row, find_rows
from collections import defaultdict


class LinearStaticAnalysis:
    def __init__(self, name, job):
        self.name = name
        self._csi = job._tree
        self._job = job
        self.type = "LinStatic"


    def run(self, asm):
        """
        Run the linear static analysis case.
        """
        self._apply_loads(asm)

        # Run the analysis
        model = asm.model
        model.test("EnergyIncr", 1e-10, 10)
        model.system("Umfpack")
        model.constraints("Transformation")
        model.numberer("RCM")
        model.integrator("LoadControl", 1.0)
        model.algorithm("Newton")
        model.analysis("Static")
        model.analyze(1)
        model.reactions()
        model.print()

    def _apply_loads(self, asm):
        """
        Apply loads to the model for this linear static analysis case.
        """

        for assign in find_rows(self._csi.get("CASE - STATIC 1 - LOAD ASSIGNMENTS", []),
                       Case=self.name):
            pattern = self._job.pattern(assign["LoadName"])

            pattern.apply(asm)


    def __str__(self):
        return f"LinearStaticAnalysis(name={self.name})"
    
    def __repr__(self):
        return f"LinearStaticAnalysis(name={self.name})"


class LinearDynamicAnalysis:
    _option_fields = {
    }
    def __init__(self, name, job):
        self.name = name
        self._csi = job._tree
        self._job = job
        self.type = "LinDirHist"


    def run(self, asm):
        """
        Run the linear static analysis case.
        """
        self._apply_loads(asm)
        {
            "CASE - DIRECT HISTORY 3 - VISCOUS PROPORTIONAL DAMPING": [
                {"Case": "ACASE1", "SpecifyType": "Period", "MassCoeff": 0.0920869591121277, "StiffCoeff": 0.00433236240908684, "Period1": 1.4344, "Damping1": 0.02, "Period2": 1.29484, "Damping2": 0.02}
            ],
            "CASE - DIRECT HISTORY 4 - INTEGRATION PARAMETERS": [
                {"Case": "ACASE1", "IntMethod": "HilberHughesTaylor", "Gamma": 0.5, "Beta": 0.25, "Alpha": 0}
            ],
        }
        #
        # Damping
        #
        p03 = find_row(self._csi.get("CASE - DIRECT HISTORY 3 - VISCOUS PROPORTIONAL DAMPING", []), Case=self.name)
        if p03 is not None:
            model.rayleigh(p03.get("MassCoeff",0), 0, p03.get("StiffCoeff",0), 0)

        # Integration parameters
        beta = 0.25
        gamma = 0.5
        alpha = 0.0
        p04 = find_row(self._csi.get("CASE - DIRECT HISTORY 4 - INTEGRATION PARAMETERS", []), Case=self.name)
        if p04 is not None:
            beta = p04.get("Beta", beta)
            gamma = p04.get("Gamma", gamma)
            alpha = p04.get("Alpha", 0)

        # Run the analysis
        model = asm.model
        model.test("EnergyIncr", 1e-16, 10)
        model.system("Umfpack")
        model.constraints("Transformation")
        model.numberer("RCM")
        model.integrator("Newmark", beta=beta,gamma=gamma)
        model.algorithm("Newton")
        model.analysis("Transient")
        model.analyze(1)


    def _apply_loads(self, asm):
        """
        Apply loads to the model for this linear static analysis case.
        """

        for assign in find_rows(self._csi.get("CASE - DIRECT HISTORY 2 - LOAD ASSIGNMENTS", []),
                       Case=self.name):
            pattern = self._job.pattern(assign["LoadName"])
            pattern.apply(asm)

    def __str__(self):
        return f"LinearStaticAnalysis(name={self.name})"

    def __repr__(self):
        return f"LinearStaticAnalysis(name={self.name})"




class ModalAnalysis:
    """
    Uses tables:
    - "CASE - MODAL 1 - GENERAL"
    """
    _option_fields = {
        "solver",
        "max_num_modes",
        "min_num_modes",
    }
    def __init__(self, name, job, solver=None):
        self.name = name
        self._csi = job._tree
        self._job = job
        self.type = "LinModal"
        if solver is None:
            solver =  "-genBandArpack" #  "-fullGenLapack" # 
        self.solver = solver

        for assign in find_rows(self._csi.get("CASE - MODAL 1 - GENERAL", []),
                       Case=self.name):

            self.max_num_modes = assign.get("MaxNumModes",None)
            self.min_num_modes = assign.get("MinNumModes",1)

    @property 
    def options(self):
        return {
            "solver": self.solver,
            "max_num_modes": self.max_num_modes,
            "min_num_modes": self.min_num_modes,
        }

    def run(self, asm, verbose=True):
        """
        Run the modal analysis case.
        """
        self._apply_loads(asm)

        # Run the analysis
        model = asm.model
        model.constraints("Auto")
        ev = model.eigen(self.max_num_modes, solver=self.solver)
        # Print a summary of the last eigenvalue analysis 
        # (legacy OpenSeesPy command, will be changed in future)
        if verbose:
            model.modalProperties(print=True)
        return {
            "periods": [
                2 * 3.141592653589793 / (v ** 0.5) for v in ev
                if v > 1e-12
            ],
            "eigenvalues": ev
        }


    def _apply_loads(self, asm):
        """
        Apply loads to the model for this modal analysis case.
        """


    def __str__(self):
        return f"ModalAnalysis(name={self.name})"

    def __repr__(self):
        return f"ModalAnalysis(name={self.name})"


class NonlinearStaticAnalysis:
    """
    Uses tables:
    - "CASE - STATIC 1 - LOAD ASSIGNMENTS"
    - "CASE - STATIC 2 - NONLINEAR LOAD APPLICATION"
    - "CASE - STATIC 4 - NONLINEAR PARAMETERS"
    """
    def __init__(self, name, job):
        self.name = name
        self._csi = job._tree
        self._job = job
        csi = self._csi
        self.type = "NonStatic"

        self.SolScheme = "Iterative Only"

        p04 = find_row(csi.get("CASE - STATIC 4 - NONLINEAR PARAMETERS", []),
                       Case=name)

        MaxIterNR = 10
        if p04 is not None:
            self.SolScheme = p04.get("SolScheme", self.SolScheme)

            MaxIterNR = p04.get("MaxIterNR", MaxIterNR)
            ItConvTol = p04.get("ItConvTol", 1e-6)
            


    def _apply_loads(self, model):
        """
        Apply loads to the model for this nonlinear static analysis case.
        """

        "CASE - MODAL HISTORY 4 - PROPORTIONAL DAMPING"

        for assign in find_rows(self._csi.get("CASE - STATIC 1 - LOAD ASSIGNMENTS", []),
                       Case=self.name):
            pattern = self._job.pattern(assign["LoadName"])
            pattern.apply(model)

    def run(self, asm):
        model = asm.model
        self._apply_loads(model)

        if self.SolScheme != "Iterative Only":
            raise ValueError(f"Unsupported solution scheme: {self.SolScheme}")

        for assign in find_rows(self._csi.get("CASE - STATIC 2 - NONLINEAR LOAD APPLICATION", []),
                       Case=self.name):
            # pattern = self._job.pattern(assign["LoadName"])
            integrator = assign["LoadApp"]
        
        if integrator == "Displ Ctrl":
            u_max = assign["TargetDispl"]
            i_ctrl = assign["MonitorDOF"]

            # Use the Use Conjugate Displacement option if the analysis is having trouble converging.
            # The conjugate displacement is a weighted average of all displacements in the structure where each displacement degree of freedom is weighted by the load acting on that degree of freedom.
            # In other words, it is a measure of the work done by the applied load.
            model.integrator("DisplacementControl", 1, 1, 1.0, 1, 100)

        else:
            raise ValueError(f"Unsupported nonlinear load application: {integrator}")
