"""
GUI support classes for the Reflectivity plug-in.
"""

from typing import Callable, Dict, List, Union

import wx.html

from genx.core.custom_logging import iprint
from genx.gui.help import rst_html
from genx.model import Model
from genx.models.lib.base import AltStrEnum
from genx.models.lib.refl_base import ReflBase as ReflBaseNew
from genx.parameters import Parameters
from genx.plugins.utils import ShowQuestionDialog, ShowWarningDialog

from . import reflectivity_images as images
from .custom_dialog import (ComplexObjectValidator, FloatObjectValidator, NoMatchValidTextObjectValidator,
                            ParameterExpressionCombo, TextObjectValidator, ValidateDialog, ValidateFitDialog,
                            ValidateFitNotebookDialog)
from .reflectivity_misc import ReflectivityModule
from .reflectivity_utils import SampleHandler, find_code_segment

_set_func_prefix = "set"


class MyHtmlListBox(wx.html.HtmlListBox):

    def __init__(self, parent, id, size=(-1, -1), style=wx.BORDER_SUNKEN):
        wx.html.HtmlListBox.__init__(self, parent, id, size=size, style=style)
        self.SetItemList(["Starting up..."])

    def SetItemList(self, html_list):
        self.html_items = html_list
        self.SetItemCount(len(html_list))
        # self.RefreshAll()
        self.Refresh()

    def OnGetItem(self, n):
        return self.html_items[n]


class ReflClassHelpDialog(wx.Frame):
    def __init__(self, parent, item: ReflBaseNew):
        wx.Frame.__init__(
            self,
            parent,
            -1,
            f"{item.__class__.__name__} Help",
            style=wx.MINIMIZE_BOX
            | wx.MAXIMIZE_BOX
            | wx.RESIZE_BORDER
            | wx.FRAME_TOOL_WINDOW
            | wx.CAPTION
            | wx.CLOSE_BOX
            | wx.CLIP_CHILDREN
            | wx.STAY_ON_TOP,
        )

        sizer = wx.BoxSizer(wx.VERTICAL)

        self.html_win = wx.html.HtmlWindow(self, -1, style=wx.NO_FULL_REPAINT_ON_RESIZE)
        sizer.Add(self.html_win, 1, flag=wx.EXPAND, border=20)

        self.SetSizer(sizer)

        sizer.Fit(self)
        self.Layout()
        size = parent.GetSize()
        pos = parent.GetPosition()
        gsize = wx.GetApp().GetTopWindow().GetSize()
        gpos = wx.GetApp().GetTopWindow().GetPosition()
        self.SetSize((600, gsize.y))
        self.SetPosition((pos.x + size.x, gpos.y))

        doc = f"{item.__class__.__name__}\n{'='*len(item.__class__.__name__)}\n\n{item.__class__.__doc__}"
        doc = rst_html(doc)
        self.html_win.SetPage(doc)
        self.html_win.SetHTMLBackgroundColour((230, 230, 255, 255))


class ReflClassEditor:
    """
    Class to handle GUI input for parameters to any ReflBase derived class.

    The class is analyzed and suitable entries are created automatically. The parameters use the value
    supplied when instantiated within the GenX model script, thus numbers can be calculated using formulae.
    """

    parent: wx.Panel
    object_name: str
    eval_func: Callable
    grid_parameters: Parameters

    instance: ReflBaseNew
    ignore_attributes: List[str]
    pars: List[str]
    par_vals: dict
    par_validators: dict
    par_editable: Dict[str, int]

    def __init__(self, parent, object_name, eval_func, grid_parameters=None, ignore_attributes=None):
        self.parent = parent
        self.object_name = object_name
        self.eval_func = eval_func
        self.grid_parameters = grid_parameters
        self.ignore_attributes = ignore_attributes or []
        self.help_dialog = None

        self.initialize()
        self.extract_values()
        self.extract_grid_params()

    def initialize(self):
        """
        Set up the necessary attributes for the analysis.
        """
        self.instance = self.eval_func(self.object_name)
        if not isinstance(self.instance, ReflBaseNew):
            raise RuntimeError(f"{self.instance} is not an instance of a ReflBase derived object")
        self.pars = []
        self.par_vals = {}
        self.par_validators = {}
        self.par_editable = {}

    def extract_values(self):
        """
        Collect information from current model needed to build the dialog window.

        Each ModelParamBase object has a ._ca dictionary that is populated during model
        compilation with a string of the parameter values given on class creation. If
        keyword was not supplied the dictionary contains the default value.
        """
        for par_name, value_info in self.instance._parameter_info().items():
            if par_name in self.ignore_attributes:
                continue
            value = self.instance._ca[par_name]
            validator = self.get_validator(value_info)
            if isinstance(value_info.type, type) and issubclass(value_info.type, AltStrEnum):
                # for AltStrEnum evaluate value string, convert to Enum and then get string representation
                value = str(value_info.type(self.eval_func(value)))
            self.pars.append(par_name)
            self.par_vals[par_name] = value
            self.par_validators[par_name] = validator

    def extract_grid_params(self):
        """
        Checks the grid for fit parameters and overwrites the dialog
        values with those.
        Also sets the editable state accordingly.
        """
        if self.grid_parameters is None:
            return
        for par_name, value_info in self.instance._parameter_info().items():
            if par_name in self.ignore_attributes:
                continue
            func_name = self.object_name + "." + _set_func_prefix + par_name.capitalize()
            if value_info.type is not complex:
                grid_value = self.grid_parameters.get_value_by_name(func_name)
                if grid_value is not None:
                    self.par_vals[par_name] = grid_value
                self.par_editable[par_name] = self.grid_parameters.get_fit_state_by_name(func_name)
            else:
                grid_value_real = self.grid_parameters.get_value_by_name(func_name + "real")
                grid_value_imag = self.grid_parameters.get_value_by_name(func_name + "imag")
                if grid_value_imag is not None or grid_value_real is not None:
                    v = self.eval_func(self.par_vals[par_name])
                    if grid_value_real is not None:
                        v = grid_value_real + v.imag * 1.0j
                    if grid_value_imag is not None:
                        v = v.real + grid_value_imag * 1.0j
                    self.par_vals[par_name] = repr(v)
                self.par_editable[par_name] = max(
                    self.grid_parameters.get_fit_state_by_name(func_name + "real"),
                    self.grid_parameters.get_fit_state_by_name(func_name + "imag"),
                )

    def ShowHelp(self, event):
        if self.help_dialog:
            return
        self.help_dialog = ReflClassHelpDialog(self.dlg, self.instance)
        self.help_dialog.Show()

    def ShowDialog(self, title=None):
        if title is None:
            title = f"{self.instance.__class__.__name__} Editor ({self.object_name})"

        groups = self.instance.Groups
        units = getattr(self.instance, "Units", False)

        self.dlg = ValidateFitDialog(
            self.parent,
            self.pars,
            self.par_vals,
            self.par_validators,
            title=title,
            groups=groups,
            units=units,
            editable_pars=self.par_editable,
        )

        if self.instance.__class__.__doc__ != "":
            help_btn = wx.Button(self.dlg, -1, "Show Help")
            help_btn.SetBackgroundColour((230, 230, 255, 255))
            # dlg.border_sizer.InsertSpacer(0, 2)
            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.AddStretchSpacer(1)
            sizer.Add(help_btn, 0, wx.FIXED_MINSIZE, 0)
            self.dlg.border_sizer.Insert(0, sizer, 0, wx.ALIGN_RIGHT)
            self.dlg.Fit()
            self.dlg.Bind(wx.EVT_BUTTON, self.ShowHelp, help_btn)

        res = self.dlg.ShowModal()
        if res == wx.ID_OK:
            vals = self.dlg.GetValues()
            states = self.dlg.GetStates()
            self.update_object(vals, states)
            self.update_grid(vals, states)
        if self.help_dialog:
            self.help_dialog.Destroy()
        self.dlg.Destroy()
        return res == wx.ID_OK

    def update_object(self, vals, states):
        for name, value_info in self.instance._parameter_info().items():
            if name not in self.pars:
                continue
            if not states[name]:
                # only update parameters in the script that are not set in the grid
                orig_type = value_info.type
                if getattr(orig_type, "__origin__", None) is Union:
                    e_value = self.eval_func(vals[name])
                    success = False
                    for subtype in orig_type.__args__:
                        try:
                            setattr(self.instance, name, subtype(e_value))
                        except (ValueError, TypeError):
                            continue
                        else:
                            self.instance._ca[name] = str(vals[name])
                    return
                elif orig_type is str or issubclass(orig_type, AltStrEnum):
                    e_value = vals[name]
                    self.instance._ca[name] = repr(vals[name])
                else:
                    e_value = self.eval_func(vals[name])
                    self.instance._ca[name] = str(vals[name])
                setattr(self.instance, name, orig_type(e_value))

    def update_grid(self, vals, states):
        vspan = self.parent.variable_span
        for name, value_info in self.instance._parameter_info().items():
            if name not in self.pars:
                continue
            if states[name] and self.par_editable[name] != 3 and self.par_editable[name] != states[name]:
                value = self.eval_func(vals[name])
                # if the state of a parameter is changed to fit or set in grid, update the grid line
                if value_info.type is complex:
                    func_name = self.object_name + "." + _set_func_prefix + name.capitalize() + "real"
                    val = value.real
                    minval = min(val * (1 - vspan), val * (1 + vspan))
                    maxval = max(val * (1 - vspan), val * (1 + vspan))
                    self.grid_parameters.set_fit_state_by_name(func_name, val, states[name], minval, maxval)
                    val = value.imag
                    minval = min(val * (1 - vspan), val * (1 + vspan))
                    maxval = max(val * (1 - vspan), val * (1 + vspan))
                    func_name = self.object_name + "." + _set_func_prefix + name.capitalize() + "imag"
                    self.grid_parameters.set_fit_state_by_name(func_name, val, states[name], minval, maxval)
                else:
                    val = value
                    minval = min(val * (1 - vspan), val * (1 + vspan))
                    maxval = max(val * (1 - vspan), val * (1 + vspan))
                    func_name = self.object_name + "." + _set_func_prefix + name.capitalize()
                    self.grid_parameters.set_fit_state_by_name(func_name, value, states[name], minval, maxval)
            elif self.par_editable[name] and not states[name]:
                # remove fit parameter from grid as is changed to set in script
                if value_info.type is complex or complex in getattr(value_info.type, "__args__", ()):
                    func_name = self.object_name + "." + _set_func_prefix + name.capitalize() + "real"
                    self.grid_parameters.set_fit_state_by_name(func_name, 0.0, 0, 0, 0)
                    func_name = self.object_name + "." + _set_func_prefix + name.capitalize() + "imag"
                    self.grid_parameters.set_fit_state_by_name(func_name, 0.0, 0, 0, 0)
                else:
                    func_name = self.object_name + "." + _set_func_prefix + name.capitalize()
                    self.grid_parameters.set_fit_state_by_name(func_name, 0.0, 0, 0, 0)

    def get_validator(self, value_info):
        if isinstance(value_info.type, type) and issubclass(value_info.type, AltStrEnum):
            validator = [i.value for i in value_info.type]
        elif value_info.type is complex or complex in getattr(value_info.type, "__args__", ()):
            validator = ComplexObjectValidator(eval_func=self.eval_func)
        elif value_info.type is bool:
            validator = ["True", "False"]
        else:
            validator = FloatObjectValidator(eval_func=self.eval_func)
        return validator


