__author__ = "Matts Bjorck"

import inspect
import math

import wx
import wx.html
import wx.lib.scrolledpanel as scrolledpanel

from genx.core.custom_logging import iprint
from genx.models import utils

from ...utils import ShowInfoDialog, ShowQuestionDialog, ShowWarningDialog
from . import custom_dialog as cust_dia
from . import reflectivity_images as icons
from . import sxrd_images


class ModelScriptInteractor:
    _begin_string = "# BEGIN %s"
    _end_string = "# END %s"
    _tab_string = 4 * " "
    _sim_def_string = "def Sim(data):\n" + _tab_string + "I = []\n"
    _sim_end_string = _tab_string + "return I"
    _data_set_name = "DataSet"
    _custom_parameter_name = "Parameters"

    def __init__(self, preamble=""):
        """A class that parses and handles a model script.

        Parameters:
            preamble (string):  Preamble inserted before the autogenerated code.

        Returns:
            ModelScriptInteractor
        """
        self.preable = preamble
        self.code_section_interactors = {}
        self.code_sections = []
        self.interactor_lists = {}
        self.data_sections_interactors = []
        self.custom_parameters_interactors = []

    def add_section(self, name, object_interactor, **kwargs):
        """Add a code section to the model.

        The code should be on the following form:
        # BEGIN [name]
        ......
        # END [name]

        Parameters:
            name (string): Unique name of the code section has to be a valid variable name.
            object_interactor (ObjectScriptInteractor): A class that allows the objects to be created.
            **kwargs: keyword arguments passed on to the object_interactor constructor.
        """

        if hasattr(self, name):
            raise ValueError("ModelScriptInteractor: The name %s already exist")

        def constructor():
            return object_interactor(**kwargs)

        self.code_section_interactors[name] = constructor
        self.code_sections.append(name)
        # Init the list of interactor objects
        setattr(self, name.lower(), [])

    def insert_dataset(self, pos=-1):
        """Inserts a new data set to simulate in the sim function before pos

        Parameters:
            pos (int): The position to add the the data set
        """
        raise NotImplementedError("insert_dataset is not yet implemented in ModelScriptInteractor")

    def append_dataset(self):
        """Append a new data set to the sim function"""
        iprint("Append data set")
        self.data_sections_interactors.append(DataSetSimulationInteractor())
        default_name = self.get_sim_object_names()[-1]
        sim_method = self.get_sim_methods_info(default_name)[0]
        self.data_sections_interactors[-1].set_from_sim_method(default_name, sim_method)
        self.data_sections_interactors[-1].instrument = self.instruments[0].name

    def append_custom_parameter(self, name, value):
        """Append a new custom parameter to the model

        Parameters:
            name (string): Name of the custom parameter.
            value (string): An expression of the value for the init of the parameter.
        """
        self.custom_parameters_interactors.append(CustomParameterInteractor(name, value))

    def delete_custom_parameter(self, index):
        """Delete custom parameter with index"""
        self.custom_parameters_interactors.pop(index)

    def get_sim_object_names(self):
        """Returns the names of the objects (interactors) that can simulate.

        Returns:
            names (list of strings): names of the objects that can simulate.
        """
        names = []
        for section in self.code_sections:
            for interactor in getattr(self, section.lower()):
                # print interactor.get_sim_methods_info()
                if interactor.get_sim_methods_info():
                    names.append(interactor.name)
        return names

    def get_sim_methods_info(self, name):
        """Get the simualtion method info of interactor (object) with name

        Parameters:
            name (string): name of the interactor (object).

        Returns:
            sim_method_info (list of SimMethodInfo): The information about the simualtion method
        """
        interactor = self.get_interactor_by_name(name)
        return interactor.get_sim_methods_info()

    def get_interactor_by_name(self, name):
        """Returns the first instance of interactor with name

        Raises a ValueError if name is not found

        Parameters:
            name (string): name of the interactor

        Returns:
            interactor (ObjectScriptInteractor): Interactor with name

        """
        for section in self.code_sections:
            for interactor in getattr(self, section.lower()):
                if interactor.name.lower() == name.lower():
                    return interactor
        raise ValueError("%s is not a name of an interactor (get_interactor_name)")

    def find_section_index(self, code, name):
        """Find the index of a section of code.

        Parameters:
            code (string): The code.
            name (string): A section name.

        Returns:
            begin_index (int): The start index of the section code.
            end_section (int): The end index of the section code.
        """
        begin_string = self._begin_string % name
        try:
            begin_index = code.index(begin_string) + len(begin_string) + 1
        except ValueError:
            raise ValueError('ModelScriptInteractor Could not locate the beginning of the code section "%s"' % name)
        try:
            end_index = code.index(self._end_string % name)
        except ValueError:
            raise ValueError('ModelScriptInteractor Could not locate the end of the code section "%s"' % name)
        return begin_index, end_index

    def find_new_data_set_insertion_index(self, code):
        """Finds the insertion point of a new data set. Right before the end of the simulation function.

        Parameters:
            code (string): The code.

        Returns:
            index (int): The index to insert the code.
        """
        try:
            index = code.index(self._sim_end_string)
        except ValueError:
            raise ValueError("ModelScriptInteractor could not locate the end of the simulation function.")

        return index

    def find_section(self, name, code):
        """Find and extract a certain section of code.

        Parameters:
            name (string): A section name.
            code (string): The code to parse.
        """
        begin_index, end_index = self.find_section_index(code, name)
        code = code[begin_index:end_index].strip()
        return code

    def split_section(self, code, multiline):
        """Splits the code into chuncks, one chunk one object, that can be parsed by the respective
        ObjectScriptInteractor.

        If multiline is True then an empty line will signify switching of object.

        Parameters:
            code (string): Code to split
            multiline (bool): Signals whether or not a definition is multiline.

        Returns:
            chunks (list): A list of code chunks.
        """
        if multiline:
            chunks = [""]
            for line in code.splitlines():
                line = line.strip()
                if line == "":
                    chunks.append("")
                else:
                    chunks[-1] += line + "\n"
        else:
            chunks = code.splitlines()

        return chunks

    def parse_code(self, code):
        """Parses the code to create the different objects.

        If a ObjectScriptInteractor is of multiline type it is expected that the definitons are seperated by an extra
        newline.

        Parameters:
            code (string): The code to parse.
        """
        for name in self.code_section_interactors:
            interactor_constructor = self.code_section_interactors[name]
            code_section = self.find_section(name, code)
            tmp = interactor_constructor()
            chunks = self.split_section(code_section, tmp.get_multiline())
            interactors = []
            for chunk in chunks:
                interactors.append(interactor_constructor())
                interactors[-1].parse_code(chunk)

            # setattr(self, name.lower(), interactors)
            replace_list_items(getattr(self, name.lower()), interactors)

        # Empty list
        # [self.data_sections_interactors.pop(0) for d in self.data_sections_interactors]
        self.data_sections_interactors[:] = []
        iprint("parse_code", len(self.data_sections_interactors))
        missing_data_section = False
        i = 0
        while not missing_data_section:
            try:
                code_section = self.find_section(self.get_data_set_name(i), code)
            except ValueError:
                missing_data_section = True
            else:
                self.append_dataset()
                self.data_sections_interactors[-1].parse_code(code_section)
                i += 1

        # Parse the custom parameters:
        code_section = self.find_section(self._custom_parameter_name, code)
        # print code_section
        chunks = self.split_section(code_section, False)
        interactors = []
        # Skipt the first row this is the definition of the custom parameters object
        for chunk in chunks[1:]:
            interactors.append(CustomParameterInteractor())
            interactors[-1].parse_code(chunk)
        replace_list_items(self.custom_parameters_interactors, interactors)

    def get_section_code(self, name, tab_lvl=0, outer_comments=True, member_name=None):
        """Get the code for section [name]

        Parameters:
            name (string): name of the section.
            tablvl (int): The tab level of the code.
            outer_comments (bool): Flag to indicate if the begin and end comments should be included.
            member_name (string): Special name for the member variable interactor to be used, if not given defaults
                                    to name.

        Returns:
            code (string): Code for the section
        """
        if not member_name:
            interactors = getattr(self, name.lower())
        else:
            interactors = getattr(self, member_name)
        code = ""
        if outer_comments:
            code += tab_lvl * self._tab_string + self._begin_string % name + "\n"
        for interactor in interactors:
            for line in interactor.get_code().splitlines():
                code += tab_lvl * self._tab_string + line + "\n"
            if interactor.get_multiline():
                code += "\n"
        if interactor.get_multiline():
            code = code[:-1]
        if outer_comments:
            code += tab_lvl * self._tab_string + self._end_string % name + "\n"

        return code

    def get_data_set_name(self, index):
        """Returns the name of the data set in the code"""
        return "%s %d" % (self._data_set_name, index)

    def get_data_set_code(self, index, tab_lvl=0, outer_comments=True):
        """Returns the code of a data set interactor

        Parameters:
            index (int): The index of the data set
            tablvl (int): The tab level of the code.
            outer_comments (bool): Flag to indicate if the begin and end comments should be included.

        Returns:
            code (string): Code for the section
        """
        code = ""
        name = self.get_data_set_name(index)
        if outer_comments:
            code += tab_lvl * self._tab_string + self._begin_string % name + "\n"
        interactor = self.data_sections_interactors[index]
        interactor.set_pos(index)
        for line in interactor.get_code().splitlines():
            code += tab_lvl * self._tab_string + line + "\n"
        if outer_comments:
            code += tab_lvl * self._tab_string + self._end_string % name + "\n"

        return code

    def insert_section_code(self, code, name, new_section_code, tab_lvl):
        """Update the code for the section name.

        Parameters:
            code (string): The script code
            new_section_code (string): The new code for section.
            name (string): name of the section.
            tablvl (int): The tab level of the code.

        Returns:
            new_code (string): New updated code
        """
        begin_index, end_index = self.find_section_index(code, name)
        new_code = code[:begin_index] + new_section_code + code[(end_index - tab_lvl * len(self._tab_string)) :]
        return new_code

    def update_section(self, code, name, tab_lvl=0, member_name=None):
        """Update the code for the section name.

        Parameters:
            code (string): The script code
            name (string): name of the section.
            tablvl (int): The tab level of the code.
            member_name (string): Special name for the member variable interactor to be used, if not given defaults
                                    to name.

        Returns:
            code (string): Code for the section
        """
        new_section_code = self.get_section_code(name, tab_lvl, False, member_name=member_name)
        new_code = self.insert_section_code(code, name, new_section_code, tab_lvl)

        return new_code

    def update_custom_parameter_section(self, code):
        """Update the custom parameter section

        Parameters:
            code (string): The code to update
        Returns:
            new_code (string): The updated code
        """
        new_section_code = self.get_custom_parameter_code(outer_comments=False)
        new_code = self.insert_section_code(code, self._custom_parameter_name, new_section_code, 0)

        return new_code

    def update_data_set(self, code, index, tab_lvl=0):
        """Update the code for the section name.

        Parameters:
            code (string): The script code
            index (int): data set index.
            tablvl (int): The tab level of the code.
            member_name (string): Special name for the member variable interactor to be used, if not given defaults
                                    to name.

        Returns:
            code (string): Code for the section
        """
        new_code = self.insert_section_code(
            code, self.get_data_set_name(index), self.get_data_set_code(index, tab_lvl, False), tab_lvl
        )

        return new_code

    def remove_section(self, code, name):
        """Remove the section name from code

        Parameters
            code (string): The script code
            name (string): name of the section.

        Returns:
            code (string): Code for the section
        """
        begin_string = self._begin_string % name
        end_string = self._end_string % name
        new_code = ""
        keeplines = True
        for line in code.splitlines(True):
            if begin_string in line:
                keeplines = False
            elif end_string in line:
                keeplines = True
            elif keeplines:
                new_code += line

        return new_code

    def get_number_data_sets_in_code(self, code):
        """Returns the number of data sets in the code"""
        missing_data_section = False
        i = 0
        while not missing_data_section:
            try:
                code_section = self.find_section(self.get_data_set_name(i), code)
            except ValueError:
                missing_data_section = True
            else:
                i += 1
        return i

    def get_preamble(self):
        """Returns the preamble that should be inserted before the created code."""
        return self.preable + "\n\n"

    def get_sim_code(self):
        """Returns the code for the simulation function"""
        code = self._sim_def_string
        for i, data_set in enumerate(self.data_sections_interactors):
            data_set.set_pos(i)
            code += self.get_data_set_code(i, tab_lvl=1)
        code += self._sim_end_string

        return code

    def get_custom_parameter_code(self, outer_comments=True):
        """Returns the code for the custom parameters"""
        tab_lvl = 0
        code = ""
        if outer_comments:
            code += tab_lvl * self._tab_string + self._begin_string % self._custom_parameter_name + "\n"

        code += "cp = UserVars()\n"
        for cust_par in self.custom_parameters_interactors:
            code += cust_par.get_code()
            code += "\n"

        if outer_comments:
            code += tab_lvl * self._tab_string + self._end_string % self._custom_parameter_name + "\n"

        return code

    def get_code(self):
        """Creates the code for the script based on the different interactors.

        Returns:
            code (string): The code that can be compiled into a model
        """
        code = self.get_preamble()
        for name in self.code_sections:
            code += self.get_section_code(name)
            code += "\n"

        # Create the custom parameter code
        code += self.get_custom_parameter_code()
        code += "\n"

        # Define the Simulation function
        code += self.get_sim_code()

        return code

    def update_code(self, code):
        """Updates the code contained in the sections based on the different interactors.

        Parameters:
            code (string): The code that should be updated.

        Returns:
            new_code (string): The updated code.
        """
        new_code = code[:]
        for name in self.code_sections:
            new_code = self.update_section(new_code, name)

        # Update the custom parameters
        new_code = self.update_custom_parameter_section(new_code)

        data_sets_in_code = self.get_number_data_sets_in_code(code)
        # Update the data sets that are already there
        data_sets_to_update = min(data_sets_in_code, len(self.data_sections_interactors))
        for i in range(0, data_sets_to_update):
            new_code = self.update_data_set(new_code, i, tab_lvl=1)
        # Handle the cases where data sets are removed
        if data_sets_in_code > len(self.data_sections_interactors):
            for i in range(len(self.data_sections_interactors), data_sets_in_code):
                new_code = self.remove_section(new_code, self.get_data_set_name(i))
        # Handle the cases where data sets are added
        elif data_sets_in_code < len(self.data_sections_interactors):
            for i in range(data_sets_in_code, len(self.data_sections_interactors)):
                insertion_index = self.find_new_data_set_insertion_index(new_code)
                self.data_sections_interactors[i].set_pos(i)
                # print "Insertion: ", self.get_data_set_code(i, tab_lvl=1)
                new_code = (
                    new_code[:insertion_index] + self.get_data_set_code(i, tab_lvl=1) + new_code[insertion_index:]
                )
        return new_code


