from .main import ElegantRunToolkit
from .sdds import SDDS
import pandas as pd
import numpy as np
import os
from io import StringIO

class misc(ElegantRunToolkit):

    def add_vary_element_from_file(self, **kwargs):
        """
        Add single vary element line, loading value from
        dataset file.
        """
        if "enumeration_file" not in kwargs.keys():
            raise RuntimeError("External filename missing.")
        else:
            self.er.commandfile.addCommand(
                "vary_element",
                name=kwargs.get("name", "*"),
                item=kwargs.get("item", "L"),
                index_number=kwargs.get("index_number", 0),
                index_limit=kwargs.get("index_limit", 1),
                enumeration_file=kwargs.get("enumeration_file"),
                enumeration_column=kwargs.get("enumeration_column"),
            )

    def use_standard_nkicks(self):
        self.er.add_alter_elements(name="*", type="CSBEND", item="N_KICKS", value=16)
        self.er.add_alter_elements(name="*", type="KQUAD", item="N_KICKS", value=8)
        self.er.add_alter_elements(name="*", type="KSEXT", item="N_KICKS", value=8)
        
        
    def findtwiss(self, **kwargs):
        """
        Run Twiss and return Twiss parameters
        together with Twiss data.

        Parameters:
        ----------
        kwargs  : dict twiss command options
        """
        # TODO: add matched = 0 case
        matched = kwargs.get("matched", 1)
        initial_optics = kwargs.get("initial_optics", [])
        alternate_element = kwargs.get("alternate_elements", {})
        closed_orbit = kwargs.get("closed_orbit", 1)

        # make sure not residual is there
        self.er.commandfile.clear()

        # add setup command
        self.er.add_run_setup()

        # add twiss calc
        self.er.commandfile.addCommand(
            "twiss_output",
            matched=matched,
            output_at_each_step=0,
            filename="%s.twi",
            radiation_integrals=1,
        )

        # add controls # TODO: not yet working for new version
        self.add_basic_controls()

        # write command file
        self.er.commandfile.write()
        self.er.commandfile.clear()

        # set cmdstr and run
        cmdstr = "{} elegant {}.ele".format(self.er.sif, self.er.rootname)
        with open(os.devnull, "w") as f:
            subp.call(shlex.split(cmdstr), stdout=f)

        # load twiss output
        twifile = SDDS(self.er.sif, "{}.twi".format(self.er.rootname), 0)
        twiparams = twifile.getParameterValues()
        twidata = twifile.getColumnValues()

        twiparams["length"] = np.round(twidata.iloc[-1]["s"], 3)

        return twidata, twiparams

    def find_matrices(self, **kwargs):
        """
        Find element by element matrix and map elements (depending on given order).
        Constant vector and R matrix are returned as numpy arrays, the maps are
        returned as dicts.

        Parameters:
        -----------
        kwargs  :
          - SDDS_output_order : order of maps (max is 3)

        Returns:
        --------
        C       : np.array constant vector
        R       : np.array R matrix
        T_dict  : dict T map Tijk as key
        Q_dict  : dict U map Qijkl as key
        """
        assert kwargs.get("SDDS_output_order", 1) < 4

        self.er.commandfile.clear()
        self.er.add_run_setup()
        self.er.commandfile.addCommand(
            "matrix_output",
            SDDS_output="%s.sdds",
            SDDS_output_order=kwargs.get("SDDS_output_order", 1),
            printout="%s.mat",
            printout_order=kwargs.get("SDDS_output_order", 1),
            full_matrix_only=kwargs.get("full_matrix_only", 0),
            individual_matrices=kwargs.get("individual_matrices", 1),
            output_at_each_step=kwargs.get("output_at_each_step", 1),
        )

        # add controls # TODO: not yet working for new version
        self.add_basic_controls()

        # write command file
        self.er.commandfile.write()
        self.er.commandfile.clear()

        # set cmdstr
        cmdstr = "{} elegant {}.ele".format(self.er.sif, self.er.rootname)
        with open(os.devnull, "w") as f:
            subp.call(shlex.split(cmdstr), stdout=f)

        with open(f"{self.er.rootname}.mat", "r") as f:
            mdata = f.read()

        # get full turn matrix and
        dfmat = pd.read_csv(
            StringIO("\n".join(mdata.split("full", 1)[1].splitlines()[1:])),
            delim_whitespace=True,
            names=[1, 2, 3, 4, 5, 6],
        )
        C = dfmat.loc[dfmat.index == "C:"].values.T
        R = dfmat.loc[dfmat.index.str.contains("R")].values
        T = dfmat.loc[dfmat.index.str.contains("T")]
        Q = dfmat.loc[dfmat.index.str.contains("Q")]

        T_dict = {}
        for _, row in T.iterrows():
            _basekey = row.name[:-1]
            for c in T.columns:
                _key = _basekey + str(c)
                _value = row[c]
                if not pd.isna(_value):
                    T_dict[_key] = _value

        Q_dict = {}
        for _, row in Q.iterrows():
            _basekey = row.name[:-1]
            for c in Q.columns:
                _key = _basekey + str(c)
                _value = row[c]
                if not pd.isna(_value):
                    Q_dict[_key] = _value

        sddsmat = SDDS(self.er.sif, f"{self.er.rootname}.sdds", 0)
        ElementMatrices = sddsmat.getColumnValues()

        return C, R, ElementMatrices, T_dict, Q_dict

    def fma(self, **kwargs):
        """
        Run Elegant frequency map analysis.
        """
        self.er.commandfile.clear()
        self.er.add_run_setup()

        self.er.commandfile.addCommand("run_control", n_passes=kwargs.pop("n_passes", 2 ** 8))
        self.er.add_twiss_output()
        self.er.add_frequency_map(**kwargs)

        self.er.run()

    def table_scan(self, scan_list_of_dicts, mode="row", add_watch_start=True, **kwargs):
        """ """
        assert mode in ["row", "table"]
        n_idx = 1 if mode == "row" else len(scan_list_of_dicts)
        self.er.commandfile.clear()
        self.er.add_run_setup()

        if add_watch_start:
            self.er.add_watch_at_start()
        self.er.commandfile.addCommand(
            "run_control", n_indices=n_idx, n_passes=kwargs.get("n_passes", 2 ** 8)
        )
        for i, l in enumerate(scan_list_of_dicts):
            if mode == "table":
                inx = i
            else:
                inx = 0
            self.er.commandfile.addCommand(
                "vary_element",
                name=l.get("name"),
                item=l.get("item"),
                initial=l.get("initial"),
                final=l.get("final"),
                index_number=inx,
                index_limit=l.get("index_limit"),
            )
        self.er.commandfile.addCommand(
            "sdds_beam", input=self.er.sdds_beam_file, input_type='"elegant"', reuse_bunch=1
        ) # TODO sdds_beam_file handling ...
        self.er.commandfile.addCommand("track")

        # run will write command file and execute it
        self.er.run()
        
        