class SamplePanel(wx.Panel):
    sampleh: SampleHandler
    model: ReflectivityModule

    def __init__(self, parent, plugin, refindexlist=None):
        wx.Panel.__init__(self, parent)
        if refindexlist is None:
            refindexlist = []
        self.refindexlist = refindexlist
        self.plugin = plugin
        self.variable_span = 0.25

        # Colours indicating different states
        # Green wx.Colour(138, 226, 52), ORANGE wx.Colour(245, 121, 0)
        self.fit_colour = (245, 121, 0)
        # Tango Sky blue wx.Colour(52, 101, 164), wx.Colour(114, 159, 207)
        self.const_fit_colour = (114, 159, 207)

        boxver = wx.BoxSizer(wx.HORIZONTAL)
        boxhor = wx.BoxSizer(wx.VERTICAL)
        self.toolbar = wx.ToolBar(self, style=wx.TB_FLAT | wx.TB_HORIZONTAL)
        self.do_toolbar()
        boxhor.Add(self.toolbar, proportion=0, flag=wx.EXPAND)
        boxhor.AddSpacer(2)
        self.listbox = MyHtmlListBox(self, -1, style=wx.BORDER_SUNKEN)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.lbDoubleClick, self.listbox)
        boxhor.Add(self.listbox, 1, wx.EXPAND)

        boxver.Add(boxhor, 1, wx.EXPAND)

        self.SetSizer(boxver)
        self.toolbar.Realize()
        self.update_callback = lambda event: ""

    def do_toolbar(self):
        dpi_scale_factor = wx.GetApp().dpi_scale_factor
        tb_bmp_size = int(dpi_scale_factor * 20)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Insert Layer",
            bitmap=wx.Bitmap(images.insert_layer.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Insert a Layer",
        )
        self.Bind(wx.EVT_TOOL, self.InsertLay, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Insert Stack",
            bitmap=wx.Bitmap(images.insert_stack.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Insert a Stack",
        )
        self.Bind(wx.EVT_TOOL, self.InsertStack, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Delete",
            bitmap=wx.Bitmap(images.delete.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Delete item",
        )
        self.Bind(wx.EVT_TOOL, self.DeleteSample, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Rename",
            bitmap=wx.Bitmap(images.change_name.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Rename",
        )
        self.Bind(wx.EVT_TOOL, self.ChangeName, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Move up",
            bitmap=wx.Bitmap(images.move_up.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Move item up",
        )
        self.Bind(wx.EVT_TOOL, self.MoveUp, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Move down",
            bitmap=wx.Bitmap(images.move_down.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Move item down",
        )
        self.Bind(wx.EVT_TOOL, self.MoveDown, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Edit Sample",
            bitmap=wx.Bitmap(images.sample.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Edit Sample parameters",
        )
        self.Bind(wx.EVT_TOOL, self.EditSampleParameters, id=newid)

        newid = wx.NewId()
        self.toolbar.AddTool(
            newid,
            "Edit Instrument",
            bitmap=wx.Bitmap(images.instrument.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            shortHelp="Edit Instruments",
        )
        self.Bind(wx.EVT_TOOL, self.EditInstrument, id=newid)

    def SetUpdateCallback(self, func):
        """SetUpdateCallback(self, func) --> None

        Sets the update callback will be called when the sample is updated.
        The call is on the form func(event)
        """
        self.update_callback = func

    def set_sampleh(self, sampleh: SampleHandler):
        self.sampleh = sampleh

    def set_model(self, model: ReflectivityModule):
        self.model = model

    def create_html_decorator(self):
        """
        creates a html decorator function
        :return:
        """
        grid_parameters = self.plugin.GetModel().get_parameters()
        dic_lookup = {}
        for par in grid_parameters.get_names():
            l = par.split(".")
            if len(l) == 2:
                name = l[0]
                par_name = l[1][3:].lower()
                dic_lookup[(name, par_name)] = (
                    grid_parameters.get_value_by_name(par),
                    grid_parameters.get_fit_state_by_name(par),
                )
        fit_color_str = "rgb(%d,%d,%d)" % self.fit_colour
        const_fit_color_str = "rgb(%d,%d,%d)" % self.const_fit_colour

        def decorator(name, str):
            """Decorator to indicate the parameters that are fitted"""
            try:
                start_index = str.index("(") + 1
            except ValueError:
                start_index = 0
            ret_str = str[:start_index]
            for par_str in str[start_index:].split(","):
                par_name = par_str.split("=")[0].strip()
                # par_name normal parameter (real number)
                if (name, par_name) in dic_lookup:
                    val, state = dic_lookup[(name, par_name)]
                    if state == 1:
                        par_str = " <font color=%s><b>%s=%.2e</b></font>," % (fit_color_str, par_name, val)
                    elif state == 2:
                        par_str = " <font color=%s><b>%s=%.2e</b></font>," % (const_fit_color_str, par_name, val)
                # par_name is a complex parameter...
                elif (name, par_name + "real") in dic_lookup or (name, par_name + "imag") in dic_lookup:
                    if (name, par_name + "real") in dic_lookup:
                        val, state = dic_lookup[(name, par_name + "real")]
                        if state == 1:
                            par_str = " <font color=%s><b>%s=(%.2e,</b></font>" % (fit_color_str, par_name, val)
                        elif state == 2:
                            par_str = " <font color=%s><b>%s=(%.2e,</b></font>" % (const_fit_color_str, par_name, val)
                    else:
                        par_str = " <b>%s=??+</b>" % par_name
                    if (name, par_name + "imag") in dic_lookup:
                        val, state = dic_lookup[(name, par_name + "imag")]
                        if state == 1:
                            par_str += " <font color=%s><b>%.2e)</b></font>," % (fit_color_str, val)
                        elif state == 2:
                            par_str += " <font color=%s><b>%.2e)</b></font>," % (const_fit_color_str, val)
                    else:
                        par_str += " <b>??)</b>,"

                else:
                    par_str += ","
                ret_str += par_str
            # Remove trailing ,
            if ret_str[-1] == ",":
                ret_str = ret_str[:-1]
            if str[-1] == ")" and ret_str[-1] != ")":
                ret_str += ")"
            return ret_str

        return decorator

    def Update(self, update_script=True):
        deco = self.create_html_decorator()
        sl = self.sampleh.getStringList(html_encoding=True, html_decorator=deco)
        self.listbox.SetItemList(sl)
        if update_script:
            self.update_callback(None)

    def SetSample(self, sample, names):
        self.sampleh.sample = sample
        self.sampleh.names = names
        self.Update()

    def EditSampleParameters(self, evt):
        """Event handler that creates a dialog box to edit the sample parameters.

        :param evt:
        :return: Nothing
        """
        obj_name = "sample"
        eval_func = self.plugin.GetModel().eval_in_model

        if isinstance(self.sampleh.sample, ReflBaseNew):
            grid_parameters = self.plugin.GetModel().get_parameters()
            editor = ReflClassEditor(
                self, obj_name, eval_func, grid_parameters, ignore_attributes=["Ambient", "Substrate", "Stacks"]
            )
            if editor.ShowDialog():
                self.plugin.parent.paramter_grid.SetParameters(grid_parameters)
                self.sampleh.sample = self.plugin.GetModel().script_module.sample

                self.Update()
            return

        editable, grid_parameters, groups, pars, units, validators, vals = self.ExtractSampleParameters(obj_name)

        dlg = ValidateFitDialog(
            self, pars, vals, validators, title="Sample Editor", groups=groups, units=units, editable_pars=editable
        )

        if dlg.ShowModal() == wx.ID_OK:
            old_vals = vals
            vals = dlg.GetValues()
            # print vals
            states = dlg.GetStates()
            for par in pars:
                if not states[par]:
                    old_type = type(old_vals[par])
                    if isinstance(self.sampleh.sample, ReflBaseNew):
                        self.sampleh.sample._ca[par] = vals[par]
                        setattr(self.sampleh.sample, par, old_type(vals[par]))
                    else:
                        setattr(self.sampleh.sample, par, old_type(vals[par]))
                if editable[par] != states[par]:
                    value = eval_func(vals[par])
                    minval = min(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    maxval = max(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    func_name = obj_name + "." + _set_func_prefix + par.capitalize()
                    grid_parameters.set_fit_state_by_name(func_name, value, states[par], minval, maxval)
                    # Tell the grid to reload the parameters
                    self.plugin.parent.paramter_grid.SetParameters(grid_parameters)

            self.Update()

        dlg.Destroy()

    def ExtractSampleParameters(self, obj_name):
        grid_parameters = self.plugin.GetModel().get_parameters()
        validators = {}
        vals = {}
        pars = []
        items = []
        editable = {}
        try:
            string_choices = self.model.sample_string_choices
        except Exception as e:
            string_choices = {}
        for item in self.model.SampleParameters:
            if item != "Stacks" and item != "Substrate" and item != "Ambient":
                if item in string_choices:
                    validators[item] = string_choices[item]
                else:
                    validators[item] = FloatObjectValidator()
                val = getattr(self.sampleh.sample, item)
                vals[item] = val
                pars.append(item)
                items.append((item, val))
                # Check if the parameter is in the grid and in that case set it as uneditable
                func_name = obj_name + "." + _set_func_prefix + item.capitalize()
                grid_value = grid_parameters.get_value_by_name(func_name)
                editable[item] = grid_parameters.get_fit_state_by_name(func_name)
                if grid_value is not None:
                    vals[item] = grid_value
        try:
            groups = self.model.SampleGroups
        except Exception:
            groups = False
        try:
            units = self.model.SampleUnits
        except Exception:
            units = False
        return editable, grid_parameters, groups, pars, units, validators, vals

    def SetInstrument(self, instruments):
        """SetInstrument(self, instrument) --> None

        Sets the instruments should be a dictionary of instruments with the key being the
        name of the instrument
        """
        self.instruments = instruments

    def EditInstrument(self, evt):
        """Event handler that creates a dialog box to edit the instruments.

        :param evt:
        :return: Nothing
        """
        eval_func = self.plugin.GetModel().eval_in_model
        editable, grid_parameters, model_inst_params, pars, validators, vals, groups, units = (
            self.ExtractInstrumentParams()
        )

        old_insts = []
        for inst_name in self.instruments:
            old_insts.append(inst_name)

        dlg = ValidateFitNotebookDialog(
            self,
            pars,
            vals,
            validators,
            title="Instrument Editor",
            groups=groups,
            units=units,
            fixed_pages=["inst"],
            editable_pars=editable,
        )

        if isinstance(self.sampleh.sample, ReflBaseNew):

            def show_help(event):
                hlp = ReflClassHelpDialog(dlg, self.sampleh.model.Instrument())
                hlp.Show()

            help_btn = wx.Button(dlg, -1, "Show Help")
            help_btn.SetBackgroundColour((230, 230, 255, 255))
            # dlg.border_sizer.InsertSpacer(0, 2)
            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.AddStretchSpacer(1)
            sizer.Add(help_btn, 0, wx.FIXED_MINSIZE, 0)
            dlg.border_sizer.Insert(0, sizer, 0, wx.ALIGN_RIGHT)
            dlg.Fit()
            dlg.Bind(wx.EVT_BUTTON, show_help, help_btn)

        if dlg.ShowModal() == wx.ID_OK:
            old_vals = vals
            vals = dlg.GetValues()
            # print vals
            states = dlg.GetStates()
            self.UpdateInstrumentConfiguration(
                editable, eval_func, grid_parameters, model_inst_params, old_vals, states, vals
            )

            # Loop to remove instrument from grid if not returned from Dialog
            for inst_name in old_insts:
                if inst_name not in list(vals.keys()):
                    for par in pars:
                        if editable[inst_name][par] > 0:
                            func_name = inst_name + "." + _set_func_prefix + par.capitalize()
                            grid_parameters.set_fit_state_by_name(func_name, 0, 0, 0, 0)

            # Tell the grid to reload the parameters
            self.plugin.parent.paramter_grid.SetParameters(grid_parameters)

            for change in dlg.GetChanges():
                if change[0] != "" and change[1] != "":
                    self.plugin.InstrumentNameChange(change[0], change[1])
                elif change[1] == "":
                    self.plugin.InstrumentNameChange(change[0], "inst")

            self.Update()
        else:
            pass
        dlg.Destroy()

    def UpdateInstrumentConfiguration(
        self, editable, eval_func, grid_parameters, model_inst_params, old_vals, states, vals
    ):
        if isinstance(self.sampleh.sample, ReflBaseNew):
            return self.UpdateInstrumentConfigurationNew(
                editable, eval_func, grid_parameters, model_inst_params, old_vals, states, vals
            )
        self.instruments = {}
        for inst_name in vals:
            new_instrument = False
            if inst_name not in self.instruments:
                # A new instrument must be created:
                self.instruments[inst_name] = self.model.Instrument()
                new_instrument = True
            for par in self.model.InstrumentParameters:
                if not states[inst_name][par]:
                    orig_type = type(model_inst_params[par])
                    if orig_type is str:
                        e_value = vals[inst_name][par]
                    else:
                        e_value = eval_func(vals[inst_name][par])
                    setattr(self.instruments[inst_name], par, orig_type(e_value))
                else:
                    setattr(self.instruments[inst_name], par, old_vals[inst_name][par])
                if new_instrument and states[inst_name][par] > 0:
                    value = eval_func(vals[inst_name][par])
                    minval = min(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    maxval = max(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    func_name = inst_name + "." + _set_func_prefix + par.capitalize()
                    grid_parameters.set_fit_state_by_name(func_name, value, states[inst_name][par], minval, maxval)
                elif not new_instrument:
                    if editable[inst_name][par] != states[inst_name][par]:
                        value = eval_func(vals[inst_name][par])
                        minval = min(value * (1 - self.variable_span), value * (1 + self.variable_span))
                        maxval = max(value * (1 - self.variable_span), value * (1 + self.variable_span))
                        func_name = inst_name + "." + _set_func_prefix + par.capitalize()
                        grid_parameters.set_fit_state_by_name(func_name, value, states[inst_name][par], minval, maxval)

    def UpdateInstrumentConfigurationNew(
        self, editable, eval_func, grid_parameters, model_inst_params, old_vals, states, vals
    ):
        self.instruments = {}
        for inst_name in vals:
            new_instrument = False
            if inst_name not in self.instruments:
                # A new instrument must be created:
                self.instruments[inst_name] = self.model.Instrument()
                new_instrument = True
            inst = self.instruments[inst_name]
            for name, value_info in inst._parameter_info().items():
                if not states[inst_name][name]:
                    orig_type = value_info.type
                    if orig_type is str or issubclass(orig_type, AltStrEnum):
                        e_value = vals[inst_name][name]
                    else:
                        e_value = eval_func(vals[inst_name][name])
                    setattr(self.instruments[inst_name], name, orig_type(e_value))
                elif states[inst_name][name] != 3:
                    setattr(self.instruments[inst_name], name, old_vals[inst_name][name])
                if states[inst_name][name] == 3:
                    # ignore attribute overwritten in script and replace by default
                    orig_type = value_info.type
                    e_value = getattr(inst, name)
                    setattr(self.instruments[inst_name], name, orig_type(e_value))
                elif new_instrument and states[inst_name][name] > 0:
                    value = eval_func(vals[inst_name][name])
                    minval = min(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    maxval = max(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    func_name = inst_name + "." + _set_func_prefix + name.capitalize()
                    grid_parameters.set_fit_state_by_name(func_name, value, states[inst_name][name], minval, maxval)
                elif not new_instrument:
                    if editable[inst_name][name] != states[inst_name][name]:
                        value = eval_func(vals[inst_name][name])
                        minval = min(value * (1 - self.variable_span), value * (1 + self.variable_span))
                        maxval = max(value * (1 - self.variable_span), value * (1 + self.variable_span))
                        func_name = inst_name + "." + _set_func_prefix + name.capitalize()
                        grid_parameters.set_fit_state_by_name(func_name, value, states[inst_name][name], minval, maxval)

    def ExtractInstrumentParams(self):
        if isinstance(self.sampleh.sample, ReflBaseNew):
            return self.ExtractInstrumentParamsNew()
        validators = {}
        vals = {}
        editable = {}
        grid_parameters = self.plugin.GetModel().get_parameters()
        for inst_name in self.instruments:
            vals[inst_name] = {}
            editable[inst_name] = {}
        pars = []
        model_inst_params = self.model.InstrumentParameters
        for item in model_inst_params:
            if item in self.model.instrument_string_choices:
                # validators.append(self.model.instrument_string_choices[item])
                validators[item] = self.model.instrument_string_choices[item]
            else:
                # validators.append(FloatObjectValidator())
                validators[item] = FloatObjectValidator()
            for inst_name in self.instruments:
                val = getattr(self.instruments[inst_name], item)
                vals[inst_name][item] = val
                # Check if the parameter is in the grid and in that case set it as uneditable
                func_name = inst_name + "." + _set_func_prefix + item.capitalize()
                grid_value = grid_parameters.get_value_by_name(func_name)
                editable[inst_name][item] = grid_parameters.get_fit_state_by_name(func_name)
                if grid_value is not None:
                    vals[inst_name][item] = grid_value
            pars.append(item)

        try:
            groups = self.model.InstrumentGroups
        except Exception:
            groups = False
        try:
            units = self.model.InstrumentUnits
        except Exception:
            units = False

        return editable, grid_parameters, model_inst_params, pars, validators, vals, groups, units

    def ExtractInstrumentParamsNew(self):
        validators = {}
        vals = {}
        editable = {}
        grid_parameters = self.plugin.GetModel().get_parameters()
        pars = []
        groups = []
        units = []
        for inst_name in self.instruments:
            inst = self.instruments[inst_name]
            vals[inst_name] = {}
            editable[inst_name] = {}
            model_inst_params = []
            groups = inst.Groups
            units = getattr(inst, "Units", [])
            for name, value_info in inst._parameter_info().items():
                val = getattr(self.instruments[inst_name], name)
                val, validator = self.get_validator(val, value_info)
                model_inst_params.append(name)
                vals[inst_name][name] = val
                validators[name] = validator
                # Check if the parameter is in the grid and in that case set it as uneditable
                if val == "set in script":
                    editable[inst_name][name] = 3
                else:
                    func_name = inst_name + "." + _set_func_prefix + name.capitalize()
                    grid_value = grid_parameters.get_value_by_name(func_name)
                    editable[inst_name][name] = grid_parameters.get_fit_state_by_name(func_name)
                    if grid_value is not None:
                        vals[inst_name][name] = grid_value
                    pars.append(name)

        return editable, grid_parameters, model_inst_params, pars, validators, vals, groups, units

    def get_validator(self, val, value_info, eval_func=eval):
        try:
            val = value_info.type(val)
        except Exception:
            pass
        if issubclass(value_info.type, AltStrEnum):
            validator = [i.value for i in value_info.type]
            val = str(val)
        elif not isinstance(val, value_info.type):
            # current value does not correspond to type, should have been converted on class creation
            val = "set in script"
            validator = ["set in script"]
        elif value_info.type is complex or complex in getattr(value_info.type, "__args__", ()):
            validator = ComplexObjectValidator(eval_func=eval_func)
        elif value_info.type is bool:
            validator = ["True", "False"]
            val = repr(val)
        else:
            validator = FloatObjectValidator(eval_func=eval_func)
        return val, validator

    def MoveUp(self, evt):
        sl = self.sampleh.moveUp(self.listbox.GetSelection())
        if sl:
            self.Update()
            self.listbox.SetSelection(self.listbox.GetSelection() - 1)

    def MoveDown(self, evt):
        sl = self.sampleh.moveDown(self.listbox.GetSelection())
        if sl:
            self.Update()
            self.listbox.SetSelection(self.listbox.GetSelection() + 1)

    def InsertStack(self, evt):
        # Create Dialog box
        validators = {"Name": NoMatchValidTextObjectValidator(self.sampleh.names)}
        vals = {}
        pars = ["Name"]
        vals["Name"] = "name"
        dlg = ValidateDialog(self, pars, vals, validators, title="Give Stack Name")

        # Show the dialog
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
        dlg.Destroy()
        # if not a value is selected operate on first
        pos = max(self.listbox.GetSelection(), 0)
        sl = self.sampleh.insertItem(pos, "Stack", vals["Name"])
        if sl:
            self.Update()
        else:
            self.plugin.ShowWarningDialog("Can not insert a stack at the" " current position.")

    def InsertLay(self, evt):
        # Create Dialog box
        # items = [('Name', 'name')]
        # validators = [NoMatchValidTextObjectValidator(self.sampleh.names)]
        dlg = ValidateDialog(
            self,
            ["Name"],
            {"Name": "name"},
            {"Name": NoMatchValidTextObjectValidator(self.sampleh.names)},
            title="Give Layer Name",
        )
        # Show the dialog
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
        else:
            vals = {"Name": "name"}
        dlg.Destroy()
        # if not a value is selected operate on first
        pos = max(self.listbox.GetSelection(), 0)
        # Create the Layer
        sl = self.sampleh.insertItem(pos, "Layer", vals["Name"])
        if sl:
            self.Update()
        else:
            self.plugin.ShowWarningDialog(
                "Can not insert a layer at the" " current position. Layers has to be part of a stack."
            )

    def DeleteSample(self, evt):
        slold = self.sampleh.getStringList()
        sl = self.sampleh.deleteItem(self.listbox.GetSelection())
        if sl:
            self.Update()

    def ChangeName(self, evt):
        """Change the name of the current selected item."""
        pos = self.listbox.GetSelection()
        if pos == 0 or pos == len(self.sampleh.names) - 1:
            self.plugin.ShowInfoDialog(
                "It is forbidden to change the" "name of the substrate (Sub) and the Ambient (Amb) layers."
            )
        else:
            unallowed_names = self.sampleh.names[:pos] + self.sampleh.names[max(0, pos - 1) :]
            dlg = ValidateDialog(
                self,
                ["Name"],
                {"Name": self.sampleh.names[pos]},
                {"Name": NoMatchValidTextObjectValidator(unallowed_names)},
                title="Give New Name",
            )

            if dlg.ShowModal() == wx.ID_OK:
                vals = dlg.GetValues()
                result = self.sampleh.changeName(pos, vals["Name"])
                if result:
                    self.Update()
                else:
                    iprint("Unexpected problems when changing name...")
            dlg.Destroy()

    def lbDoubleClick(self, evt):
        sel = self.sampleh.getItem(self.listbox.GetSelection())
        if isinstance(sel, self.model.Layer):
            sl = self.EditLayer(sel)
        else:
            sl = self.EditStack(sel)

        if sl:
            self.Update()

    def EditStack(self, sel):
        if isinstance(sel, ReflBaseNew):
            return self.EditStackNew()
        obj_name = self.sampleh.getName(self.listbox.GetSelection())
        eval_func = self.plugin.GetModel().eval_in_model
        sl = None
        items = []
        validators = {}
        vals = {}
        pars = []
        editable = {}
        grid_parameters = self.plugin.GetModel().get_parameters()
        # The selected item is a Stack
        if isinstance(sel, ReflBaseNew):
            for name, value_info in sel._parameter_info().items():
                if name != "Layers":
                    value = getattr(sel, name)
                    if value_info.type in [int, float]:
                        validators[name] = FloatObjectValidator(eval_func, alt_types=[self.model.Stack])
                    else:
                        validators[name] = TextObjectValidator()
                    items.append((name, value))
                    pars.append(name)
                    vals[name] = value

                    # Check if the parameter is in the grid and in that case set it as uneditable
                    func_name = obj_name + "." + _set_func_prefix + name.capitalize()
                    grid_value = grid_parameters.get_value_by_name(func_name)
                    editable[name] = grid_parameters.get_fit_state_by_name(func_name)
                    if grid_value is not None:
                        vals[name] = grid_value

            try:
                groups = self.model.StackGroups
            except Exception:
                groups = False
            try:
                units = self.model.StackUnits
            except Exception:
                units = False
        else:
            for item in list(self.model.StackParameters.keys()):
                if item != "Layers":
                    value = getattr(sel, item)
                    if isinstance(value, float):
                        validators[item] = FloatObjectValidator(eval_func, alt_types=[self.model.Stack])
                    else:
                        validators[item] = TextObjectValidator()
                    items.append((item, value))
                    pars.append(item)
                    vals[item] = value

                    # Check if the parameter is in the grid and in that case set it as uneditable
                    func_name = obj_name + "." + _set_func_prefix + item.capitalize()
                    grid_value = grid_parameters.get_value_by_name(func_name)
                    editable[item] = grid_parameters.get_fit_state_by_name(func_name)
                    if grid_value is not None:
                        vals[item] = grid_value

            try:
                groups = self.model.StackGroups
            except Exception:
                groups = False
            try:
                units = self.model.StackUnits
            except Exception:
                units = False
        dlg = ValidateFitDialog(
            self, pars, vals, validators, title="Layer Editor", groups=groups, units=units, editable_pars=editable
        )
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
            states = dlg.GetStates()
            for par in pars:
                if not states[par]:
                    setattr(sel, par, vals[par])
                if editable[par] != states[par]:
                    value = eval_func(vals[par])
                    minval = min(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    maxval = max(value * (1 - self.variable_span), value * (1 + self.variable_span))
                    func_name = obj_name + "." + _set_func_prefix + par.capitalize()
                    grid_parameters.set_fit_state_by_name(func_name, value, states[par], minval, maxval)
                    # Does not seem to be necessary
                    self.plugin.parent.paramter_grid.SetParameters(grid_parameters)
            sl = self.sampleh.getStringList()
        dlg.Destroy()
        return sl

    def EditStackNew(self):
        obj_name = self.sampleh.getName(self.listbox.GetSelection())
        eval_func = self.plugin.GetModel().eval_in_model
        sl = None
        grid_parameters = self.plugin.GetModel().get_parameters()
        editor = ReflClassEditor(self, obj_name, eval_func, grid_parameters, ignore_attributes=["Layers"])
        if editor.ShowDialog():
            self.plugin.parent.paramter_grid.SetParameters(grid_parameters)
            self.sampleh.sample = self.plugin.GetModel().script_module.sample
            sl = self.sampleh.getStringListNew()
        return sl

    def EditLayer(self, sel):
        if isinstance(sel, ReflBaseNew):
            return self.EditLayerNew()
        obj_name = self.sampleh.getName(self.listbox.GetSelection())
        eval_func = self.plugin.GetModel().eval_in_model
        sl = None
        items = []
        validators = {}
        vals = {}
        pars = []
        editable = {}
        grid_parameters = self.plugin.GetModel().get_parameters()
        # The selected item is a Layer
        for item in self.model.LayerParameters.keys():
            value = getattr(sel, item)
            vals[item] = value
            # if item!='n' and item!='fb':
            if type(self.model.LayerParameters[item]) != type(1 + 1.0j):
                # Handle real parameters
                validators[item] = FloatObjectValidator(eval_func, alt_types=[self.model.Layer])
                func_name = obj_name + "." + _set_func_prefix + item.capitalize()
                grid_value = grid_parameters.get_value_by_name(func_name)
                if grid_value is not None:
                    vals[item] = grid_value
                editable[item] = grid_parameters.get_fit_state_by_name(func_name)

            else:
                # Handle complex parameters
                validators[item] = ComplexObjectValidator(eval_func, alt_types=[self.model.Layer])
                func_name = obj_name + "." + _set_func_prefix + item.capitalize()
                grid_value_real = grid_parameters.get_value_by_name(func_name + "real")
                grid_value_imag = grid_parameters.get_value_by_name(func_name + "imag")
                if grid_value_real is not None:
                    v = eval_func(vals[item]) if type(vals[item]) is str else vals[item]
                    vals[item] = grid_value_real + v.imag * 1.0j
                if grid_value_imag is not None:
                    v = eval_func(vals[item]) if type(vals[item]) is str else vals[item]
                    vals[item] = v.real + grid_value_imag * 1.0j
                editable[item] = max(
                    grid_parameters.get_fit_state_by_name(func_name + "real"),
                    grid_parameters.get_fit_state_by_name(func_name + "imag"),
                )

            items.append((item, value))
            pars.append(item)

            # Check if the parameter is in the grid and in that case set it as uneditable
            # func_name = obj_name + '.' + _set_func_prefix + item.capitalize()
            # grid_value = grid_parameters.get_value_by_name(func_name)
            # editable[item] = grid_parameters.get_fit_state_by_name(func_name)

        try:
            groups = self.model.LayerGroups
        except Exception:
            groups = False
        try:
            units = self.model.LayerUnits
        except Exception:
            units = False
        dlg = ValidateFitDialog(
            self, pars, vals, validators, title="Layer Editor", groups=groups, units=units, editable_pars=editable
        )
        if dlg.ShowModal() == wx.ID_OK:
            vals = dlg.GetValues()
            states = dlg.GetStates()
            for par in pars:
                if not states[par]:
                    setattr(sel, par, vals[par])
                if editable[par] != states[par]:
                    value = eval_func(vals[par])

                    if type(value) is complex:
                        # print type(value)
                        func_name = obj_name + "." + _set_func_prefix + par.capitalize() + "real"
                        val = value.real
                        minval = min(val * (1 - self.variable_span), val * (1 + self.variable_span))
                        maxval = max(val * (1 - self.variable_span), val * (1 + self.variable_span))
                        grid_parameters.set_fit_state_by_name(func_name, val, states[par], minval, maxval)
                        val = value.imag
                        minval = min(val * (1 - self.variable_span), val * (1 + self.variable_span))
                        maxval = max(val * (1 - self.variable_span), val * (1 + self.variable_span))
                        func_name = obj_name + "." + _set_func_prefix + par.capitalize() + "imag"
                        grid_parameters.set_fit_state_by_name(func_name, val, states[par], minval, maxval)
                    else:
                        val = value
                        minval = min(val * (1 - self.variable_span), val * (1 + self.variable_span))
                        maxval = max(val * (1 - self.variable_span), val * (1 + self.variable_span))
                        func_name = obj_name + "." + _set_func_prefix + par.capitalize()
                        grid_parameters.set_fit_state_by_name(func_name, value, states[par], minval, maxval)

                    # Does not seem to be necessary
                    self.plugin.parent.paramter_grid.SetParameters(grid_parameters)
            sl = self.sampleh.getStringList()
        dlg.Destroy()
        return sl

    def EditLayerNew(self):
        obj_name = self.sampleh.getName(self.listbox.GetSelection())
        eval_func = self.plugin.GetModel().eval_in_model
        sl = None
        grid_parameters = self.plugin.GetModel().get_parameters()
        editor = ReflClassEditor(self, obj_name, eval_func, grid_parameters)
        if editor.ShowDialog():
            self.plugin.parent.paramter_grid.SetParameters(grid_parameters)
            self.sampleh.sample = self.plugin.GetModel().script_module.sample
            sl = self.sampleh.getStringListNew()
        return sl


class DataParameterPanel(wx.Panel):
    """Widget that defines parameters coupling and different parameters
    for different data sets.
    """

    def __init__(self, parent, plugin):
        wx.Panel.__init__(self, parent)
        self.plugin = plugin
        boxver = wx.BoxSizer(wx.VERTICAL)
        # Indention for a command - used to separate commands and data
        self.command_indent = "<pre>   "
        self.script_update_func = None
        self.parameterlist = []

        self.toolbar = wx.ToolBar(self, style=wx.TB_FLAT | wx.TB_HORIZONTAL)
        self.do_toolbar()
        boxver.Add(self.toolbar, proportion=0, flag=wx.EXPAND, border=1)
        boxver.AddSpacer(4)

        self.listbox = MyHtmlListBox(self, -1, style=wx.BORDER_SUNKEN)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.Edit, self.listbox)
        boxver.Add(self.listbox, 1, wx.EXPAND)

        self.SetSizer(boxver)

    def do_toolbar(self):
        dpi_scale_factor = wx.GetApp().dpi_scale_factor
        tb_bmp_size = int(dpi_scale_factor * 20)

        button_names = ["Insert", "Delete", "User Variables"]
        button_images = [
            wx.Bitmap(images.add.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(images.delete.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(images.custom_parameters.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
        ]
        callbacks = [self.Insert, self.Delete, self.EditPars]
        tooltips = ["Insert a command", "Delete command", "Edit user variables"]

        for i in range(len(button_names)):
            newid = wx.NewId()
            self.toolbar.AddTool(newid, label=button_names[i], bitmap=button_images[i], shortHelp=tooltips[i])
            self.Bind(wx.EVT_TOOL, callbacks[i], id=newid)
        self.toolbar.Realize()

    def onsimulate(self, event):
        """Function to simulate the model.

        :return:
        """
        self.plugin.parent.eh_tb_simulate(event)

    def SetDataList(self, datalist):
        """SetDataList(self, datalist) --> None

        Sets the name of the available data sets
        """
        self.datalist = datalist

    def GetDataList(self):
        """SetDataList(self) --> list

        Retrives the data list
        """
        return self.datalist

    def SetParameterList(self, parameterlist):
        """SetParameterList(self, parameterlist) --> None

        Sets the code list for all definition of custom parameters
        """
        self.parameterlist = parameterlist

    def GetParameterList(self):
        """SetParameterList(self) --> list

        Retrives the parameter list
        """
        return self.parameterlist

    def SetExpressionList(self, expressionlist):
        """SetExpressionList(expressionlist) --> None

        Sets the expression list, should be a 2D list with the
        one list for each item in datalist
        """
        if len(expressionlist) != len(self.datalist):
            raise ValueError("The list of expression has to have the" + " same length as the data list")
        self.expressionlist = expressionlist

    def GetExpressionList(self):
        """GetExpressionList(self) --> expression list

        Returns the expressionlist
        """
        return self.expressionlist

    def SetSimArgs(self, sim_funcs, insts, args):
        """SetSimArgs(self, sim_func, inst, args) --> None

        Sets the current simulation function for each data set
        their instruments and arguments
        sim_func: A list of simulation names
        inst: A list of instrument names
        args: A list of argument list for the sim_func's each argument should be a string.
        """
        if len(sim_funcs) != len(self.datalist):
            raise ValueError("The list of sim_funcs has to have the" + " same length as the data list")
        if len(insts) != len(self.datalist):
            raise ValueError("The list of insts has to have the" + " same length as the data list")
        if len(args) != len(self.datalist):
            raise ValueError("The list of args has to have the" + " same length as the data list")
        self.sim_funcs = sim_funcs[:]
        self.insts = insts[:]
        self.args = args[:]

    def GetSimArgs(self):
        """GetSimArgs(self) --> (sim_funcs, insts, args)

        See SetSimArgs for a description of the parameters
        """
        return self.sim_funcs, self.insts, self.args

    def AppendSim(self, sim_func, inst, args):
        """AppendSim(self, sim_func, inst, args) --> None

        Appends a simultion to the Panel lists
        """
        self.sim_funcs.append(sim_func)
        self.insts.append(inst)
        self.args.append(args)

    def InstrumentNameChange(self, old_name, new_name):
        """OnInstrumentNameChange --> None

        Exchanges old_name to new name in the simulations."""

        for i in range(len(self.insts)):
            if self.insts[i] == old_name:
                self.insts[i] = new_name
        self.update_listbox()

    def SetUpdateScriptFunc(self, func):
        """SetUpdateScriptFunc(self, func) --> None

        Sets the function to be called when the script needs to be updated.
        will only be called as func(event)
        """
        self.script_update_func = func

    def UpdateListbox(self, update_script=True):
        """Update(self) --> None

        Update the listbox and runs the callback script_update_func
        """
        self.update_listbox()
        if self.script_update_func and update_script:
            self.script_update_func(None)
        self.Refresh()

    def update_listbox(self):
        """update_listbox(self) --> None

        updates the listbox.
        """
        list_strings = []
        for i in range(len(self.datalist)):
            str_arg = ", ".join(self.args[i])
            list_strings.append(
                "<code><b>%s</b>: %s(%s, %s)</code>"
                " \n" % (self.datalist[i], self.sim_funcs[i], str_arg, self.insts[i])
            )
            for item in self.expressionlist[i]:
                list_strings.append(self.command_indent + "%s</pre>" % item)

        self.listbox.SetItemList(list_strings)

    def get_expression_position(self):
        """get_expression_position(self) --> (dataitem, expression)

        Finds the position in the expression list for a certain item.
        return -1 if it can not be found.
        """
        index = self.listbox.GetSelection()

        if index == wx.NOT_FOUND:
            return -1, -1

        dataindex = -1
        itemindex = -1
        listindex = -1
        for i in range(len(self.datalist)):
            dataindex += 1
            listindex += 1
            itemindex = -1
            if listindex >= index:
                return dataindex, itemindex
            for item in self.expressionlist[i]:
                listindex += 1
                itemindex += 1
                if listindex >= index:
                    return dataindex, itemindex

        # If all other things fail...
        return -1, -1

    def Edit(self, event):
        """Edit(self, event) --> None

        Edits an entry in the list
        """
        data_pos, exp_pos = self.get_expression_position()
        if exp_pos != -1 and data_pos != -1:
            # Editing the expressions for variables
            list_item = self.expressionlist[data_pos][exp_pos]
            dlg = ParameterExpressionDialog(self, self.plugin.GetModel(), list_item, sim_func=self.onsimulate)
            if dlg.ShowModal() == wx.ID_OK:
                exp = dlg.GetExpression()
                self.expressionlist[data_pos][exp_pos] = exp
                self.UpdateListbox()
        if exp_pos == -1 and data_pos != -1:
            # Editing the simulation function and its arguments
            dlg = SimulationExpressionDialog(
                self,
                self.plugin.GetModel(),
                self.plugin.sample_widget.instruments,
                self.sim_funcs[data_pos],
                self.args[data_pos],
                self.insts[data_pos],
                data_pos,
            )
            if dlg.ShowModal() == wx.ID_OK:
                self.args[data_pos] = dlg.GetExpressions()
                self.insts[data_pos] = dlg.GetInstrument()
                self.sim_funcs[data_pos] = dlg.GetSim()
                self.UpdateListbox()

    def Insert(self, event):
        """Insert(self, event) --> None

        Inserts a new operations
        """
        data_pos, exp_pos = self.get_expression_position()
        if data_pos != -1:
            dlg = ParameterExpressionDialog(self, self.plugin.GetModel(), sim_func=self.onsimulate)
            if dlg.ShowModal() == wx.ID_OK:
                exp = dlg.GetExpression()
                if exp_pos == -1:
                    self.expressionlist[data_pos].insert(0, exp)
                else:
                    self.expressionlist[data_pos].insert(exp_pos, exp)
                self.UpdateListbox()

    def Delete(self, event):
        """Delete(self, event) --> None

        Deletes an operation
        """
        data_pos, exp_pos = self.get_expression_position()
        if exp_pos != -1 and data_pos != -1:
            self.expressionlist[data_pos].pop(exp_pos)
            self.UpdateListbox()

    def MoveUp(self, event):
        """MoveUp(self, event) --> None

        Move an operation up
        """
        pass

    def MoveDown(self, event):
        """MoveDown(self, event) --> None

        Moves an operation down
        """
        pass

    def EditPars(self, event):
        """EditPars(self, event) --> None

        Creates a new parameter
        """
        dlg = EditCustomParameters(self, self.plugin.GetModel(), self.parameterlist)
        if dlg.ShowModal() == wx.ID_OK:
            self.parameterlist = dlg.GetLines()
            self.UpdateListbox()
        dlg.Destroy()

    def OnDataChanged(self, event):
        """OnDataChanged(self, event) --> None

        Updated the data list
        """
        self.UpdateListbox(update_script=False)


class EditCustomParameters(wx.Dialog):
    model: Model

    def __init__(self, parent, model, lines):
        wx.Dialog.__init__(self, parent, -1, "Custom parameter editor")
        self.SetAutoLayout(True)
        self.model = model
        self.lines = lines
        self.var_name = "cp"

        sizer = wx.BoxSizer(wx.VERTICAL)
        name_ctrl_sizer = wx.GridBagSizer(2, 3)

        col_labels = ["Name", "Value", "Sigma (for systematic error)"]

        for item, index in zip(col_labels, list(range(len(col_labels)))):
            label = wx.StaticText(self, -1, item)
            name_ctrl_sizer.Add(label, (0, index), flag=wx.ALIGN_LEFT, border=5)

        self.name_ctrl = wx.TextCtrl(self, -1, size=(120, -1))
        name_ctrl_sizer.Add(self.name_ctrl, (1, 0), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
        self.value_ctrl = wx.TextCtrl(self, -1, size=(120, -1))
        name_ctrl_sizer.Add(self.value_ctrl, (1, 1), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
        self.error_ctrl = wx.TextCtrl(self, -1, size=(120, -1))
        name_ctrl_sizer.Add(self.error_ctrl, (1, 2), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
        self.add_button = wx.Button(self, id=wx.ID_ANY, label="Add")
        name_ctrl_sizer.Add(self.add_button, (1, 3), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
        sizer.Add(name_ctrl_sizer)
        self.Bind(wx.EVT_BUTTON, self.OnAdd, self.add_button)

        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP, 5)

        self.listbox = MyHtmlListBox(self, -1, size=(-1, 150), style=wx.BORDER_SUNKEN)
        self.listbox.SetItemList(self.lines)
        sizer.Add(self.listbox, 1, wx.GROW | wx.ALL, 10)

        self.delete_button = wx.Button(self, id=wx.ID_ANY, label="Delete")
        sizer.Add(self.delete_button, 0, wx.CENTRE, 0)
        self.Bind(wx.EVT_BUTTON, self.OnDelete, self.delete_button)

        button_sizer = wx.StdDialogButtonSizer()
        okay_button = wx.Button(self, id=wx.ID_OK)
        # okay_button.SetDefault()
        button_sizer.AddButton(okay_button)
        button_sizer.AddButton(wx.Button(self, id=wx.ID_CANCEL))
        button_sizer.Realize()
        self.Bind(wx.EVT_BUTTON, self.OnApply, okay_button)

        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP, 5)

        sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT, 5)
        self.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()

    def OnApply(self, event):
        """OnApply(self, event) --> None

        Callback for ok button click or apply button
        """
        event.Skip()

    def OnAdd(self, event):
        """OnAdd(self, event) --> None

        Callback for adding an entry
        """
        sigma = self.error_ctrl.GetValue()
        if sigma.strip() == "":
            line = "%s.new_var('%s', %s)" % (self.var_name, self.name_ctrl.GetValue(), self.value_ctrl.GetValue())
        else:
            line = "%s.new_sys_err('%s', %s, %s)" % (
                self.var_name,
                self.name_ctrl.GetValue(),
                self.value_ctrl.GetValue(),
                sigma,
            )
        try:
            self.model.eval_in_model(line)
        except Exception as e:
            result = "Could not evaluate the expression. The python error" + "is: \n" + e.__repr__()
            ShowWarningDialog(self, result, "Error in expression")
        else:
            self.lines.append(line)
            self.listbox.SetItemList(self.lines)

    def OnDelete(self, event):
        """OnDelete(self, event) --> None

        Callback for deleting an entry
        """
        result = "Do you want to delete the expression?\n" + "Remember to check if parameter is used elsewhere!"
        result = ShowQuestionDialog(self, result, "Delete expression?")
        if result:
            self.lines.pop(self.listbox.GetSelection())
            self.listbox.SetItemList(self.lines)

    def GetLines(self):
        """GetLines(self) --> uservars lines [list]

        Returns the list user variables.
        """
        return self.lines


class SimulationExpressionDialog(wx.Dialog):
    """A dialog to edit the Simulation expression"""

    model: Model

    def __init__(self, parent, model, instruments, sim_func, arguments, inst_name, data_index):
        """Creates a SimualtionExpressionDialog.

        model - a Model object.
        instruments - a dictionary of possible instruments
        arguments - the arguments to the simulation function, a list of strings.
        sim_func - a string of the simulation function name.
        inst_name - the name of the current instrument.
        data_index - an integer for the current data index.
        """
        if not model.compiled:
            model.compile_script()

        self.model = model
        self.instruments = instruments
        self.available_sim_funcs = list(self.model.eval_in_model("model.SimulationFunctions.keys()"))
        self.data_index = data_index

        # Do the layout of the dialog
        wx.Dialog.__init__(self, parent, -1, "Simulation editor")
        self.SetAutoLayout(True)

        # Find out the maximum number of arguments to the available sim_funcs
        max_val = -1
        self.sim_args = {}
        self.sim_defaults = {}
        for func in self.available_sim_funcs:
            doc = self.model.eval_in_model("model.SimulationFunctions" '["%s"].__doc__' % func)
            doc_lines = find_code_segment(doc, "Parameters").splitlines()
            max_val = max(len(doc_lines), max_val)
            args = []
            defaults = []
            for line in doc_lines:
                items = line.lstrip().rstrip().split(" ")
                args.append(items[0])
                defaults.append(items[1].replace("data", "d"))
            self.sim_args[func] = args
            self.sim_defaults[func] = defaults

        expressions = {"Instrument": inst_name}
        for arg_name, arg in zip(self.sim_args[sim_func], arguments):
            expressions[arg_name] = arg

        if max_val < 0:
            raise ValueError("Wrongly formatted function docs for the simulation functions")

        gbs = wx.GridBagSizer(2, max_val)

        # Creating the column labels
        col_labels = ["Simulation", "Instrument"]
        [col_labels.append(arg) for arg in self.sim_args[sim_func] if not arg in col_labels]
        self.labels = []
        self.arg_controls = []
        for index in range(2 + max_val):
            label = wx.StaticText(self, -1, "")
            gbs.Add(label, (0, index), flag=wx.ALIGN_LEFT, border=5)
            self.labels.append(label)
            # If the expression is not an instrument or simulation function
            if index > 1:
                exp_ctrl = wx.TextCtrl(self, -1, size=(100, -1))
                gbs.Add(exp_ctrl, (1, index), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
                self.arg_controls.append(exp_ctrl)

        for item, label in zip(col_labels[:2], self.labels[:2]):
            label.SetLabel(item)
        # Creating the text boxes for the arguments
        # Setting the text in the column labels and text controls
        for item, label, arg_ctrl in zip(col_labels[2:], self.labels[2:], self.arg_controls):
            label.SetLabel(item)
            arg_ctrl.SetValue(expressions[item])
            arg_ctrl.SetEditable(True)

        for i in range(len(col_labels) - 2, len(self.arg_controls)):
            self.arg_controls[i].SetEditable(False)
            # self.arg_controls[i].Show(False)
            # self.arg_controls[i].SetValue('NA')

        # Creating the controls
        # Simulation choice control
        self.sim_choice = wx.Choice(self, -1, choices=self.available_sim_funcs)
        self.Bind(wx.EVT_CHOICE, self.on_sim_change, self.sim_choice)
        self.sim_choice.SetSelection(self.available_sim_funcs.index(sim_func))
        gbs.Add(self.sim_choice, (1, 0), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)

        # Instrument choice control
        self.inst_choice = wx.Choice(self, -1, choices=list(self.instruments.keys()))
        # self.Bind(wx.EVT_CHOICE, self.on_inst_change, self.inst_choice)
        self.inst_choice.SetSelection(list(self.instruments.keys()).index(expressions["Instrument"]))
        gbs.Add(self.inst_choice, (1, 1), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)

        button_sizer = wx.StdDialogButtonSizer()
        okay_button = wx.Button(self, wx.ID_OK)
        okay_button.SetDefault()
        button_sizer.AddButton(okay_button)
        button_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))

        button_sizer.Realize()
        self.Bind(wx.EVT_BUTTON, self.on_ok_button, okay_button)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(gbs, 1, wx.GROW | wx.ALL, 10)
        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP, 5)
        sizer.AddSpacer(5)

        sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT, 5)
        sizer.AddSpacer(5)
        self.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()

    def on_sim_change(self, evt):
        """Callback for changing the choice widget for the different simulations."""
        new_sim = self.sim_choice.GetStringSelection()
        # Update the column labels
        new_labels = []
        for label, arg_name in zip(self.labels[2:], self.sim_args[new_sim]):
            new_labels.append(label.GetLabel() != arg_name)
            label.SetLabel(arg_name)
        # Clear the remaining column labels
        for label in self.labels[len(self.sim_args[new_sim]) + 2 :]:
            label.SetLabel("")

        # Update the text controls - if needed
        for i in range(len(self.sim_args[new_sim])):
            # if new_labels[i]:
            if True:
                self.arg_controls[i].SetValue(self.sim_defaults[new_sim][i])
            self.arg_controls[i].SetEditable(True)
            # self.arg_controls[i].Show(True)
        # Hide and clear the remaining text controls
        for ctrl in self.arg_controls[len(self.sim_args[new_sim]) :]:
            ctrl.SetEditable(False)
            ctrl.SetValue("")

            # ctrl.Show(False)

    def on_ok_button(self, event):
        """Callback for pressing the ok button in the dialog"""
        expressions = self.GetExpressions()
        # Hack to get it working with d = data[0]
        exec("d = data[%d]" % self.data_index, self.model.script_module.__dict__)
        for exp in expressions:
            try:
                self.model.eval_in_model(exp)
            except Exception as e:
                result = "Could not evaluate expression:\n%s.\n" % exp + " The python error is: \n" + e.__repr__()
                ShowWarningDialog(self, result, "Error in expression")
            else:
                event.Skip()

    def GetExpressions(self):
        """Returns the current expressions in the dialog box"""
        return [ctrl.GetValue() for ctrl in self.arg_controls if ctrl.IsEditable()]

    def GetInstrument(self):
        """Returns the selected instrument, a string"""
        return self.inst_choice.GetStringSelection()

    def GetSim(self):
        """Returns the selected simulation, a string"""
        return self.sim_choice.GetStringSelection()


class ParameterExpressionDialog(wx.Dialog):
    """A dialog for setting parameters for fitting"""

    model: Model

    def __init__(self, parent, model, expression=None, sim_func=None):
        wx.Dialog.__init__(self, parent, -1, "Parameter editor")
        self.SetAutoLayout(True)
        self.model = model
        self.sim_func = sim_func
        if not model.compiled:
            model.compile_script()

        gbs = wx.GridBagSizer(2, 3)

        col_labels = ["Object", "Parameter", "Expression"]

        for item, index in zip(col_labels, list(range(len(col_labels)))):
            label = wx.StaticText(self, -1, item)
            gbs.Add(label, (0, index), flag=wx.ALIGN_LEFT, border=5)

        # Get the objects that should be in the choice boxes
        par_dict = model.get_possible_parameters()
        objlist = []
        funclist = []
        for cl in par_dict:
            obj_dict = par_dict[cl]
            for obj in obj_dict:
                objlist.append(obj)
                funclist.append(obj_dict[obj])

        self.objlist = objlist
        self.funclist = funclist
        self.obj_choice = wx.Choice(self, -1, choices=objlist)
        self.Bind(wx.EVT_CHOICE, self.on_obj_change, self.obj_choice)

        self.func_choice = wx.Choice(self, -1)
        # This will init func_choice
        self.obj_choice.SetSelection(0)

        gbs.Add(self.obj_choice, (1, 0), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)
        gbs.Add(self.func_choice, (1, 1), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)

        exp_right = ""
        if expression:
            p = expression.find("(")
            exp_left = expression[:p]
            obj = exp_left.split(".")[0]
            func = exp_left.split(".")[1]
            exp_right = expression[p + 1 : -1]
            obj_pos = [i for i in range(len(objlist)) if objlist[i] == obj]
            if len(obj_pos) > 0:
                self.obj_choice.SetSelection(obj_pos[0])
                self.on_obj_change(None)
                func_pos = [i for i in range(len(funclist[obj_pos[0]])) if funclist[obj_pos[0]][i] == func]
                if len(func_pos) > 0:
                    self.func_choice.SetSelection(func_pos[0])
                else:
                    raise ValueError("The function %s for object %s does not exist" % (func, obj))
            else:
                raise ValueError("The object %s does not exist" % obj)

        # self.expression_ctrl = wx.TextCtrl(self, -1, exp_right,\
        #                       size=(300, -1))

        self.expression_ctrl = ParameterExpressionCombo(par_dict, sim_func, self, -1, exp_right, size=(300, -1))
        gbs.Add(self.expression_ctrl, (1, 2), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)

        button_sizer = wx.StdDialogButtonSizer()
        okay_button = wx.Button(self, wx.ID_OK)
        okay_button.SetDefault()
        button_sizer.AddButton(okay_button)
        button_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))
        # apply_button = wx.Button(self, wx.ID_APPLY)
        # apply_button.SetDefault()
        # button_sizer.AddButton(apply_button)
        button_sizer.Realize()
        self.Bind(wx.EVT_BUTTON, self.OnApply, okay_button)
        # self.Bind(wx.EVT_BUTTON, self.OnApply, apply_button)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(gbs, 1, wx.GROW | wx.ALL, 10)
        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP, 5)
        sizer.AddSpacer(5)

        sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT, 5)
        sizer.AddSpacer(5)
        self.SetSizer(sizer)
        sizer.Fit(self)
        self.Layout()

    def on_obj_change(self, event):
        """on_obj_change(self, event) --> None

        On changing the object the funclist should be updated
        """
        index = self.obj_choice.GetSelection()
        self.func_choice.SetItems(self.funclist[index])

    def OnApply(self, event):
        """OnApply(self, event) --> None"""
        evalstring = self.GetExpression()
        try:
            self.model.eval_in_model(evalstring)
        except Exception as e:
            result = "Could not evaluate the expression. The python" + "is: \n" + e.__repr__()
            ShowWarningDialog(self, result, "Error in expression")
        else:
            event.Skip()

    def GetExpression(self):
        """GetExpression(self) --> expression

        Yields the string that has been edited in the dialog
        """
        objstr = self.obj_choice.GetStringSelection()
        funcstr = self.func_choice.GetStringSelection()
        set_expression = self.expression_ctrl.GetValue()
        evalstring = "%s.%s(%s)" % (objstr, funcstr, set_expression)

        return evalstring

class ORSOExportDialogHook(wx.FileDialogCustomizeHook):
    export_compact = False

    def __init__(self):
        super().__init__()

    def AddCustomControls(self, customizer):
        self.checkbox = customizer.AddCheckBox("Compact view without explicit stacks")
        self.checkbox.SetValue(self.export_compact)

    def TransferDataFromCustomControls(self):
        self.export_compact = self.checkbox.GetValue()