class ScriptInteractor:
    """Base class for script interactors.

    The following methods has to be overridden:
        get_code
        parse_code
    """

    def create_new(self):
        """Create a new object"""
        return self.__class__()

    def copy(self):
        """Creates a copy of the object"""
        cpy = self.create_new()
        cpy.parse_code(self.get_code())
        return cpy

    def get_multiline(self):
        """Returns True if there is multiple lines in the object definition, otherwise returns false."""
        return False

    def get_code(self):
        """Creates the code of the object - has to be overridden"""
        raise NotImplementedError()

    def parse_code(self, code):
        """Parses the code for the object - has to be overridden"""
        raise NotImplementedError()

    def parse_list(self, code):
        """Utility function to parse a list on the form [[expression], [expression], ....].

        Parameters:
            code (string): A string representing the list.

        Returns:
            expr_list (list): A list of strings.
        """
        # Clean the code from leading and trailing whitespaces
        code = code.strip()
        # Remove [ and ]
        code = code[1:-1]
        expr_list = [item.strip() for item in code.split(",")]
        if len(expr_list) == 1 and expr_list[0] == "":
            expr_list = []
        return expr_list

    def get_list_code(self, expr_list):
        """Create the code to make a list out of expression

        Parameters:
            expr_list (list): A list of strings.

        Returns:
            code (string): The code that represents the list
        """
        code = "["
        for item in expr_list:
            code += item + ", "
        if len(expr_list) > 0:
            code = code[:-2]
        code += "]"
        return code

    def parse_parameter_string(self, code):
        """Parse the string inside the parentheses when calling the instructor. That is, parsing of code on the
        following form: [parameter]=[value], ....

        Parameters:
            code (string): A string of of parameter value pars in the form given above.

        Returns:
            new_parameter_values (dict): A dictionary of parameters (keys) and values (in the form of strings)

        """

        new_parameter_values = {}
        for par_str in code.split(","):
            (par_name, par_val) = par_str.split("=")
            if len(par_val.strip()) > 0:
                new_parameter_values[par_name.strip()] = par_val.strip().strip("'").strip('"')
            else:
                raise ValueError("Could not parse the parameter value string: %s" % par_str)

        return new_parameter_values

    def parse_value_string(self, code):
        output = []
        for par_str in code.split(","):
            par_val = par_str.strip()
            if len(par_val) > 0:
                output.append(par_val.strip("'").strip('"'))
            else:
                raise ValueError("Could not parse the parameter value string: %s" % par_str)
        return output

    def parse_name_method(self, code, method_name):
        """Parses the name and the method

        Parameters:
            code (string): code to parse.
            method_name (string): name of the method which is expected to be called.
        """
        # Find the equal sign and consequently the variable name
        name_ind = code.index("=")
        if name_ind < 0 or code.index("(") < name_ind:
            raise ValueError("Could not parse the code, no equal sign for setting the name")
        new_name = code[:name_ind].strip()
        parameter_start_ind = name_ind + code[name_ind:].index(method_name + "(") + len(method_name) + 1
        return new_name, parameter_start_ind


class ParameterExpressionInteractor(ScriptInteractor):
    def __init__(self):
        """Class that stores an expression (coupling of an expression to a variable in the model"""
        self.obj_name = ""
        self.obj_method = ""
        self.expression = ""

    def parse_code(self, code):
        """Parses code and sets the defined values in the object - note that values are represented as strings.

        The code should be on the form:
        [obj_name].[obj_method]([expression])

        Parameters:
            code (string): A string that represent the code for the simulation.
        """
        code = code.strip()
        ind = code.index(".")
        self.obj_name = code[:ind]
        code = code[ind + 1 :]
        ind = code.index("(")
        self.obj_method = code[:ind]
        self.expression = code[ind + 1 : -1]

    def get_code(self):
        """Returns the code of the object"""
        return "%s.%s(%s)" % (self.obj_name, self.obj_method, self.expression)


class CustomParameterInteractor(ScriptInteractor):
    def __init__(self, name="", value=""):
        self.obj_name = "cp"
        self.obj_method = "new_var"
        self.name = name
        self.value = value

    def parse_code(self, code):
        """Parses code and sets the defined values in the object - note that values are represented as strings.

        The code should be on the form:
        [obj_name].new_var([name], [value])

        Parameters:
            code (string): A string that represent the code for the simulation.
        """
        code = code.strip()
        ind = code.index(self.obj_name + ".")
        code = code[ind + 1 + len(self.obj_name) :]
        ind = code.index("(")
        expressions = code[ind + 1 : -1].split(",")
        self.name = expressions[0].strip().strip("'").strip('"')
        self.value = expressions[1].strip()

    def get_code(self):
        """Returns the code of the object"""
        return "%s.%s('%s', %s)" % (self.obj_name, self.obj_method, self.name, self.value)


class DataSetSimulationInteractor(ScriptInteractor):
    def __init__(self):
        """ "Class for storing the simulations of one data set."""
        self.expression_list = []
        self.obj_name = ""
        self.obj_method = ""
        self.instrument = ""
        self.arguments = []
        self.position = 0
        self.name = None

    def set_from_sim_method(self, name, sim_method):
        """Sets the simulation method arguments from a SimMethodInfo object

        Parameters:
            name (string): Name of the object that has sim_method
            sim_method (SimMethodInfo): Simulation method
        """
        self.obj_name = name
        self.obj_method = sim_method.name
        self.arguments = list(sim_method.def_args)

    def set_pos(self, pos):
        """Set the position of the data set"""
        self.position = pos

    def set_name(self, val):
        """Set the name of the data set"""
        self.name = val

    def get_sim_string(self):
        """Get the simulation string for the dataset"""
        code = "%s.%s(" % (self.obj_name, self.obj_method)
        for arg in [
            self.instrument,
        ] + self.arguments:
            code += arg + ", "
        code = code[:-2] + ")"
        return code

    def get_name(self):
        """Returns the name of the data set"""
        if self.name:
            return self.name
        else:
            return "%d" % self.position

    def get_code(self):
        """Returns the code (string) that defines the current interactor"""
        code = ""
        code += "d = data[%i]\n" % self.position
        for exp in self.expression_list:
            code += exp.get_code() + "\n"
        code += "I.append(%s)" % self.get_sim_string()
        return code

    def parse_code(self, code):
        """Parses code and sets the defined values in the object - note that values are represented as strings.

        The code should be on the form:
        d = data.x[i]
        [Expression]
        [Expression]
        .....
        I.append([obj_name].[sim_func](inst, [par_names]**))

        Parameters:
            code (string): A string that represent the code for the simulation.
        """
        # print "code: ", code
        code = code.strip()
        lines = code.splitlines()
        self.expression_list = []
        for line in lines[1:-1]:
            self.expression_list.append(ParameterExpressionInteractor())
            self.expression_list[-1].parse_code(line.strip())
        sim_code = lines[-1]
        # print sim_code
        # Remove the I.append stuff and the last paranthesis
        sim_code = sim_code[sim_code.index("(") + 1 : -1]
        # Parse the name of the object
        ind = sim_code.index(".")
        self.obj_name = sim_code[:ind]
        sim_code = sim_code[ind + 1 :]
        # Parse the name of the method
        ind = sim_code.index("(")
        self.obj_method = sim_code[:ind]
        # Keep only the arguments to the functions
        sim_code = sim_code[ind + 1 : -1]
        # all that remains is the parameters to the function
        args = sim_code.split(",")
        # print args
        self.instrument = args[0].strip()
        self.arguments = []
        for arg in args[1:]:
            self.arguments.append(arg.strip())

    def get_multiline(self):
        return True


class ObjectScriptInteractor(ScriptInteractor):
    def __init__(self, class_name="", class_impl=None):
        """A class that parses code and allows the GUI to interact with the code.

        Parameters:
            class_name (string): The name of the class used to create the code.
            class_impl (class): The class of the object.
        Returns:
            ObjectScriptInteractor
        """
        self.class_name = class_name
        self.class_impl = class_impl
        self.name = None
        self.parameters = ["name"] + self.class_impl.__parameters__
        self.groups = self.class_impl.__groups__
        self.values = self.class_impl.__values__
        self.values["name"] = ""
        self.units = self.class_impl.__units__
        self.validators = self.create_validators()

        # Add the choice dictonary (parameter key and its choices a list of the element)
        if hasattr(self.class_impl, "__choices__"):
            self.choices = self.class_impl.__choices__
        else:
            self.choices = {}

        # Create members with the right name
        self.types = {}
        for p in self.parameters:
            self.types[p] = type(self.class_impl.__values__[p])
            setattr(self, p, str(self.class_impl.__values__[p]))

        # check if the class can simulate
        if hasattr(self.class_impl, "__sim_methods__"):
            self.sim_methods_info = self.class_impl.__sim_methods__
        else:
            self.sim_methods_info = []

    def get_sim_methods_info(self):
        """Returns the simulation methods of the interactor. Empty list if it does have any"""
        return self.sim_methods_info

    def get_unit(self, parameter_name):
        """Returns the unit of parameter_name"""
        if parameter_name not in self.parameters:
            ValueError("%s is not a valid parameter name" % parameter_name)
        if parameter_name in self.units:
            return self.units[parameter_name]
        else:
            return ""

    def is_editable(self, parameter_name):
        """Returns whether or not the parameter is editable"""
        # TODO: Implement editable flag into the structure
        return True

    def is_choice(self, parameter_name):
        """Returns a bool to indicate if the parameter is a choice (multiple choices)"""
        return parameter_name in self.choices

    def get_choices(self, parameter_name):
        """Returns a list of choices for the parameter name. If parameter_name is not a choice parameter returns None"""
        if self.is_choice(parameter_name):
            return self.choices[parameter_name]
        else:
            # raise ValueError('%s is not a choice'%parameter_name)
            return None

    def create_new(self):
        """Creates a new, empty, object"""
        return self.__class__(class_name=self.class_name, class_impl=self.class_impl)

    def create_validators(self):
        """Create a validator dictionary to use with the GUI

        Returns:
            validators (dict): A dictionary of all the validators.
        """
        validators = {}
        example_vals = self.class_impl.__values__
        for key in example_vals:
            val = example_vals[key]
            if isinstance(val, str):
                validators[key] = cust_dia.TextObjectValidator()
            elif isinstance(val, float):
                validators[key] = cust_dia.FloatObjectValidator()
            elif isinstance(val, list):
                validators[key] = self.class_impl.__choices__[key]
            else:
                ValueError("Value can not be represented as an Validator")
        return validators

    def set_name(self, name):
        """Sets the name of the object"""
        self.name = name

    def get_name(self):
        """Get the name of the object"""
        return self.name

    def get_parameter_code(self, extra_pars=None, extra_types=None):
        """Create the code for the parameters in the form [parameter]=[value].

        Returns:
            code (string): A string of the code
        """
        if extra_types is None:
            extra_types = {}
        if extra_pars is None:
            extra_pars = []
        code = ""
        pars = self.parameters + extra_pars
        types = self.types.copy()
        types.update(extra_types)
        for p in pars:
            if p != "name":
                if types[p] is str:
                    code += "%s='%s', " % (p, getattr(self, p))
                else:
                    code += "%s=%s, " % (p, getattr(self, p))
        if len(pars) > 0:
            code = code[:-2]
        return code

    def get_code(self):
        """Creates the code to create a object of the class.

        Returns:
            code (string): A string representing the code.
        """
        code = "%s = %s(" % (self.name, self.class_name)
        code += self.get_parameter_code()
        code += ")"
        return code

    def parse_code(self, code):
        """Parses code and sets the defined values in the object - note that the values are represented as strings.

        The code should be on the form [name] = [class_name]([parameter]=[value], ....)

        Parameters:
            code (string): A string that represent the construction of the object
        """
        # Remove trailing and leading whitespaces
        code = code.strip()

        new_name, parameter_start_ind = self.parse_name_method(code, self.class_name)
        new_parameter_values = self.parse_parameter_string(code[parameter_start_ind:-1])

        # At this point all values are parsed consequently it is safe to set the members
        self.name = new_name
        for p in new_parameter_values:
            setattr(self, p, new_parameter_values[p])