from .main import ElegantRunToolkit
from .sdds import SDDS
import pandas as pd
import numpy as np
import os
from io import StringIO

class misc(ElegantRunToolkit):

    def add_vary_element_from_file(self, **kwargs):
        """
        Add single vary element line, loading value from
        dataset file.
        """
        if "enumeration_file" not in kwargs.keys():
            raise RuntimeError("External filename missing.")
        else:
            self.er.commandfile.addCommand(
                "vary_element",
                name=kwargs.get("name", "*"),
                item=kwargs.get("item", "L"),
                index_number=kwargs.get("index_number", 0),
                index_limit=kwargs.get("index_limit", 1),
                enumeration_file=kwargs.get("enumeration_file"),
                enumeration_column=kwargs.get("enumeration_column"),
            )

    def use_standard_nkicks(self):
        self.er.add_alter_elements(name="*", type="CSBEND", item="N_KICKS", value=16)
        self.er.add_alter_elements(name="*", type="KQUAD", item="N_KICKS", value=8)
        self.er.add_alter_elements(name="*", type="KSEXT", item="N_KICKS", value=8)
        
        
    def findtwiss(self, **kwargs):
        """
        Run Twiss and return Twiss parameters
        together with Twiss data.

        Parameters:
        ----------
        kwargs  : dict twiss command options
        """
        # TODO: add matched = 0 case
        matched = kwargs.get("matched", 1)
        initial_optics = kwargs.get("initial_optics", [])
        alternate_element = kwargs.get("alternate_elements", {})
        closed_orbit = kwargs.get("closed_orbit", 1)

        # make sure not residual is there
        self.er.commandfile.clear()

        # add setup command
        self.er.add_run_setup()

        # add twiss calc
        self.er.commandfile.addCommand(
            "twiss_output",
            matched=matched,
            output_at_each_step=0,
            filename="%s.twi",
            radiation_integrals=1,
        )

        # add controls # TODO: not yet working for new version
        self.add_basic_controls()

        # write command file
        self.er.commandfile.write()
        self.er.commandfile.clear()

        # set cmdstr and run
        cmdstr = "{} elegant {}.ele".format(self.er.sif, self.er.rootname)
        with open(os.devnull, "w") as f:
            subp.call(shlex.split(cmdstr), stdout=f)

        # load twiss output
        twifile = SDDS(self.er.sif, "{}.twi".format(self.er.rootname), 0)
        twiparams = twifile.getParameterValues()
        twidata = twifile.getColumnValues()

        twiparams["length"] = np.round(twidata.iloc[-1]["s"], 3)

        return twidata, twiparams

    def find_matrices(self, **kwargs):
        """
        Find element by element matrix and map elements (depending on given order).
        Constant vector and R matrix are returned as numpy arrays, the maps are
        returned as dicts.

        Parameters:
        -----------
        kwargs  :
          - SDDS_output_order : order of maps (max is 3)

        Returns:
        --------
        C       : np.array constant vector
        R       : np.array R matrix
        T_dict  : dict T map Tijk as key
        Q_dict  : dict U map Qijkl as key
        """
        assert kwargs.get("SDDS_output_order", 1) < 4

        self.er.commandfile.clear()
        self.er.add_run_setup()
        self.er.commandfile.addCommand(
            "matrix_output",
            SDDS_output="%s.sdds",
            SDDS_output_order=kwargs.get("SDDS_output_order", 1),
            printout="%s.mat",
            printout_order=kwargs.get("SDDS_output_order", 1),
            full_matrix_only=kwargs.get("full_matrix_only", 0),
            individual_matrices=kwargs.get("individual_matrices", 1),
            output_at_each_step=kwargs.get("output_at_each_step", 1),
        )

        # add controls # TODO: not yet working for new version
        self.add_basic_controls()

        # write command file
        self.er.commandfile.write()
        self.er.commandfile.clear()

        # set cmdstr
        cmdstr = "{} elegant {}.ele".format(self.er.sif, self.er.rootname)
        with open(os.devnull, "w") as f:
            subp.call(shlex.split(cmdstr), stdout=f)

        with open(f"{self.er.rootname}.mat", "r") as f:
            mdata = f.read()

        # get full turn matrix and
        dfmat = pd.read_csv(
            StringIO("\n".join(mdata.split("full", 1)[1].splitlines()[1:])),
            delim_whitespace=True,
            names=[1, 2, 3, 4, 5, 6],
        )
        C = dfmat.loc[dfmat.index == "C:"].values.T
        R = dfmat.loc[dfmat.index.str.contains("R")].values
        T = dfmat.loc[dfmat.index.str.contains("T")]
        Q = dfmat.loc[dfmat.index.str.contains("Q")]

        T_dict = {}
        for _, row in T.iterrows():
            _basekey = row.name[:-1]
            for c in T.columns:
                _key = _basekey + str(c)
                _value = row[c]
                if not pd.isna(_value):
                    T_dict[_key] = _value

        Q_dict = {}
        for _, row in Q.iterrows():
            _basekey = row.name[:-1]
            for c in Q.columns:
                _key = _basekey + str(c)
                _value = row[c]
                if not pd.isna(_value):
                    Q_dict[_key] = _value

        sddsmat = SDDS(self.er.sif, f"{self.er.rootname}.sdds", 0)
        ElementMatrices = sddsmat.getColumnValues()

        return C, R, ElementMatrices, T_dict, Q_dict

    def fma(self, **kwargs):
        """
        Run Elegant frequency map analysis.
        """
        self.er.commandfile.clear()
        self.er.add_run_setup()

        self.er.commandfile.addCommand("run_control", n_passes=kwargs.pop("n_passes", 2 ** 8))
        self.er.add_twiss_output()
        self.er.add_frequency_map(**kwargs)

        self.er.run()

    def table_scan(self, scan_list_of_dicts, mode="row", add_watch_start=True, **kwargs):
        """ """
        assert mode in ["row", "table"]
        n_idx = 1 if mode == "row" else len(scan_list_of_dicts)
        self.er.commandfile.clear()
        self.er.add_run_setup()

        if add_watch_start:
            self.er.add_watch_at_start()
        self.er.commandfile.addCommand(
            "run_control", n_indices=n_idx, n_passes=kwargs.get("n_passes", 2 ** 8)
        )
        for i, l in enumerate(scan_list_of_dicts):
            if mode == "table":
                inx = i
            else:
                inx = 0
            self.er.commandfile.addCommand(
                "vary_element",
                name=l.get("name"),
                item=l.get("item"),
                initial=l.get("initial"),
                final=l.get("final"),
                index_number=inx,
                index_limit=l.get("index_limit"),
            )
        self.er.commandfile.addCommand(
            "sdds_beam", input=self.er.sdds_beam_file, input_type='"elegant"', reuse_bunch=1
        ) # TODO sdds_beam_file handling ...
        self.er.commandfile.addCommand("track")

        # run will write command file and execute it
        self.er.run()
        
        
Add single vary element line, loading value from         dataset file. Run Twiss and return Twiss parameters         together with Twiss data.          Parameters:         ----------         kwargs  : dict twiss command options # TODO: add matched = 0 case # make sure not residual is there # add setup command # add twiss calc # add controls # TODO: not yet working for new version # write command file # set cmdstr and run # load twiss output Find element by element matrix and map elements (depending on given order).         Constant vector and R matrix are returned as numpy arrays, the maps are         returned as dicts.          Parameters:         -----------         kwargs  :           - SDDS_output_order : order of maps (max is 3)          Returns:         --------         C       : np.array constant vector         R       : np.array R matrix         T_dict  : dict T map Tijk as key         Q_dict  : dict U map Qijkl as key # add controls # TODO: not yet working for new version # write command file # set cmdstr # get full turn matrix and Run Elegant frequency map analysis. # TODO sdds_beam_file handling ... # run will write command file and execute it