class SampleInteractor(ObjectScriptInteractor):
    def __init__(self, **kwargs):
        ObjectScriptInteractor.__init__(self, **kwargs)
        self.domains = []

    def parse_parameter_string(self, code):
        """Parses the creation of a sample object. Overloaded from ObjectScriptInteractor.

        The expected form of a domain object is: [name] = [class_name]([[domain_name], [domain_name]...],
                                                                        [parameter]=[value]....)

        Parameters:
            code (string): code to parse

        Returns:
            new_parameters_values (dict): A dictionary of new parameter values.
        """
        special_values = {}

        # Parsing domains
        ind = code.index("]")
        if ind < 0:
            ValueError("Could not parse domain, argument slabs")
        special_values["domains"] = self.parse_list(code[: ind + 1].strip())
        code = code[ind + 1 :].strip()[1:]

        new_parameter_values = ObjectScriptInteractor.parse_parameter_string(self, code)
        new_parameter_values.update(special_values)
        return new_parameter_values

    def get_parameter_code(self):
        """Create the code to generate the object. Overloaded from ObjectScriptInteractor.

        Returns:
        code (string): Code representing the parameters, the things inside the parenthesis
        """
        code = "%s, " % (self.get_list_code(self.domains))
        code += ObjectScriptInteractor.get_parameter_code(self)
        return code


class DomainInteractor(ObjectScriptInteractor):
    def __init__(self, **kwargs):
        ObjectScriptInteractor.__init__(self, **kwargs)
        self.slabs = []
        self.bulk_slab = None
        self.unitcell = None
        self.bulk_sym = "p1"
        self.surface_sym = "p1"

    def parse_parameter_string(self, code):
        """Parses the creation of a domain object. Overloaded from ModelScriptInteractor.

        The expected form of a domain object is: [name] = [class_name]([bulk_name], [[slab_name], [slab_name]...],
        [unitcell], [parameter]=[value]....)

        Parameters:
            code (string): code to parse

        Returns:
            new_parameters_values (dict): A dictionary of new parameter values.
        """
        domain_special_values = {}
        # Parsing bulk_slab
        ind = code.index(",")
        if ind < 0:
            ValueError("Could not parse domain, argument bulk_slab")
        domain_special_values["bulk_slab"] = code[:ind].strip()
        code = code[ind + 1 :]
        # Parsing slabs
        ind = code.index("]")
        if ind < 0:
            ValueError("Could not parse domain, argument slabs")
        domain_special_values["slabs"] = self.parse_list(code[: ind + 1].strip())
        code = code[ind + 1 :].strip()[1:]
        # Parsing unitcell
        ind = code.index(",")
        if ind < 0:
            ValueError("Could not parse domain, unitcell")
        domain_special_values["unitcell"] = code[:ind].strip()
        code = code[ind + 1 :]

        new_parameter_values = ObjectScriptInteractor.parse_parameter_string(self, code)
        new_parameter_values.update(domain_special_values)
        return new_parameter_values

    def get_parameter_code(self):
        """Create the code to generate the object. Overloaded from ModelScriptInteractor.

        Returns:
            code (string): Code representing the parameters, the things inside the parenthisis
        """
        code = "%s, %s, %s, " % (self.bulk_slab, self.get_list_code(self.slabs), self.unitcell)
        code += ObjectScriptInteractor.get_parameter_code(
            self, ["surface_sym", "bulk_sym"], {"surface_sym": [], "bulk_sym": []}
        )
        return code


class SlabInteractor(ObjectScriptInteractor):
    def __init__(self, **kwargs):
        ObjectScriptInteractor.__init__(self, **kwargs)
        # Locate the atom parameters
        arg_specs = inspect.getfullargspec(self.class_impl.add_atom)
        self.atom_parameters = arg_specs.args[1:]
        atom_defaults = arg_specs.defaults

        self.atom_types = {}
        self.atom_defaults = {}
        for p, default in zip(self.atom_parameters, atom_defaults):
            self.atom_types[p] = type(default)
            self.atom_defaults[p] = str(default)
            setattr(self, p, [])

    def get_multiline(self):
        """Returns True if there is multiple lines in the object definition, otherwise returns false."""
        return True

    def parse_atom_string(self, code):
        """Parses code and returns defined values in the object - note that the values are represented as strings.

        The code should be on the form:
        [XXXX].add_atom([parameter]=[value], ....)

        Parameters:
            code (string): A string that represent the construction of the object
        """
        method_name = "add_atom("
        parameter_start_ind = code.index(method_name) + len(method_name)
        if parameter_start_ind < 0:
            raise ValueError("Could not parse the code, add_atom method")
        code = code[parameter_start_ind:-1]

        try:
            new_parameter_dict = ObjectScriptInteractor.parse_parameter_string(self, code)
        except ValueError:
            # in case add_atom is given without keyword arguments, try to parse into dict
            new_parameter_dict = dict(id="", el="Al", x=0.0, y=0.0, z=0.0, u=0.0, oc=1.0, m=1.0)
            keys = list(new_parameter_dict.keys())
            new_parameter_list = ObjectScriptInteractor.parse_value_string(self, code)
            for i, val in enumerate(new_parameter_list):
                new_parameter_dict[keys[i]] = val

        return new_parameter_dict

    def add_atom(self, **kwargs):
        """Add and an atom with parameters defined in kwargs (dict)"""
        # Setting the defaults of the object
        for parameter_name in self.atom_defaults:
            getattr(self, parameter_name).append(self.atom_defaults[parameter_name])
        for parameter_name in kwargs:
            getattr(self, parameter_name)[-1] = kwargs[parameter_name]

    def parse_code(self, code):
        """Parses code and sets the defined values in the object - note that the values are represented as strings.

        The code should be on the form:
        [name] = [class_name]([parameter]=[value], ....)
        [name].add_atom([parameter]=[value], ....)

        Parameters:
            code (string): A string that represent the construction of the object
        """
        lines = code.strip().split("\n")
        ObjectScriptInteractor.parse_code(self, lines[0])
        if len(lines) > 1:
            new_atoms = []
            for line in lines[1:]:
                new_atoms.append(self.parse_atom_string(line))

            # Successfully parsed the atoms, updating the values
            for new_atom in new_atoms:
                self.add_atom(**new_atom)

    def get_atom_code(self):
        """Creates the code to define the atoms for the current object.

        Returns:
            code (string): A string representing the code.
        """
        code = ""
        for i in range(len(getattr(self, self.atom_parameters[0]))):
            code += "%s.add_atom(" % self.name
            for p in self.atom_parameters:
                if self.atom_types[p] is str:
                    code += "%s='%s', " % (p, getattr(self, p)[i])
                else:
                    code += "%s=%s, " % (p, getattr(self, p)[i])
            if len(self.atom_parameters) > 0:
                code = code[:-2]
            code += ")\n"

        if len(getattr(self, self.atom_parameters[0])) > 0:
            code = "\n" + code[:-1]

        return code

    def get_code(self):
        """Creates the code to create a object of the class.

        Returns:
            code (string): A string representing the code.
        """
        code = ObjectScriptInteractor.get_code(self)
        code += self.get_atom_code()
        return code


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.html_items = []
        self.SetItemList(["Starting up..."])

    def SetItemList(self, list):
        self.html_items = list
        self.SetItemCount(len(list))
        self.Refresh()

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


class InteractorChangedEvent(wx.CommandEvent):
    def __init__(self, evt_type, id):
        wx.CommandEvent.__init__(self, evt_type, id)


myEVT_INTERACTOR_CHANGED = wx.NewEventType()
EVT_INTERACTOR_CHANGED = wx.PyEventBinder(myEVT_INTERACTOR_CHANGED, 1)


class SelectionChangedEvent(wx.CommandEvent):
    def __init__(self, evt_type, id):
        wx.CommandEvent.__init__(self, evt_type, id)


myEVT_SELECTION_CHANGED = wx.NewEventType()
EVT_SELECTION_CHANGED = wx.PyEventBinder(myEVT_SELECTION_CHANGED, 1)


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

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

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

        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)
        self.toolbar.Realize()

        self.update_listbox()

    def do_toolbar(self):
        """Layout the toolbar"""
        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(icons.add.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.delete.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.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)

    def Update(self, update_script=True):
        """Update the listbox and runs the callback script_update_func"""
        self.update_listbox()

    def _send_change_event(self):
        """ "Sends a change event"""
        evt = InteractorChangedEvent(myEVT_INTERACTOR_CHANGED, self.GetId())
        self.GetEventHandler().ProcessEvent(evt)

    def update_listbox(self):
        """Updates the listbox."""
        list_strings = []
        for ds in self.ds_sim_list:
            list_strings.append("<code><b>%s</b>: %s</code> \n" % (ds.get_name(), ds.get_sim_string()))
            for expression in ds.expression_list:
                list_strings.append(self.command_indent + "%s</pre>" % expression.get_code())

        self.listbox.SetItemList(list_strings)

    def get_expression_position(self):
        """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 ds in self.ds_sim_list:
            dataindex += 1
            listindex += 1
            itemindex = -1
            if listindex >= index:
                return dataindex, itemindex
            for item in ds.expression_list:
                listindex += 1
                itemindex += 1
                if listindex >= index:
                    return dataindex, itemindex

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

    def Edit(self, event):
        """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.ds_sim_list[data_pos].expression_list[exp_pos]
            if self.plugin:
                model = self.plugin.GetModel()
            else:
                model = None
            dlg = ParameterExpressionDialog(self, model, list_item)
            if dlg.ShowModal() == wx.ID_OK:
                exp = dlg.GetExpression()
                self.ds_sim_list[data_pos].expression_list[exp_pos] = exp
                self.Update()
                self._send_change_event()

        if exp_pos == -1 and data_pos != -1:
            # Editing the simulation function and its arguments
            dlg = SimulationExpressionDialog(
                self, self.plugin.GetModel(), self.ds_sim_list[data_pos], self.model_script_interactor
            )
            if dlg.ShowModal() == wx.ID_OK:
                self.ds_sim_list[data_pos] = dlg.GetObject()
                self.Update()
                self._send_change_event()

    def Insert(self, event):
        """Inserts a new operations"""
        data_pos, exp_pos = self.get_expression_position()
        if data_pos != -1:
            dlg = ParameterExpressionDialog(self, self.plugin.GetModel())
            if dlg.ShowModal() == wx.ID_OK:
                exp = dlg.GetExpression()
                if exp_pos == -1:
                    self.ds_sim_list[data_pos].expression_list.insert(0, exp)
                else:
                    self.ds_sim_list[data_pos].expression_list.insert(exp_pos, exp)
                self.Update()
                self._send_change_event()

    def Delete(self, event):
        """Deletes an operation"""
        data_pos, exp_pos = self.get_expression_position()
        if exp_pos != -1 and data_pos != -1:
            self.ds_sim_list[data_pos].expressionlist.pop(exp_pos)
            self.Update()
            self._send_change_event()

    def MoveUp(self, event):
        """Move an operation up"""
        pass

    def MoveDown(self, event):
        """Moves an operation down"""
        pass

    def EditPars(self, event):
        """Creates a new parameter"""
        dlg = CustomParametersDialog(
            self, self.plugin.GetModel(), self.model_script_interactor.custom_parameters_interactors
        )
        if dlg.ShowModal() == wx.ID_OK:
            new_interactors = dlg.GetObjectList()
            replace_list_items(self.model_script_interactor.custom_parameters_interactors, new_interactors)
            self.Update()
            self._send_change_event()
        dlg.Destroy()

    def OnDataChanged(self, event):
        """Update the data list"""
        self.update_listbox()


class EditList(wx.Panel):
    def __init__(
        self,
        parent,
        object_list=None,
        default_name="object",
        edit_dialog=None,
        taken_names=None,
        id=-1,
        edit_dialog_name="object editor",
        undeletable_names=None,
    ):
        """A List control that allows editing of objects

        Parameters:
            object_list (list): A list of object to edit.
            edit_dialog (wx.Dialog): A dialog that can edit an item in the list.
            edit_dialog_name (string): Title of the edit dialog
            default_name (string): The default name (a number is appended) of the object.
            taken_names (list of strings): Names not allowed to be used for objects.
            undeletable_names (list of strings): Names of objects that are not allowed to be deleted or has their
                                                names changed.

        Returns:
            EditList
        """
        if undeletable_names is None:
            undeletable_names = []
        self.parent = parent
        if not object_list:
            object_list = []
        self.object_list = object_list
        self.edit_dialog = edit_dialog
        self.edit_dialog_name = edit_dialog_name
        self.undeletable_names = undeletable_names
        self.parent = parent
        self.default_name = default_name
        if not taken_names:
            self.taken_names = []
        else:
            self.taken_names = taken_names

        wx.Panel.__init__(self, parent, id=id)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add((-1, 2))

        # Toolbar
        self.toolbar = self.do_toolbar()
        main_sizer.Add(self.toolbar, proportion=0, flag=wx.EXPAND)
        main_sizer.Add((-1, 2))

        # Listbox for objects
        self.listbox = wx.ListBox(self, choices=[obj.get_name() for obj in self.object_list], style=wx.LB_SINGLE)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListBoxDClick)
        main_sizer.Add(self.listbox, 1, wx.EXPAND)

        self.SetSizer(main_sizer)
        self.toolbar.Realize()

    def do_toolbar(self):
        """Create and do the toolbar"""
        toolbar = wx.ToolBar(self, style=wx.TB_FLAT | wx.TB_HORIZONTAL)
        dpi_scale_factor = wx.GetApp().dpi_scale_factor
        tb_bmp_size = int(dpi_scale_factor * 20)

        button_names = ["Insert", "Delete", "User Variables"]

        button_names = ["Insert", "Delete", "Move Up", "Move Down"]
        button_images = [
            wx.Bitmap(icons.add.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.delete.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.move_up.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.move_down.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
        ]
        callbacks = [self.OnInsert, self.OnDelete, self.OnMoveUp, self.OnMoveDown]
        tooltips = ["Insert item", "Delete item", "Move item up", "Move item down"]

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

        return toolbar

    def set_undeletable_names(self, names):
        """Sets the undeletable_names"""
        self.undeletable_names = names

    def set_taken_names(self, names):
        """Sets the taken names"""
        for obj in self.object_list:
            try:
                names.remove(obj.name)
            except ValueError:
                pass
        self.taken_names = names

    def _send_change_event(self):
        """ "Sends a change event"""
        evt = InteractorChangedEvent(myEVT_INTERACTOR_CHANGED, self.GetId())
        self.GetEventHandler().ProcessEvent(evt)

    def OnListBoxDClick(self, event):
        """Event handler for double click on item in listbox"""
        # print "OnDoubleClick"
        ind = self.listbox.GetSelection()
        obj = self.object_list[ind]
        if self.edit_dialog is not None:
            dialog = self.edit_dialog(
                self,
                obj,
                taken_names=self.taken_names,
                title=self.edit_dialog_name,
                edit_name=obj.name not in self.undeletable_names,
            )
            if dialog.ShowModal() == wx.ID_OK:
                new_obj = dialog.GetObject()
                # print new_obj.get_code()
                self.object_list[ind] = new_obj
                self.listbox.SetItems([obj.get_name() for obj in self.object_list])
                # print new_obj.c
                # print type(new_obj.c)
                self._send_change_event()
            dialog.Destroy()

    def OnInsert(self, event):
        """Event handler for inserting an item"""
        ind = self.listbox.GetSelection()
        if True:  # self.default_class is not None:
            # Find a new name:
            i = 0
            name = self.default_name + str(i)
            while i < 999 and name in self.listbox.GetStrings():
                i += 1
                name = self.default_name + str(i)
            new_obj = self.object_list[0].create_new()
            new_obj.set_name(name)
            if ind < 0:
                # Not item selected add new onject to the end
                self.object_list.append(new_obj)
                self.listbox.Append(self.object_list[-1].get_name())
            else:
                self.object_list.insert(ind, new_obj)
                self.listbox.Insert(self.object_list[ind].get_name(), ind)
            self._send_change_event()

    def OnDelete(self, event):
        """Event handler for deleting an item"""
        if len(self.object_list) > 1:
            ind = self.listbox.GetSelection()
            if not self.object_list[ind].name in self.undeletable_names:
                self.object_list.pop(ind)
                self.listbox.Delete(ind)
            else:
                ShowInfoDialog(self.parent, "Object %s is not allowed to delete" % self.object_list[ind].name)
        else:
            ShowInfoDialog(self.parent, "At least one item has to be left")
        self._send_change_event()

    def OnMoveUp(self, event):
        """Event handler for moving an item up"""
        ind = self.listbox.GetSelection()
        if ind > 0:
            obj = self.object_list.pop(ind)
            self.listbox.Delete(ind)
            self.object_list.insert(ind - 1, obj)
            self.listbox.Insert(obj.get_name(), ind - 1)
            self.listbox.SetSelection(ind - 1)
            self._send_change_event()

    def OnMoveDown(self, event):
        """Event handler for moving an item down"""
        ind = self.listbox.GetSelection()
        if len(self.object_list) - 1 > ind >= 0:
            obj = self.object_list.pop(ind)
            self.listbox.Delete(ind)
            self.object_list.insert(ind + 1, obj)
            self.listbox.Insert(obj.get_name(), ind + 1)
            self.listbox.SetSelection(ind + 1)
            self._send_change_event()


class DomainListCtrl(wx.Panel):
    def __init__(
        self,
        parent,
        id=-1,
        domain_list=None,
        slab_list=None,
        unitcell_list=None,
        taken_names=None,
        symmetries=None,
        sample_list=None,
    ):
        self.sample_list = sample_list

        wx.Panel.__init__(self, parent, id)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add((-1, 2))

        # DomainListCtrl
        self.listbox = DomainWidget(
            self,
            id=-1,
            domain_list=domain_list,
            slab_list=slab_list,
            unitcell_list=unitcell_list,
            taken_names=taken_names,
            symmetries=symmetries,
        )
        self.Bind(EVT_INTERACTOR_CHANGED, self._send_change_event, self.listbox)
        self.Bind(EVT_SELECTION_CHANGED, self._send_selection_change_event, self.listbox)

        # Toolbar
        self.toolbar = self.do_toolbar()
        main_sizer.Add(self.toolbar, proportion=0, flag=wx.EXPAND)
        main_sizer.Add((-1, 2))

        main_sizer.Add(self.listbox, 1, wx.EXPAND)

        self.SetSizer(main_sizer)
        self.toolbar.Realize()

    def do_toolbar(self):
        """Create and do the toolbar"""
        toolbar = wx.ToolBar(self, style=wx.TB_FLAT | wx.TB_HORIZONTAL)
        dpi_scale_factor = wx.GetApp().dpi_scale_factor
        tb_bmp_size = int(dpi_scale_factor * 20)

        button_names = ["Insert", "Delete", "User Variables"]

        button_names = [
            "Insert Slab",
            "Insert Domain",
            "Delete",
            "Move Up",
            "Move Down",
            "Copy",
            "Cut",
            "Paste",
            "Paste as New",
            "Domain Editor",
            "Sample Editor",
        ]
        button_images = [
            wx.Bitmap(sxrd_images.insert_layer.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.insert_domain.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.delete.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.move_up.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(icons.move_down.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.copy.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.cut.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.paste.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.paste_new.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.domain.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
            wx.Bitmap(sxrd_images.sample.GetImage().Scale(tb_bmp_size, tb_bmp_size)),
        ]
        callbacks = [
            self.OnNewSlab,
            self.OnNewDomain,
            self.OnDelete,
            self.listbox.OnMoveUp,
            self.listbox.OnMoveDown,
            self.listbox.OnCopy,
            self.listbox.OnCut,
            self.listbox.OnPaste,
            self.listbox.OnPasteClone,
            self.OnDomainEdit,
            self.OnSampleEdit,
        ]
        tooltips = [
            "Insert Slab",
            "Insert Domain",
            "Delete item",
            "Move item up",
            "Move item down",
            "Copy",
            "Cut",
            "Paste",
            "Paste as New",
            "Domain Editor",
            "Sample Editor",
        ]

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

        return toolbar

    def OnNewSlab(self, event):
        """Add a new Slab"""
        if self.listbox.selected_item[1] == -1:
            self.listbox.selected_item = (self.listbox.selected_item[0], 0)
        self.listbox.OnNew(event)

    def OnNewDomain(self, event):
        """Add a new domain."""
        self.listbox.selected_item = (self.listbox.selected_item[0], -1)
        self.listbox.OnNew(event)

    def OnDelete(self, event):
        """On deleting an item"""
        self.listbox.OnDelete(event)
        domains = [domain.name for domain in self.listbox.domain_list]
        self.sample_list[0].domains = domains
        self._send_change_event(None)

    def OnSampleEdit(self, event):
        """On edit the sample parameters"""
        if self.sample_list:
            dialog = ObjectDialog(self, self.sample_list[0], title="Sample editor", edit_name=False)
            if dialog.ShowModal() == wx.ID_OK:
                new_obj = dialog.GetObject()
                self.sample_list[0] = new_obj
                self._send_change_event(None)
            dialog.Destroy()

    def OnDomainEdit(self, event):
        """On edit a domain by toolbar icon"""
        # Make the domain the selected item
        self.listbox.selected_item = (self.listbox.selected_item[0], -1)
        self.listbox.OnEditItem()

    def get_selected_domain_name(self):
        """Returns the selected domain domain name None if nothing is selected"""
        selection = self.listbox.selected_item
        if selection[0] > -1:
            return self.listbox.domain_list[selection[0]].name
        else:
            return None

    def get_selected_slab_name(self):
        """Returns the selected domain domain name None if nothing is selected"""
        selection = self.listbox.selected_item
        if selection[1] > -1 and selection[0] > -1:
            return self.listbox.domain_list[selection[0]].slabs[selection[1]].name
        else:
            return None

    def set_taken_names(self, taken_names):
        self.listbox.set_taken_names(taken_names)

    def set_symmetries(self, symmetries):
        self.listbox.set_symmetries(symmetries)

    def _send_change_event(self, event):
        """ "Sends a change event"""
        # Just Process a new event upwards.
        evt = InteractorChangedEvent(myEVT_INTERACTOR_CHANGED, self.GetId())
        self.GetEventHandler().ProcessEvent(evt)

    def _send_selection_change_event(self, event):
        """Sends an selection change event"""
        evt = SelectionChangedEvent(myEVT_SELECTION_CHANGED, self.GetId())
        self.GetEventHandler().ProcessEvent(evt)


class DomainWidget(wx.ScrolledWindow):
    def __init__(
        self, parent, id=-1, domain_list=None, slab_list=None, unitcell_list=None, taken_names=None, symmetries=None
    ):
        """An editor to edit a sample with multiple domains.

        Parameters:
            parent (wx.Window): Parent window
            id (int): id of the window
            domain_list: A list of domain interactors
            slab_list: A list of slab interactors
            unitcell_list: A list of unit cell interactors
            taken_name: A list of strings representing forbidden names
            symmetries: A list of symmetry names available

        Returns:
            SampleEditor
        """
        wx.ScrolledWindow.__init__(self, parent, id)
        self.parent = parent

        self.domain_list = domain_list
        if not self.domain_list:
            self.domain_list = []
        self.slab_list = slab_list
        if not self.slab_list:
            self.slab_list = []
        self.unitcell_list = unitcell_list
        if not self.unitcell_list:
            self.unitcell_list = []
        self.taken_names = taken_names
        if not self.taken_names:
            self.taken_names = []
        self.symmetries = symmetries
        if not self.symmetries:
            self.symmetries = []

        self.header_rects = []
        self.list_rects = []
        self.selected_item = (-1, -1)
        self.clipboard = None
        self.clipboard_type = ""

        self.select_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)

    def set_symmetries(self, symmetries):
        self.symmetries = symmetries

    def set_taken_names(self, taken_names):
        """Set the taken names"""
        self.taken_names = taken_names

    def OnMouse(self, event):
        if event.LeftDown():
            self.IdentifyEventItem(event)
        elif event.RightDown():
            self.IdentifyEventItem(event)
            self.OnContextMenu()
        elif event.LeftDClick():
            self.IdentifyEventItem(event)
            self.OnEditItem()

    def OnContextMenu(self):
        """Display a popup menu for the domains."""
        if not hasattr(self, "copy_id"):
            self.copy_id = wx.NewId()
            self.cut_id = wx.NewId()
            self.paste_id = wx.NewId()
            self.paste_clone_id = wx.NewId()
            self.insert_id = wx.NewId()
            self.delete_id = wx.NewId()
            self.new_id = wx.NewId()

            self.Bind(wx.EVT_MENU, self.OnCopy, id=self.copy_id)
            self.Bind(wx.EVT_MENU, self.OnCut, id=self.cut_id)
            self.Bind(wx.EVT_MENU, self.OnPaste, id=self.paste_id)
            self.Bind(wx.EVT_MENU, self.OnPasteClone, id=self.paste_clone_id)
            self.Bind(wx.EVT_MENU, self.OnDelete, id=self.delete_id)
            self.Bind(wx.EVT_MENU, self.OnNew, id=self.new_id)

        menu = wx.Menu()
        if True:
            self.insert_menu = wx.Menu()
            for slab in self.slab_list:
                new_id = wx.NewId()
                self.insert_menu.Append(new_id, slab.name)
                self.Bind(wx.EVT_MENU, self.OnInsert, id=new_id)
            menu.Append(self.insert_id, "Insert", self.insert_menu)
        if self.selected_item[1] != 0:
            menu.Append(self.delete_id, "Delete")

        menu.AppendSeparator()
        menu.Append(self.new_id, "New...")
        menu.AppendSeparator()

        menu.Append(self.copy_id, "Copy")
        if self.selected_item[1] != 0:
            # You should not be allowed to cut a bulk slab
            menu.Append(self.cut_id, "Cut")
        menu.Append(self.paste_id, "Paste")
        menu.Append(self.paste_clone_id, "Paste As New")
        self.PopupMenu(menu)
        menu.Destroy()

    def find_slab_by_name(self, slab_name):
        """Finds the slab by the given name"""
        selected_slab = None
        for slab in self.slab_list:
            if slab.name == slab_name:
                selected_slab = slab

        if slab is None:
            raise ValueError("Could not find slab named %s" % slab_name)

        return selected_slab

    def set_slab_by_name(self, slab_name, new_slab):
        """Sets the slab given the name"""
        item = -1
        for i, slab in enumerate(self.slab_list):
            if slab.name == slab_name:
                item = i
                break

        if item == -1:
            raise ValueError("Could not find slab named %s" % slab_name)

        self.slab_list[item] = new_slab

    def set_clipboard(self, item, item_type="slab"):
        """Set the clipboard with item, item_type is the type of the item"""
        if item_type == "domain":
            self.clipboard = item.copy()
        else:
            self.clipboard = item
        self.clipboard_type = item_type

    def is_clipboard_slab(self):
        return self.clipboard_type == "slab"

    def is_clipboard_domain(self):
        return self.clipboard_type == "domain"

    def replace_slab_name(self, slab_name, new_slab_name):
        """Replaced slab_name with new_slab_name"""
        for d in self.domain_list:
            for i, s in enumerate(d.slabs):
                if s == slab_name:
                    d.slabs[i] = new_slab_name
            if d.bulk_slab == slab_name:
                d.bulk_slab = new_slab_name

    def _send_change_event(self):
        """ "Sends a change event"""
        evt = InteractorChangedEvent(myEVT_INTERACTOR_CHANGED, self.GetId())
        self.GetEventHandler().ProcessEvent(evt)

    def _send_selection_change_event(self, event):
        """Sends an selection change event"""
        evt = SelectionChangedEvent(myEVT_SELECTION_CHANGED, self.GetId())
        self.GetEventHandler().ProcessEvent(evt)

    def OnEditItem(self):
        """Edit an item in the list"""
        if self.selected_item[0] > -1:
            domain = self.domain_list[self.selected_item[0]]
            if self.selected_item[1] > -1:
                if self.selected_item[1] == 0:
                    # The item is the bulk slab
                    slab_name = domain.bulk_slab
                else:
                    # The item is a surface slab
                    slab_name = domain.slabs[self.selected_item[1] - 1]
                slab = self.find_slab_by_name(slab_name)

                tnames = self.taken_names[:]
                try:
                    tnames.remove(slab.name)
                except ValueError:
                    pass
                dialog = SlabDialog(self, slab, taken_names=tnames)
                if dialog.ShowModal() == wx.ID_OK:
                    new_slab = dialog.GetObject()
                    self.set_slab_by_name(slab_name, new_slab)
                    if slab_name != new_slab.name:
                        # The name has changed - needs updating
                        self.replace_slab_name(slab_name, new_slab.name)
                    self._send_change_event()
            else:
                # We edit a domain
                tnames = self.taken_names[:]
                try:
                    tnames.remove(domain.name)
                except ValueError:
                    pass
                unit_cells = [uc.name for uc in self.unitcell_list]
                dialog = DomainDialog(
                    self, domain, taken_names=tnames, unit_cells=unit_cells, symmetries=self.symmetries
                )
                if dialog.ShowModal() == wx.ID_OK:
                    new_domain = dialog.GetObject()
                    self.domain_list[self.selected_item[0]] = new_domain
                    self._send_change_event()
            self.Refresh()

    def OnNew(self, event):
        """Create a new object, either a domain or a slab"""
        if self.selected_item[0] > -1:
            if self.selected_item[1] == -1:
                # A domain has been selected
                new_domain = self.domain_list[0].create_new()
                new_domain.name = "domain"
                new_domain = self.rename_domain(new_domain, "")
                new_domain.bulk_slab = self.slab_list[-1].name
                if len(self.unitcell_list) > 0:
                    new_domain.unitcell = self.unitcell_list[0].name
                self.domain_list.insert(self.selected_item[0] + 1, new_domain)
            elif self.selected_item[1] == 0:
                # The bulk slab has been selected
                new_slab = self.slab_list[0].create_new()
                new_slab.name = "slab"
                new_slab = self.rename_slab(new_slab, "")
                self.slab_list.append(new_slab)
                self.domain_list[self.selected_item[0]].bulk_slab = new_slab.name
            else:
                # A surface slab has been selected
                new_slab = self.slab_list[0].create_new()
                new_slab.name = "slab"
                new_slab = self.rename_slab(new_slab, "")
                self.slab_list.append(new_slab)
                self.domain_list[self.selected_item[0]].slabs.insert(self.selected_item[1], new_slab.name)
                self.selected_item = (self.selected_item[0], self.selected_item[1] + 1)
            self._send_change_event()
            self.Refresh()

    def OnDelete(self, event):
        """Delete an slab from the domain"""
        if self.selected_item[0] > -1:
            domain = self.domain_list[self.selected_item[0]]
            if self.selected_item[1] > -1:
                if self.selected_item[1] > 0:
                    name = domain.slabs.pop(self.selected_item[1] - 1)
                    # Check if there are any slabs with name left
                    name_exist = False
                    for d in self.domain_list:
                        if name in d.slabs:
                            name_exist = True
                            break
                    if not name_exist:
                        result = ShowQuestionDialog(
                            self.parent, "The slab %s is no longer in use, do you want to delete it?" % name, "Delete"
                        )
                        if result:
                            index = None
                            for i, s in enumerate(self.slab_list):
                                if s.name == name:
                                    index = i
                                    break
                            self.slab_list.pop(index)
                    self.selected_item = (self.selected_item[0], self.selected_item[1] - 1)
                    self._send_change_event()
            else:
                if len(self.domain_list) > 1:
                    self.domain_list.pop(self.selected_item[0])
                    self._send_change_event()
            self.Refresh()

    def OnInsert(self, event):
        """Handle an insert of an slab in the sample"""
        slab_name = event.GetEventObject().FindItemById(event.GetId()).GetLabel()

        if self.selected_item[0] > -1:
            domain = self.domain_list[self.selected_item[0]]
            if self.selected_item[1] > -1:
                if self.selected_item[1] == 0:
                    # The item is the bulk slab
                    domain.bulk_slab = slab_name
                else:
                    # The item is a surface slab
                    domain.slabs.insert(self.selected_item[1] - 1, slab_name)
            else:
                domain.slabs.append(slab_name)
            self._send_change_event()
            self.Refresh()

    def OnCopy(self, event):
        """Called to copy an item the position which is copied is set by selected_item"""
        if self.selected_item[0] > -1:
            domain = self.domain_list[self.selected_item[0]]
            if self.selected_item[1] > -1:
                if self.selected_item[1] == 0:
                    # The item is the bulk slab
                    self.set_clipboard(domain.bulk_slab)
                else:
                    # The item is a surface slab
                    self.set_clipboard(domain.slabs[self.selected_item[1] - 1])
            else:
                self.set_clipboard(domain, item_type="domain")

    def OnCut(self, event):
        """Called to cut an item from the position set by the selected item"""
        if self.selected_item[0] > -1:
            domain = self.domain_list[self.selected_item[0]]
            if self.selected_item[1] > -1:
                if self.selected_item[1] == 0:
                    # The item is the bulk slab
                    self.set_clipboard(domain.bulk_slab)
                else:
                    # The item is a surface slab
                    self.set_clipboard(domain.slabs.pop(self.selected_item[1] - 1))
            else:
                domain = self.domain_list.pop(self.selected_item[0])
                self.set_clipboard(domain, item_type="domain")
            self.Refresh()

    def OnMoveUp(self, event):
        """Called to move an slab upwards"""
        if self.selected_item[0] > -1 and self.selected_item[1] > 0:
            domain = self.domain_list[self.selected_item[0]]
            if len(domain.slabs) > self.selected_item[1]:
                # We don't move slabs...
                slab = domain.slabs.pop(self.selected_item[1] - 1)
                domain.slabs.insert(self.selected_item[1], slab)
                self.selected_item = (self.selected_item[0], self.selected_item[1] + 1)
                self._send_change_event()
                self.Refresh()

    def OnMoveDown(self, event):
        """Called to move an slab down"""
        if self.selected_item[0] > -1 and self.selected_item[1] > 0:
            domain = self.domain_list[self.selected_item[0]]
            if self.selected_item[1] > 1:
                # We don't move slabs...
                slab = domain.slabs.pop(self.selected_item[1] - 1)
                domain.slabs.insert(self.selected_item[1] - 2, slab)
                self.selected_item = (self.selected_item[0], self.selected_item[1] - 1)
                self._send_change_event()
                self.Refresh()

    def rename_domain(self, domain, extension):
        domain_names = [d.name for d in self.domain_list]
        new_name = domain.name + extension
        i = 1
        while new_name in domain_names:
            new_name = domain.name + "%s%d" % (extension, i)
            i += 1
        new_domain = domain.copy()
        new_domain.name = new_name
        return new_domain

    def rename_slab(self, slab, extension):
        slab_names = [s.name for s in self.slab_list]
        new_name = slab.name + extension
        i = 1
        while new_name in slab_names:
            new_name = slab.name + "%s%d" % (extension, i)
            i += 1
        new_slab = slab.copy()
        new_slab.name = new_name
        return new_slab

    def OnPaste(self, event):
        """Paste and item at the position given by selected_item"""
        if self.selected_item[0] > -1 and self.clipboard is not None:
            domain = self.domain_list[self.selected_item[0]]
            if self.is_clipboard_domain():
                # We are pasting a domain
                new_domain = self.rename_domain(self.clipboard, "_copy")
                self.domain_list.insert(self.selected_item[0], new_domain)
            else:
                if self.selected_item[1] > -1:
                    if self.selected_item[1] == 0:
                        # The item is the bulk slab
                        domain.bulk_slab = self.clipboard
                    else:
                        # The item is a surface slab
                        domain.slabs.insert(self.selected_item[1] - 1, self.clipboard)
                else:
                    # We are pasting a slab onto a domain header...
                    domain.slabs.append(self.clipboard)
            self._send_change_event()
            self.Refresh()

    def OnPasteClone(self, event):
        """Pastes an item as an new item that is decoupled from the copied"""
        if self.selected_item[0] > -1 and self.clipboard is not None:
            domain = self.domain_list[self.selected_item[0]]
            if self.is_clipboard_domain():
                # We are pasting a domain
                new_domain = self.rename_domain(self.clipboard, "_copy")
                self.domain_list.insert(self.selected_item[0], new_domain)
            else:
                new_slab = self.find_slab_by_name(self.clipboard).copy()
                new_slab = self.rename_slab(new_slab, "_clone")
                self.slab_list.append(new_slab)
                if self.selected_item[1] > -1:
                    if self.selected_item[1] == 0:
                        # The item is the bulk slab
                        domain.bulk_slab = new_slab.name
                    else:
                        # The item is a surface slab
                        domain.slabs.insert(self.selected_item[1] - 1, new_slab.name)
                else:
                    # We are pasting a slab onto a domain header...
                    domain.slabs.append(new_slab.name)
            self._send_change_event()
            self.Refresh()

    def IdentifyEventItem(self, event):
        """Identify the item in the domain that created where at the position"""
        self.selected_item = (-1, -1)
        for i, rect in enumerate(self.header_rects):
            if rect.Contains(event.GetPosition()):
                self.selected_item = (i, -1)
                self.Refresh()
                self._send_selection_change_event(event)
                return
        for i, rects in enumerate(self.list_rects):
            for j, rect in enumerate(rects):
                if rect.Contains(event.GetPosition()):
                    self.selected_item = (i, len(rects) - 1 - j)
                    self._send_selection_change_event(event)
                    self.Refresh()

    def OnPaint(self, event):
        dc = wx.GCDC(wx.PaintDC(self))
        # self.PrepareDC(dc)
        render = wx.RendererNative.Get()
        x_padding = 40
        y_padding = 5
        x_start = 5
        y_start = 5

        # Determine column sizes and such beforehand
        col_width = 0
        col_height = 0
        max_slabs = 0
        for domain in self.domain_list:
            for name in [domain.name, domain.bulk_slab] + domain.slabs:
                size = self.GetTextExtent(str(name))
                if size[0] > col_width:
                    col_width = size[0]
                if size[1] > col_height:
                    col_height = size[1]
            if len(domain.slabs) > max_slabs:
                max_slabs = len(domain.slabs)
        col_width += x_padding

        max_width = col_width * len(self.domain_list)
        max_height = (col_height + y_padding) * (max_slabs + 2)
        self.SetVirtualSize((x_start + max_width, y_start + max_height))
        self.SetScrollRate(20, 20)

        xv, yv = self.GetViewStart()
        dx, dy = self.GetScrollPixelsPerUnit()
        x_orig, y_orig = (xv * dx, yv * dy)
        rgn = self.GetUpdateRegion()
        rgn.Offset(-x_orig, -y_orig)
        r = rgn.GetBox()
        # draw to the dc using the calculated clipping rect
        dc.SetClippingRegion(x_start, y_start, x_start + max_width, y_start + max_height)

        # Draw the background
        pen = dc.GetPen()
        pen.SetColour(wx.WHITE)
        dc.SetPen(pen)
        x_pos = x_start - x_orig
        dc.DrawRectangle(int(x_pos), int(y_start - y_orig), int(max_width), int(max_height))
        pen.SetColour("GREY")
        dc.SetPen(pen)
        brush = wx.Brush(wx.WHITE, style=wx.BRUSHSTYLE_TRANSPARENT)
        # Drawing the line for the bulk slab
        dc.SetPen(wx.Pen("GREY", style=wx.PENSTYLE_DOT))
        y = (col_height + y_padding) * (max_slabs + 1) + y_padding / 2.0 - y_padding / 4.0 - y_orig
        dc.DrawLine(int(x_pos), int(y), int(x_pos + max_width), int(y))

        sel_brush = wx.Brush(self.select_colour, style=wx.BRUSHSTYLE_SOLID)
        sel_pen = wx.Pen(wx.WHITE, style=wx.PENSTYLE_TRANSPARENT)
        self.header_rects = []
        self.list_rects = []
        for i, domain in enumerate(self.domain_list):
            # Drawing the slabs
            y_pos = (col_height + y_padding) * (max_slabs - len(domain.slabs) + 1) + y_padding / 2.0 - y_orig
            self.list_rects.append([])
            j = len(domain.slabs)
            for slab_name in reversed([domain.bulk_slab] + domain.slabs):
                rect = wx.Rect(
                    int(x_pos + x_padding / 4.0),
                    int(y_pos + y_padding / 4.0),
                    int(col_width - x_padding / 2.0),
                    int(col_height + y_padding / 2.0),
                )
                sel_rect = wx.Rect(
                    int(x_pos), int(y_pos - y_padding / 4.0), int(col_width), int(col_height + y_padding)
                )
                if self.selected_item[0] == i and self.selected_item[1] == j:
                    # render.DrawItemSelectionRect(self, dc, sel_rect, wx.CONTROL_SELECTED)
                    dc.SetBrush(sel_brush)
                    dc.SetPen(sel_pen)
                    dc.DrawRectangle(int(sel_rect.X), int(sel_rect.Y), int(sel_rect.Width), int(sel_rect.Height))
                dc.SetPen(pen)
                dc.SetBrush(brush)
                dc.DrawRectangle(int(rect.X), int(rect.Y), int(rect.Width), int(rect.Height))
                dc.DrawText(slab_name, int(x_pos + x_padding / 2), int(y_pos))
                self.list_rects[-1].append(rect)
                y_pos += col_height + y_padding
                j -= 1
            if self.selected_item[0] == i and self.selected_item[1] == -1:
                flag = wx.CONTROL_SELECTED
            else:
                flag = wx.CONTROL_CURRENT
            opts = wx.HeaderButtonParams()
            opts.m_labelText = domain.name
            rect = wx.Rect(x_pos, y_start, col_width, col_height)
            self.header_rects.append(rect)
            render.DrawHeaderButton(self, dc, rect, flag, params=opts)

            x_pos += col_width


class ObjectDialog(wx.Dialog):
    def __init__(
        self, parent, object_interactor, id=-1, taken_names=None, title="Object editor", cols=2, edit_name=True
    ):
        """A dialog to define/edit an ObjectScriptInteractor object

        Parameters:
            parent (wx.Window): Parent of the Dialog.
            object_interactor (ObjectScriptInteractor): The object that should be edited.
            taken_names (list of strings): A list of already taken names.

        Returns:
            ObjectDialog
        """
        self.vertical_buffer = 10
        self.border = 5
        self.not_editable_bkg_color = wx.GREEN

        self.object = object_interactor
        self.edit_name = edit_name
        self.taken_names = taken_names
        if not self.taken_names:
            self.taken_names = []
        self.cols = cols

        wx.Dialog.__init__(self, parent, id, title)

        self.main_layout()

        self.Layout()
        self.Fit()

    def main_layout(self):
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.main_sizer.Add((-1, self.vertical_buffer), 0, wx.EXPAND)

        if self.object.groups:
            # Add the name to the control as well
            name_sizer = wx.FlexGridSizer(rows=1, cols=self.cols, vgap=self.vertical_buffer, hgap=self.border)
            label = wx.StaticText(self, -1, "name" + ": ")
            name_sizer.Add(label, flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=self.border)
            if self.edit_name:
                style = wx.TE_RIGHT | wx.TE_RICH
            else:
                style = wx.TE_RIGHT | wx.TE_RICH | wx.TE_READONLY
            name_ctrl = wx.TextCtrl(
                self,
                -1,
                str(self.object.name),
                validator=cust_dia.NoMatchValidTextObjectValidator(self.taken_names),
                style=style,
            )
            name_sizer.Add(name_ctrl, flag=wx.EXPAND, border=self.border)
            self.main_sizer.Add(name_sizer, 0, wx.EXPAND)

            self.parameter_sizer, self.ctrls = self.grid_layout(self, self.object)
            self.ctrls["name"] = name_ctrl
        else:
            self.parameter_sizer, self.ctrls = self.layout_group(self, self.object, self.object.parameters)

        self.main_sizer.Add(self.parameter_sizer, 1, wx.EXPAND)
        self.main_sizer.Add((-1, self.vertical_buffer), 0, wx.EXPAND)

        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        self.main_sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP | wx.LEFT, self.border)
        self.main_sizer.Add(self.create_buttons(), 0, wx.EXPAND)

        self.SetSizer(self.main_sizer)

    def create_buttons(self):
        buttons = wx.StdDialogButtonSizer()  # wx.BoxSizer(wx.HORIZONTAL)
        b = wx.Button(self, wx.ID_OK, "OK")
        b.SetDefault()
        buttons.AddButton(b)
        buttons.AddButton(wx.Button(self, wx.ID_CANCEL, "Cancel"))
        buttons.Realize()
        return buttons

    def grid_layout(self, parent, object_interactor):
        """Do a more advanced layout with subboxes"""
        rows = math.ceil(len(object_interactor.parameters) / (self.cols * 1.0))
        sizer = wx.FlexGridSizer(rows=rows, cols=self.cols, vgap=self.vertical_buffer, hgap=self.border)
        tc = {}
        for group in object_interactor.groups:
            if type(group[0]) != str:
                raise TypeError("First item in a group has to be a string")
            # Make the box for putting in the columns
            col_box = wx.StaticBox(parent, -1, group[0])
            col_box_sizer = wx.StaticBoxSizer(col_box, wx.VERTICAL)
            group, group_tc = self.layout_group(self, object_interactor, group[1])
            col_box_sizer.Add(group, flag=wx.EXPAND, border=self.border)
            for item in group_tc:
                tc[item] = group_tc[item]
            sizer.Add(col_box_sizer, flag=wx.EXPAND)
        return sizer, tc

    def layout_group(self, parent, object_interactor, parameters):
        layout_cols = 3
        sizer = wx.FlexGridSizer(len(parameters) + 1, layout_cols, vgap=self.vertical_buffer, hgap=self.border)
        tc = {}
        for par in parameters:
            label = wx.StaticText(parent, -1, par + ": ")
            validator = object_interactor.validators[par]
            val = getattr(object_interactor, par)
            editable = object_interactor.is_editable(par)
            choices = object_interactor.get_choices(par)

            tc[par] = self.crete_edit_ctrl(parent, par, val, validator, editable, choices)

            sizer.Add(label, flag=wx.ALIGN_RIGHT, border=self.border)
            sizer.Add(tc[par], flag=wx.EXPAND, border=self.border)

            # Add the unit
            unit_label = wx.StaticText(parent, -1, " " + object_interactor.get_unit(par))
            sizer.Add(unit_label, flag=wx.ALIGN_LEFT, border=self.border)

        return sizer, tc

    def crete_edit_ctrl(self, parent, par, val, validator, editable, choices=None):
        """Creates the edit control for a parameter

        Parameters:
            editable: flag to indicate if the parameter is editable
            par: parameter name
            parent: parent of the control
            val: value of the parameter
            validator: validator of the control
            choices (list): A list of choices

        Returns:
            ctrl (wx.Control): a wx control object
        """
        if choices:
            ctrl = wx.Choice(parent, -1, choices=choices)
            # Set the position that matches cal
            pos = 0
            if isinstance(val, str) or isinstance(val, str):
                pos = choices.index(val)
            elif isinstance(val, int):
                pos = par
            ctrl.SetSelection(pos)
        else:
            # The object is a validator
            validator = validator.Clone()
            ctrl = wx.TextCtrl(parent, -1, str(val), validator=validator, style=wx.TE_RIGHT | wx.TE_RICH)
            # Create a non-editable box if needed
            ctrl.SetEditable(editable)
            if not editable:
                ctrl.SetBackgroundColour(self.not_editable_bkg_color)
        return ctrl

    def GetObject(self):
        """Returns a new object that is updated according to the inputs"""
        new_object = self.object.copy()
        parameters = ["name"] + self.object.parameters
        for par in parameters:
            if isinstance(self.ctrls[par], wx.Choice):
                text = self.ctrls[par].GetString(self.ctrls[par].GetCurrentSelection())
            else:
                text = self.ctrls[par].GetValue()
            setattr(new_object, par, text)
        return new_object


class DomainDialog(wx.Dialog):
    def make_col1(self):
        row = 0
        col = 0
        # add the name and the rest of the "normal" parameters in the first column
        for par in self.init_parameters:
            label = wx.StaticText(self, -1, par + ": ")
            self.parameter_sizer.Add(label, (row, col), flag=wx.ALIGN_RIGHT)
            val = getattr(self.domain, par)
            if par != "name":
                ctrl = wx.TextCtrl(
                    self, -1, str(val), validator=detemine_validator(val), style=wx.TE_RIGHT | wx.TE_RICH, size=(-1, -1)
                )
            else:
                ctrl = wx.TextCtrl(
                    self,
                    -1,
                    str(val),
                    validator=cust_dia.NoMatchValidTextObjectValidator(self.taken_names),
                    style=wx.TE_RIGHT | wx.TE_RICH,
                    size=(-1, -1),
                )
            self.parameter_sizer.Add(ctrl, (row, col + 1), flag=wx.ALIGN_LEFT)
            row += 1

    def make_col2(self):
        row = 0
        col = 2
        for par, choices, val in [
            ("unit cell", self.unit_cells, self.domain.unitcell),
            ("surface sym.", self.symmetries, self.domain.surface_sym),
            ("bulk sym.", self.symmetries, self.domain.bulk_sym),
        ]:
            label = wx.StaticText(self, -1, par + ": ")
            self.parameter_sizer.Add(label, (row, col), flag=wx.ALIGN_RIGHT)
            ctrl = wx.Choice(self, -1, choices=choices)
            pos = ctrl.FindString(val)
            if pos == wx.NOT_FOUND:
                pos = 0
            ctrl.SetSelection(pos)
            self.parameter_sizer.Add(ctrl, (row, col + 1), flag=wx.ALIGN_LEFT)
            row += 1

    def __init__(self, parent, domain_interactor, id=-1, taken_names=None, symmetries=None, unit_cells=None):
        """A dialog to define a domain. This editor only edits the non-slab related parameters.

        Parameters:
            parent (wx.Window): Parent of the Dialog.
            domain_interactor (DomainInteractor): The domain that should be edited.
            taken_names (list of strings): A list of already taken names.
            symmetries (list of strings): A list of the names of the  symmetries that can be set.
            unitcells (list of strings): A list of the names of the unitcells objects

        Returns:
            DomainDialog
        """
        self.text_ctrl_width = 70
        self.vertical_buffer = 10
        self.border = 5

        self.domain = domain_interactor
        self.taken_names = taken_names
        if not self.taken_names:
            self.taken_names = []
        self.symmetries = symmetries
        if not self.symmetries:
            self.symmetries = []
        self.unit_cells = unit_cells
        if not self.unit_cells:
            self.unit_cells = []

        self.init_parameters = self.domain.parameters
        self.init_defaults = [self.domain.values[par] for par in self.init_parameters]
        self.init_validators = self.domain.validators

        wx.Dialog.__init__(self, parent, id, "Domain editor")

        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add((-1, self.vertical_buffer), 0, wx.EXPAND)

        self.parameter_sizer = wx.GridBagSizer(self.border, self.border)
        main_sizer.Add(self.parameter_sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, border=self.border)
        self.make_col1()  # add the unit cell and symmetries in the next column.
        self.make_col2()  # Main Layout
        main_sizer.Add((-1, self.vertical_buffer), 0, wx.EXPAND)

        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        main_sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP, self.border)
        main_sizer.Add(self.create_buttons(), 0, wx.EXPAND)

        self.SetSizer(main_sizer)
        self.Layout()
        self.Fit()

    def create_buttons(self):
        buttons = wx.StdDialogButtonSizer()  # wx.BoxSizer(wx.HORIZONTAL)
        b = wx.Button(self, wx.ID_OK, "OK")
        b.SetDefault()
        # b.Bind(wx.EVT_BUTTON, self.OnOK)
        buttons.AddButton(b)
        buttons.AddButton(wx.Button(self, wx.ID_CANCEL, "Cancel"))
        buttons.Realize()
        return buttons

    def GetObject(self):
        """Returns the edited object (DomainInteractor)"""
        new_domain = self.domain.copy()
        names = self.init_parameters
        for r, par in enumerate(names):
            ctrl = self.parameter_sizer.FindItemAtPosition((r, 1)).GetWindow()
            setattr(new_domain, par, str(ctrl.GetValue()))
        names = ["unitcell", "surface_sym", "bulk_sym"]
        for r, par in enumerate(names):
            ctrl = self.parameter_sizer.FindItemAtPosition((r, 3)).GetWindow()
            setattr(new_domain, par, str(ctrl.GetStringSelection()))
        return new_domain


class SlabDialog(wx.Dialog):
    def __init__(self, parent, slab_interactor, id=-1, elements=None, taken_names=None):
        """A dialog to define the Slab.

        This assumes that Slab implements the add_atom and __init__ with only keyword arguments.
        The dialog does introspection of the function calls to get out the parameters.
        It also relies on the implementation of code generating functions: generate_code_[func name].

        Parameters:
            parent (wx.Window): Parent of the Dialog.
            slab_interactor (SlabInteractor): The Slab that should be edited.
            elements (list of strings): A list of allowable element names.
            taken_names (list of strings): A list of already taken names.

        Returns:
            SlabDialog
        """
        if elements is None:
            elements = list(utils.__f0_dict__.keys())
        if taken_names is None:
            taken_names = []
        self.text_ctrl_width = 70
        self.atom_panel_left_buffer = 20
        self.vertical_buffer = 10
        self.border = 5

        self.elements = elements
        self.elements.sort()
        self.taken_names = taken_names

        self.slab = slab_interactor
        self.parameters = self.slab.atom_parameters
        self.defaults = [self.slab.atom_defaults[par] for par in self.parameters]
        self.init_parameters = self.slab.parameters
        self.init_defaults = [self.slab.values[par] for par in self.init_parameters]
        self.init_validators = self.slab.validators

        wx.Dialog.__init__(self, parent, id, "Slab editor")
        main_sizer = wx.BoxSizer(wx.VERTICAL)

        slab_box = wx.StaticBox(self, -1, "Slab")
        slab_box_sizer = wx.StaticBoxSizer(slab_box, wx.VERTICAL)
        cols = min(int(len(self.parameters) / 2), len(self.init_parameters) * 2)

        self.slab_sizer = wx.GridBagSizer(self.border, self.border)
        slab_box_sizer.Add(self.slab_sizer, 1, wx.EXPAND)
        row = 0
        col = 0
        for par in self.init_parameters:
            label = wx.StaticText(self, -1, par + ": ")
            self.slab_sizer.Add(label, (row, col), flag=wx.ALIGN_RIGHT)
            val = getattr(self.slab, par)
            if par != "name":
                ctrl = wx.TextCtrl(
                    self, -1, str(val), validator=detemine_validator(val), style=wx.TE_RIGHT | wx.TE_RICH, size=(-1, -1)
                )
            else:
                ctrl = wx.TextCtrl(
                    self,
                    -1,
                    str(val),
                    validator=cust_dia.NoMatchValidTextObjectValidator(self.taken_names),
                    style=wx.TE_RIGHT | wx.TE_RICH,
                    size=(-1, -1),
                )
            self.slab_sizer.Add(ctrl, (row, col + 1), flag=wx.ALIGN_LEFT)
            if col < cols - 1:
                col += 2
            else:
                row += 1
                col = 0

        main_sizer.Add(slab_box_sizer, 0, wx.EXPAND, self.border * 2)

        # Definition of atoms
        atom_box = wx.StaticBox(self, -1, "Atoms")
        atom_box_sizer = wx.StaticBoxSizer(atom_box, wx.VERTICAL)
        # Creating title for the atom definitions
        atom_title_sizer = wx.GridBagSizer(self.border, self.border)
        atom_box_sizer.Add(atom_title_sizer, 0, wx.ALIGN_CENTER)
        for i, par in enumerate(self.parameters):
            atom_title_sizer.Add(wx.StaticText(self, -1, par, size=(self.text_ctrl_width, -1)), (0, i))
        atom_title_sizer.Add((self.atom_panel_left_buffer, -1), (0, i + 1))
        atom_title_sizer.Add((self.atom_panel_left_buffer, -1), (0, i + 2))
        atom_title_sizer.Add((self.atom_panel_left_buffer, -1), (0, i + 3))

        # Creating the panel for the atoms
        self.atom_panel = scrolledpanel.ScrolledPanel(self, -1, style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
        # self.atom_sizer = wx.GridBagSizer(len(self.parameters) + 2, slab.get_len())
        self.atom_sizer = wx.GridBagSizer(self.border, self.border)
        hor_atom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        hor_atom_sizer.Add(self.atom_sizer, 1, wx.EXPAND)
        hor_atom_sizer.Add((self.atom_panel_left_buffer, 10), 0, wx.EXPAND)
        self.atom_panel.SetSizer(hor_atom_sizer)

        # Populating the control with rows
        for i, id in enumerate(slab_interactor.id):
            self.create_atom_row(i, id)
            self.add_atom_buttons(i)
        if len(slab_interactor.id) == 0:
            self.create_atom_row(0)
            self.add_atom_buttons(0)

        # Doing the layout for atoms
        self.atom_panel.SetAutoLayout(True)
        self.atom_panel.SetupScrolling(scroll_x=False)
        atom_box_sizer.Add(self.atom_panel, 1, wx.EXPAND)
        main_sizer.Add(atom_box_sizer, 2, wx.EXPAND, self.border * 2)

        # Main Layout
        main_sizer.Add((-1, self.vertical_buffer), 0, wx.EXPAND)

        line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        main_sizer.Add(line, 0, wx.GROW | wx.RIGHT | wx.TOP, self.border)
        main_sizer.Add(self.create_buttons(), 0, wx.EXPAND)

        self.SetSizer(main_sizer)
        self.Layout()
        # TODO make the min size pixel independent.
        size = (self.atom_sizer.GetSize()[0] + self.atom_panel_left_buffer, 30 * 5)
        self.atom_panel.SetMinSize(size)
        self.Fit()
        self.Layout()

    def create_atom_row(self, row, id=None):
        """Creates a row of input boxes for a atom

        Parameters:
            id (string): If set fetches the values from the slab.
        Returns:
            None
        """

        for i in range(len(self.parameters)):
            if id is None:
                val = self.defaults[i]
                atom_index = 0
            elif self.parameters[i] == "id":
                val = id
                atom_index = self.slab.id.index(id)
            else:
                val = getattr(self.slab, self.parameters[i])[atom_index]
                atom_index = self.slab.id.index(id)
            if self.parameters[i] != "el":
                if self.parameters[i] != "id":
                    validator = detemine_validator(val)
                else:
                    validator = cust_dia.NoMatchTextCtrlValidator([])

                ctrl = wx.TextCtrl(
                    self.atom_panel,
                    -1,
                    str(val),
                    validator=validator,
                    style=wx.TE_RIGHT | wx.TE_RICH,
                    size=(self.text_ctrl_width, -1),
                )
            else:
                ctrl = wx.Choice(
                    self.atom_panel, -1, size=(self.text_ctrl_width, -1), choices=self.elements, style=wx.TAB_TRAVERSAL
                )
                if id is not None:
                    ctrl.SetSelection(ctrl.FindString(val))
            self.atom_sizer.Add(ctrl, (row, i))
        self.update_validators()

    def update_validators(self):
        """Updates the validators for changes in the panels. Currently only updates the id validators"""
        ctrl_list = []
        for i in range(self.atom_sizer.GetRows()):
            ctrl = self.atom_sizer.FindItemAtPosition((i, 0)).GetWindow()
            ctrl.GetValidator().set_nomatch_ctrls(ctrl_list)
            ctrl_list.append(ctrl)

    def add_atom_buttons(self, row):
        """Add a buttons and sizers to a row

        Parameters:
            row (int): The row in the GridBagSizer to add an row to.
        """
        dpi_scale_factor = wx.GetApp().dpi_scale_factor
        tb_bmp_size = int(dpi_scale_factor * 20)

        btn = wx.BitmapButton(self.atom_panel, -1, wx.Bitmap(icons.add.GetImage().Scale(tb_bmp_size, tb_bmp_size)))
        self.atom_sizer.Add(btn, (row, len(self.parameters)))
        self.Bind(wx.EVT_BUTTON, self.OnAddAtomRow, btn)

        btn = wx.BitmapButton(self.atom_panel, -1, wx.Bitmap(icons.delete.GetImage().Scale(tb_bmp_size, tb_bmp_size)))
        self.atom_sizer.Add(btn, (row, len(self.parameters) + 1))
        self.Bind(wx.EVT_BUTTON, self.OnRemoveAtomRow, btn)

    def create_buttons(self):
        buttons = wx.StdDialogButtonSizer()  # wx.BoxSizer(wx.HORIZONTAL)
        b = wx.Button(self, wx.ID_OK, "OK")
        b.SetDefault()
        b.Bind(wx.EVT_BUTTON, self.OnOK)
        buttons.AddButton(b)
        buttons.AddButton(wx.Button(self, wx.ID_CANCEL, "Cancel"))
        buttons.Realize()
        return buttons

    def OnAddAtomRow(self, event):
        """Add an atom row. Callback for add button click"""
        row, col = self.atom_sizer.GetItemPosition(event.EventObject)
        # Move the controls downwards
        rows_to_move = list(range(row, self.atom_sizer.GetRows()))
        rows_to_move.reverse()
        # Move everything except the buttons and buffer for the slider
        for r in rows_to_move:
            for c in range(len(self.parameters)):
                item = self.atom_sizer.FindItemAtPosition((r, c))
                obj = item.GetWindow()
                self.atom_sizer.Detach(obj)
                self.atom_sizer.Add(obj, (r + 1, c))

        # Add the new controls
        self.create_atom_row(row)
        self.add_atom_buttons(self.atom_sizer.GetRows() - 1)
        self.atom_panel.SetAutoLayout(True)
        self.atom_panel.SetupScrolling(scroll_x=False, scrollToTop=False)

    def OnRemoveAtomRow(self, event):
        """Remove a atom row. Callback for delete button click."""
        # We do not allow the last row to get deleted.
        if self.atom_sizer.GetRows() < 2:
            return

        row, col = self.atom_sizer.GetItemPosition(event.EventObject)
        for c in range(len(self.parameters)):
            item = self.atom_sizer.FindItemAtPosition((row, c))
            obj = item.GetWindow()
            self.atom_sizer.Detach(obj)
            obj.Destroy()

        # Move the controls upwards
        rows_to_move = list(range(row + 1, self.atom_sizer.GetRows()))
        # Move everything except the buttons and buffer for the slider
        for r in rows_to_move:
            for c in range(len(self.parameters)):
                item = self.atom_sizer.FindItemAtPosition((r, c))
                obj = item.GetWindow()
                self.atom_sizer.Detach(obj)
                self.atom_sizer.Add(obj, (r - 1, c))

        r = self.atom_sizer.GetRows() - 1
        # Remove the last two buttons
        for c in range(len(self.parameters), self.atom_sizer.GetCols()):
            item = self.atom_sizer.FindItemAtPosition((r, c))
            obj = item.GetWindow()
            self.atom_sizer.Detach(obj)
            # This is needed due to the fact that we might remove the button we clicked - causes a crash otherwise!
            wx.CallAfter(obj.Destroy)

        self.atom_panel.SetAutoLayout(True)
        self.atom_panel.SetupScrolling(scroll_x=False, scrollToTop=False)

        # Update the validators
        wx.CallAfter(self.update_validators)

    def OnOK(self, event):
        """Modified callback for clicking on the OK button"""
        if self.Validate() and self.atom_panel.Validate() and self.TransferDataFromWindow():
            if self.IsModal():
                self.EndModal(wx.ID_OK)
            else:
                self.SetReturnCode(wx.ID_OK)
                self.Show(False)

    def GetObject(self):
        """Returns the edited object (SlabInteractor)"""
        new_slab = self.slab.create_new()
        new_slab.name = self.slab.name
        for c, par in zip(list(range(1, len(self.init_parameters) * 2, 2)), self.init_parameters):
            ctrl = self.slab_sizer.FindItemAtPosition((0, c)).GetWindow()
            setattr(new_slab, par, str(ctrl.GetValue()))

        for r in range(self.atom_sizer.GetRows()):
            kwargs = {}
            for c, par in enumerate(self.parameters):
                ctrl = self.atom_sizer.FindItemAtPosition((r, c)).GetWindow()
                if par != "el":
                    kwargs[par] = str(ctrl.GetValue())
                else:
                    kwargs[par] = str(ctrl.GetStringSelection())
            new_slab.add_atom(**kwargs)
        return new_slab


def detemine_validator(val):
    """From the type of value determine the validator to use"""
    if isinstance(val, str):
        return cust_dia.TextObjectValidator()
    elif isinstance(val, float):
        return cust_dia.FloatObjectValidator()
    else:
        ValueError("Value can not be represented as an Validator")


def replace_list_items(list_a, list_b):
    """Replace all the items in list_a with items from list_b"""
    # Remove all items:
    [list_a.pop() for i in range(len(list_a))]
    # Move items from b to a
    [list_a.append(b) for b in list_b]


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

    def __init__(self, parent, model, expression=None, sim_func=None):
        """Creates a Parameter Expression Dialog that can couple parameters.

        Parameters:
            parent (wx.Window): The parent window
            model (Model): The GenX model
            expression (ParameterExpressionInteractor): The expression
        """
        self.text_ctrl_width = 300
        self.vertical_buffer = 10
        self.border = 5

        wx.Dialog.__init__(self, parent, -1, "Parameter editor")
        self.SetAutoLayout(True)
        self.model = model

        # if not expression:
        #   expression = ParameterExpressionInteractor()

        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=self.border)

        # Get the objects that should be in the choiceboxes
        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)

        if expression is not None:
            obj_pos = [i for i in range(len(objlist)) if objlist[i] == expression.obj_name]
            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] == expression.obj_method
                ]
                if len(func_pos) > 0:
                    self.func_choice.SetSelection(func_pos[0])
                else:
                    raise ValueError(
                        "The method %s for object %s does not exist" % (expression.obj_method, expression.obj_name)
                    )
            else:
                raise ValueError("The object %s does not exist" % expression.obj_name)
        else:
            expression = ParameterExpressionInteractor()
            expression.obj_name = objlist[0]
            expression.obj_method = self.funclist[0][0]

        self.expression = expression

        self.expression_ctrl = cust_dia.ParameterExpressionCombo(
            par_dict, sim_func, self, -1, expression.expression, size=(self.text_ctrl_width, -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))
        button_sizer.Realize()
        self.Bind(wx.EVT_BUTTON, self.OnApply, okay_button)

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

        sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT, border=self.border)
        sizer.Add((-1, self.border))
        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):
        """Callback for apply"""
        expression = self.GetExpression()
        try:
            self.model.eval_in_model(expression.expression)
        except Exception as e:
            result = "Could not evaluate the expression. The python error is: \n" + e.__repr__()
            ShowWarningDialog(self, result, "Error in expression")
        else:
            event.Skip()

    def GetExpression(self):
        """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)

        expression = ParameterExpressionInteractor()
        expression.obj_name = objstr
        expression.obj_method = funcstr
        expression.expression = set_expression

        return expression


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

    def __init__(self, parent, model, data_set_sim_interactor, script_interactor):
        """Creates a SimualtionExpressionDialog.

        Parameters:
            model - a Model object.
            data_set_sim_interactor: A Data Set Simualtion interactor
            script_interactor: A script interactor
        """

        self.model = model
        self.interactor = data_set_sim_interactor
        self.script_interactor = script_interactor

        # 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
        for name in self.script_interactor.get_sim_object_names():
            for info in self.script_interactor.get_sim_methods_info(name):
                len_args = len(info.args)
                if len_args > max_val:
                    max_val = len_args

        if max_val < 0:
            raise ValueError("Could not find simulation methods")

        gbs = wx.GridBagSizer(2, max_val)

        # Creating the column labels
        col_labels = ["Object", "Method", "Instrument"]
        arg_names = None
        for info in self.script_interactor.get_sim_methods_info(self.interactor.obj_name):
            if info.name == self.interactor.obj_method:
                arg_names = info.args
                break
        [col_labels.append(arg) for arg in arg_names if not arg in col_labels]
        self.labels = []
        self.arg_controls = []
        for index in range(3 + 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 > 2:
                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[:3], self.labels[:3]):
            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, arg in zip(
            col_labels[3:], self.labels[3:], self.arg_controls, self.interactor.arguments
        ):
            label.SetLabel(item)
            arg_ctrl.SetValue(arg)
            arg_ctrl.SetEditable(True)

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

        # Creating the controls
        # Object choice control
        self.obj_choice = wx.Choice(self, -1, choices=self.script_interactor.get_sim_object_names())
        self.Bind(wx.EVT_CHOICE, self.on_object_change, self.obj_choice)
        self.obj_choice.SetSelection(self.script_interactor.get_sim_object_names().index(self.interactor.obj_name))
        gbs.Add(self.obj_choice, (1, 0), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)

        # Method choice control
        names = [info.name for info in self.script_interactor.get_sim_methods_info(self.interactor.obj_name)]
        self.method_choice = wx.Choice(self, -1, choices=names)
        self.Bind(wx.EVT_CHOICE, self.on_method_change, self.method_choice)
        self.method_choice.SetSelection(names.index(self.interactor.obj_method))
        gbs.Add(self.method_choice, (1, 1), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5)

        # Instrument choice control
        instruments = [inter.name for inter in self.script_interactor.instruments]
        self.inst_choice = wx.Choice(self, -1, choices=instruments)
        # self.Bind(wx.EVT_CHOICE, self.on_inst_change, self.inst_choice)
        self.inst_choice.SetSelection(instruments.index(self.interactor.instrument))
        gbs.Add(self.inst_choice, (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))

        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.Add((-1, 5))

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

    def on_object_change(self, evt):
        """Callback for changing the choice widget for the different object that can do a simulation."""
        object_name = self.obj_choice.GetStringSelection()
        method_names = [info.name for info in self.script_interactor.get_sim_methods_info(object_name)]
        self.method_choice.SetItems(method_names)
        self.method_choice.SetSelection(0)
        self.on_method_change(evt)
        # Update the column labels

    def on_method_change(self, evt):
        """Callback to update the controls after a change in method"""
        method_name = self.method_choice.GetStringSelection()
        object_name = self.obj_choice.GetStringSelection()
        arg_names = []
        def_args = []
        for info in self.script_interactor.get_sim_methods_info(object_name):
            if info.name == method_name:
                arg_names = info.args
                def_args = info.def_args
                break

        new_labels = []
        for label, arg_name in zip(self.labels[3:], arg_names):
            new_labels.append(label.GetLabel() != arg_name)
            label.SetLabel(arg_name)
        # Clear the remaining column labels
        for label in self.labels[len(arg_names) + 3 :]:
            label.SetLabel("")

        # Update the text controls - if needed
        for i in range(len(arg_names)):
            # if new_labels[i]:
            if True:
                self.arg_controls[i].SetValue(def_args[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(arg_names) :]:
            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.interactor.position, 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 GetObject(self):
        """Returns the edited object"""
        new_inter = self.interactor.create_new()
        new_inter.name = self.interactor.name
        new_inter.arguments = self.GetExpressions()
        new_inter.expression_list = self.interactor.expression_list
        new_inter.instrument = self.inst_choice.GetStringSelection()
        new_inter.obj_method = self.method_choice.GetStringSelection()
        new_inter.obj_name = self.obj_choice.GetStringSelection()
        new_inter.position = self.interactor.position
        return new_inter


class CustomParametersDialog(wx.Dialog):
    def __init__(self, parent, model, custom_parameter_interactor):
        wx.Dialog.__init__(self, parent, -1, "Custom parameter editor")
        self.SetAutoLayout(True)
        self.model = model
        self.interactors = custom_parameter_interactor[:]
        # self.lines = lines
        # self.var_name = 'cp'

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

        col_labels = ["Name", "Value"]

        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, 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, border=5)
        self.add_button = wx.Button(self, -1, "Add")
        name_ctrl_sizer.Add(self.add_button, (1, 2), flag=wx.ALIGN_RIGHT, 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([inter.get_code() for inter in self.interactors])
        sizer.Add(self.listbox, 1, wx.GROW | wx.ALL, 10)

        self.delete_button = wx.Button(self, -1, "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, wx.ID_OK)
        button_sizer.AddButton(okay_button)
        button_sizer.AddButton(wx.Button(self, 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):
        """Callback for ok button click or apply button"""
        event.Skip()

    def OnAdd(self, event):
        """Callback for adding an entry"""
        new_inter = CustomParameterInteractor(name=self.name_ctrl.GetValue(), value=self.value_ctrl.GetValue())
        try:
            self.model.eval_in_model(new_inter.get_code())
        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.interactors.append(new_inter)
            self.listbox.SetItemList([inter.get_code() for inter in self.interactors])

    def OnDelete(self, event):
        """Callback for deleting an entry"""
        result = "Do you want to delete the expression?\nRemember to check if parameter is used elsewhere!"
        result = ShowQuestionDialog(self, result, "Delete expression?")
        if result:
            self.interactors.pop(self.listbox.GetSelection())
            self.listbox.SetItemList([inter.get_code() for inter in self.interactors])

    def GetObjectList(self):
        """Returns the custom parameters list."""
        return self.interactors


def test_islands():
    class SurfaceCut:
        __values__ = {
            "1DimGrating": {"elDens": 0.0, "f": 0.0j, "sig": 0.0},
            "2DimCircArray": {
                "mode": "doubleExp",
                "radiusIsl": None,
                "nominalHeight": None,
                "domeHeight": 0.0,
                "domeSig": 0.0,
                "expUpSig": 10.0**-6,
                "expLowSig": 10.0**-6,
                "expTopSig": 10.0**6,
                "expPear": 20.0,
                "genRadialShift": 0.0,
                "genXshift": 0.0,
                "genYshift": 0.0,
                "elDens": 0.0,
                "f": 0.0j,
                "sig": 0.0,
            },
            "Oxide": {"elDens": 0.0, "f": 0.0j, "sig": 0.0},
        }

        def __init__(self, cut_type, **kwargs):
            pass

    class SurfaceCutInteractor(ObjectScriptInteractor):
        def __init__(self, **kwargs):
            ObjectScriptInteractor.__init__(self, **kwargs)
            self.cut_type = ""

    def parse_parameter_string(self, code):
        """Parses the creation of a domain object. Overloaded from ModelScriptInteractor.

        The expected form of a domain object is: [name] = [class_name]([cut_type], [parameter]=[value]....)

        Parameters:
            code (string): code to parse

        Returns:
            new_parameters_values (dict): A dictionary of new parameter values.
        """
        special_values = {}
        # Parsing bulk_slab
        ind = code.index(",")
        if ind < 0:
            ValueError("Could not parse domain, argument cut_type")
        special_values["cut_type"] = code[:ind].strip().strip("'").strip('"')
        code = code[ind + 1 :]

        new_parameter_values = ObjectScriptInteractor.parse_parameter_string(self, code)
        new_parameter_values.update(special_values)
        return new_parameter_values

    def get_parameter_code(self):
        """Create the code to generate the object. Overloaded from ModelScriptInteractor.

        Returns:
            code (string): Code representing the parameters, the things inside the parenthisis
        """
        code = "'%s', " % (self.cut_type,)
        code += ObjectScriptInteractor.get_parameter_code(self)
        return code

    code = """
        # BEGIN SurfaceCuts
        sc1 = SurfaceCut('Oxide', elDens=10)
        # END SurfaceCuts

        def Sim(data):
            I = []
            # BEGIN DataSet 0
            d = data[0]
            I.append(sample.calc_i(inst, d.h, d.k, d.l))
            # END DataSet 0
            # BEGIN DataSet 1
            d = data[1]
            I.append(sample.calc_f(inst, d.h, d.k, d.l))
            # END DataSet 1
            return I

        """
    script_parser = ModelScriptInteractor()
    script_parser.add_section("SurfaceCuts", SurfaceCutInteractor, class_name="SurfaceCut", class_impl=SurfaceCut)

    script_parser.parse_code(code)
