"""Module frontend

The frontend displays the GUI (Graphic User Interface).
All functions and modules are called from here.
"""
import numpy as np
import warnings
import wx
import wx.lib.plot as plot
import wx.lib.scrolledpanel as scrolled


from pycorrfit import models as mdls
from pycorrfit import fit
from pycorrfit import Correlation


from . import tools
from . import wxutils


class FittingPanel(wx.Panel):
    """
    Those are the Panels that show the fitting dialogs with the Plots.
    """

    def __init__(self, parent, counter, modelid, active_parms, tau=None):
        """ Initialize with given parameters. """
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.parent = parent

        self.corr = Correlation(fit_model=modelid)
        if tau is not None:
            self.corr.lag_time = tau
        # active_parameters:
        # [0] labels
        # [1] values
        # [2] bool values to fit
        self.corr.fit_parameters = active_parms[1]
        self.corr.fit_parameters_variable = active_parms[2]

        self._bgselected = None
        self._bg2selected = None

        self.FitKnots = 5  # number of knots for spline fit or similiars

        self.weighted_fittype_id = 0  # integer (drop down item)
        self.weighted_nuvar = 3  # bins for std-dev. (left and rigth)

        # The weights that are plotted in the page
        # This is set by the PlotAll function
        self.weights_plot_fill_area = None

        # A list containing page numbers that share parameters with this page.
        # This parameter is defined by the global fitting tool and is saved in
        # sessions.
        self.GlobalParameterShare = []
        # Counts number of Pages already created:
        self.counter = counter
        # Has inital plot been performed?
        # Call PlotAll("init") to set this to true. If it is true, then
        # nothing will be plotted if called with "init"
        self.InitialPlot = False
        # Model we are using

        # Tool statistics uses this list:
        self.StatisticsCheckboxes = None
        # Splitter window
        # Sizes
        size = parent.notebook.GetSize()
        tabsize = 33
        size[1] = size[1] - tabsize
        self.sizepanelx = 270
        canvasx = size[0]-self.sizepanelx+5
        sizepanel = (self.sizepanelx, size[1])
        sizecanvas = (canvasx, size[1])
        self.sp = wx.SplitterWindow(self, size=size, style=wx.SP_3DSASH)
        # This is necessary to prevent "Unsplit" of the SplitterWindow:
        self.sp.SetMinimumPaneSize(1)
        # Settings Section (left side)
        # self.panelsettings = wx.Panel(self.sp, size=sizepanel)
        self.panelsettings = scrolled.ScrolledPanel(self.sp, size=sizepanel)
        self.panelsettings.SetupScrolling(scroll_x=False)
        # Setting up Plot (correlation + chi**2)
        self.spcanvas = wx.SplitterWindow(self.sp, size=sizecanvas,
                                          style=wx.SP_3DSASH)
        # This is necessary to prevent "Unsplit" of the SplitterWindow:
        self.spcanvas.SetMinimumPaneSize(1)
        # y difference in pixels between Auocorrelation and Residuals
        cupsizey = (size[1]*4)//5
        # Calculate initial data
        self.calculate_corr()
        # Draw the settings section
        self.settings()
        # Load default values
        self.apply_parameters_reverse()
        # Upper Plot for plotting of Correlation Function
        self.canvascorr = plot.PlotCanvas(self.spcanvas)
        self.canvascorr.logScale = (True, False)
        self.canvascorr.enableZoom = True
        self.PlotAll(event="init", trigger="tab_init")
        self.canvascorr.SetSize((canvasx, cupsizey))
        # Lower Plot for plotting of the residuals
        self.canvaserr = plot.PlotCanvas(self.spcanvas)
        self.canvaserr.logScale = (True, False)
        self.canvaserr.enableZoom = True
        self.canvaserr.SetSize((canvasx, size[1]-cupsizey))
        self.spcanvas.SplitHorizontally(self.canvascorr, self.canvaserr,
                                        cupsizey)
        self.sp.SplitVertically(self.panelsettings, self.spcanvas,
                                self.sizepanelx)
        # Bind resizing to resizing function.
        wx.EVT_SIZE(self, self.OnSize)

    @property
    def active_parms(self):
        names = self.corr.fit_model.parameters[0]
        parms = self.corr.fit_parameters
        bools = self.corr.fit_parameters_variable
        return [names, parms, bools]

    @property
    def IsCrossCorrelation(self):
        return self.corr.is_cc

    @property
    def modelid(self):
        return self.corr.fit_model.id

    @property
    def prevent_batch_modification(self):
        return self.wxCBPreventBatchParms.GetValue()

    @prevent_batch_modification.setter
    def prevent_batch_modification(self, value):
        self.wxCBPreventBatchParms.SetValue(value)

    @property
    def title(self):
        return self.tabtitle.GetValue()

    @title.setter
    def title(self, title):
        self.tabtitle.SetValue(title.strip())
        self.corr.title = title.strip()

    @property
    def traceavg(self):
        warnings.warn("Trace average always set to none!")
        return None

    @property
    def tracecc(self):
        if self.corr.is_cc and len(self.corr.traces) != 0:
            return self.corr.traces
        else:
            return None

    @property
    def bgselected(self):
        return self._bgselected

    @bgselected.setter
    def bgselected(self, value):
        if value is None:
            self.corr.backgrounds = []
            return
        # check paren.Background and get id
        background = self.parent.Background[value]
        self.corr.background_replace(0, background)
        self._bgselected = value

    @property
    def bg2selected(self):
        return self._bg2selected

    @bg2selected.setter
    def bg2selected(self, value):
        if value is None:
            if self.corr.is_cc:
                self.corr.backgrounds = []
            return
        # check paren.Background and get id
        background = self.parent.Background[value]
        self.corr.background_replace(1, background)
        self._bg2selected = value

    def apply_parameters(self, event=None):
        """ Read the values from the GUI form and write it to the
            pages parameters / correlation class.
            This function is called when the "Apply" button is hit.
        """
        modelid = self.corr.fit_model.id
        parameters = list()
        parameters_variable = list()
        # Read parameters from form and update self.active_parms[1]
        for i in np.arange(len(self.spincontrol)):
            parameters.append(1*self.spincontrol[i].GetValue())
            parameters_variable.append(self.checkboxes[i].GetValue())

        self.corr.fit_parameters_variable = np.array(parameters_variable,
                                                     dtype=bool)
        # As of version 0.7.5: we want the units to be displayed
        # human readable - the way they are displayed
        # in the Page info tool.
        # Here: Convert human readable units to program internal
        # units
        parmsconv = mdls.GetInternalFromHumanReadableParm(
            modelid, np.array(parameters))[1]
        self.corr.fit_parameters = parmsconv

        # Fitting parameters
        self.weighted_nuvar = self.Fitbox[5].GetValue()

        self.weighted_fittype_id = self.Fitbox[1].GetSelection()

        fitbox_value = self.Fitbox[1].GetValue()

        if self.weighted_fittype_id == -1:
            # User edited knot number
            Knots = fitbox_value
            Knots = "".join(filter(lambda x: x.isdigit(), Knots))
            if Knots == "":
                Knots = "5"
            self.weighted_fittype_id = 1
            self.FitKnots = str(Knots)
            fit_weight_type = "spline{}".format(self.FitKnots)
            fit_weight_data = self.weighted_nuvar
        elif self.weighted_fittype_id == 1:
            Knots = fitbox_value
            Knots = "".join(filter(lambda x: x.isdigit(), Knots))
            self.FitKnots = int(Knots)
            fit_weight_type = "spline{}".format(self.FitKnots)
            fit_weight_data = self.weighted_nuvar
        elif self.weighted_fittype_id == 0:
            fit_weight_type = "none"
            fit_weight_data = None
        elif self.weighted_fittype_id == 2:
            fit_weight_type = "model function"
            fit_weight_data = self.weighted_nuvar
        else:  # fitbox_selection > 2:
            fit_weight_type = fitbox_value
            self.corr.fit_weight_type = fitbox_value
            fit_weight_data = self.corr.fit_weight_data

        # Fitting algorithm
        keys = fit.GetAlgorithmStringList()[0]
        idalg = self.AlgorithmDropdown.GetSelection()

        self.corr.fit_algorithm = keys[idalg]
        self.corr.fit_weight_type = fit_weight_type
        self.corr.fit_weight_data = fit_weight_data

        # If parameters have been changed because of the check_parms
        # function, write them back.
        self.apply_parameters_reverse()

    def apply_parameters_reverse(self, event=None):
        """ Read the values from the pages parameters and write
            it to the GUI form.
        """
        modelid = self.corr.fit_model.id
        #
        # As of version 0.7.5: we want the units to be displayed
        # human readable - the way they are displayed
        # in the Page info tool.
        #
        # Here: Convert program internal units to
        # human readable units
        parameters = mdls.GetHumanReadableParms(modelid,
                                                self.corr.fit_parameters)[1]
        parameters_variable = self.corr.fit_parameters_variable
        # Write parameters to the form on the Page
        for i in np.arange(len(self.active_parms[1])):
            self.spincontrol[i].SetValue(parameters[i])
            self.checkboxes[i].SetValue(bool(parameters_variable[i]))
        # Fitting parameters
        self.Fitbox[5].SetValue(self.weighted_nuvar)
        idf = self.weighted_fittype_id
        List = self.Fitbox[1].GetItems()
        List[1] = "spline ("+str(self.FitKnots)+" knots)"
        self.Fitbox[1].SetItems(List)
        self.Fitbox[1].SetSelection(idf)
        # Normalization
        if self.corr.normparm is None:
            normsel = 0
        else:
            normsel = self.corr.normparm + 1
        self.AmplitudeInfo[2].SetSelection(normsel)
        # Fitting algorithm
        keys = fit.GetAlgorithmStringList()[0]
        idalg = keys.index(self.corr.fit_algorithm)
        self.AlgorithmDropdown.SetSelection(idalg)
        self.updateChi2()

    def calculate_corr(self):
        """
        Calculate model correlation function
        """
        return self.corr.modeled

    def Fit_enable_fitting(self):
        """ Enable the fitting button and the weighted fit control"""
        # self.Fitbox = [ fitbox, weightedfitdrop, fittext, fittext2,
        #                fittextvar, fitspin, buttonfit, textalg,
        #                self.AlgorithmDropdown]
        self.Fitbox[0].Enable()
        self.Fitbox[1].Enable()
        self.Fitbox[6].Enable()
        self.Fitbox[7].Enable()
        self.Fitbox[8].Enable()

    def Fit_function(self, event=None, noplots=False, trigger=None):
        """ Calls the fit function.

            `noplots=True` prevents plotting of spline fits

            `trigger` is passed to page.PlotAll.
                      If trigger is "fit_batch", then `noplots` is set
                      to `True`.

        """
        tools.batchcontrol.FitProgressDlg(self, self)

    def Fit_finalize(self, trigger):
        """ Things that need be done after fitting
        """
        # Reset list of globally shared parameters, because we are only
        # fitting this single page now.
        # TODO:
        # - also remove this page from the GlobalParameterShare list of
        #   the other pages
        self.GlobalParameterShare = []
        # Update spin-control values
        self.apply_parameters_reverse()
        # Plot everything
        try:
            self.PlotAll(trigger=trigger)
        except OverflowError:
            # Sometimes parameters are just bad and
            # we still want the user to use the
            # program.
            warnings.warn("Could not plot canvas.")
        # update displayed chi2
        self.updateChi2()

    def Fit_WeightedFitCheck(self, event=None):
        """ Enable Or disable variance calculation, dependent on
            "Weighted Fit" checkbox
        """
        # self.Fitbox=[ fitbox, weightedfitdrop, fittext, fittext2, fittextvar,
        #                fitspin, buttonfit ]
        weighted = (self.Fitbox[1].GetSelection() != 0)
        # In the case of "Average" we do not enable the
        # "Calculation of variance" part.
        if weighted is True and self.Fitbox[1].GetValue() != "Average":
            self.Fitbox[2].Enable()
            self.Fitbox[3].Enable()
            self.Fitbox[4].Enable()
            self.Fitbox[5].Enable()
        else:
            self.Fitbox[2].Disable()
            self.Fitbox[3].Disable()
            self.Fitbox[4].Disable()
            self.Fitbox[5].Disable()

    def MakeStaticBoxSizer(self, boxlabel):
        """ Create a Box with check boxes (fit yes/no) and possibilities to
            change initial values for fitting.

            Parameters:
            *boxlabel*: The name of the box (is being displayed)
            *self.active_parms[0]*: A list of things to put into the box

            Returns:
            *sizer*: The static Box
            *check*: The (un)set checkboxes
            *spin*: The spin text fields
        """
        modelid = self.corr.fit_model.id
        box = wx.StaticBox(self.panelsettings, label=boxlabel)
        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
        check = list()
        spin = list()
        #
        # As of version 0.7.5: we want the units to be displayed
        # human readable - the way they are displayed
        # in the Page info tool.
        #
        labels = mdls.GetHumanReadableParms(modelid,
                                            self.corr.fit_parameters)[0]
        sizerh = wx.GridSizer(len(labels), 2, 2, 2)
        for label in labels:
            checkbox = wx.CheckBox(self.panelsettings, label=label)
            # We needed to "from wx.lib.agw import floatspin" to get this:
            spinctrl = wxutils.PCFFloatTextCtrl(self.panelsettings)
            sizerh.Add(spinctrl)
            sizerh.Add(checkbox)
            # Put everything into lists to be able to refer to it later
            check.append(checkbox)
            spin.append(spinctrl)
        sizer.Add(sizerh)
        return sizer, check, spin

    def OnAmplitudeCheck(self, event=None):
        """ Enable/Disable BG rate text line.
            New feature introduced in 0.7.8
        """
        modelid = self.corr.fit_model.id
        # Normalization to a certain parameter in plots
        # Find all parameters that start with an "N"
        # ? and "C" ?
        # Create List
        normlist = list()
        normlist.append("None")
        # Add parameters
        parameterlist = list()
        for i in np.arange(len(self.active_parms[0])):
            label = self.active_parms[0][i]
            if label[0].lower() == "n":
                normlist.append("*"+label)
                parameterlist.append(i)
        # Add supplementary parameters
        # Get them from models
        supplement = mdls.GetMoreInfo(modelid, self)
        if supplement is not None:
            for i in np.arange(len(supplement)):
                label = supplement[i][0]
                if label[0].lower() == "n":
                    normlist.append("*"+label)
                    # Add the id of the supplement starting at the
                    # number of fitting parameters of current page.
                    parameterlist.append(i+len(self.active_parms[0]))
        normsel = self.AmplitudeInfo[2].GetSelection()
        if normsel in [0, -1]:
            # init or no normalization selected
            self.corr.normparm = None
            normsel = 0
        else:
            self.corr.normparm = parameterlist[normsel-1]

        if len(parameterlist) > 0:
            self.AmplitudeInfo[2].Enable()
            self.AmplitudeInfo[3].Enable()
        else:
            self.AmplitudeInfo[2].Disable()
            self.AmplitudeInfo[3].Disable()
        # Set dropdown values
        self.AmplitudeInfo[2].SetItems(normlist)
        self.AmplitudeInfo[2].SetSelection(normsel)
        # Plot intensities
        # Quick reminder:
        # self.AmplitudeInfo = [ [intlabel1, intlabel2],
        #                       [bgspin1, bgspin2],
        #                       normtoNDropdown, textnor]
        # Signal
        self.AmplitudeInfo[0][0].SetValue("{:.4f}".format(0))
        self.AmplitudeInfo[0][1].SetValue("{:.4f}".format(0))
        for i in range(len(self.corr.traces)):
            S = self.corr.traces[i].countrate
            self.AmplitudeInfo[0][i].SetValue("{:.4f}".format(S))
        if self.corr.is_cc:
            self.AmplitudeInfo[0][1].Enable()
        else:
            self.AmplitudeInfo[0][1].Disable()
        # Background
        # self.parent.Background[self.bgselected][i]
        # [0] average signal [kHz]
        # [1] signal name (edited by user)
        # [2] signal trace (tuple) ([ms], [kHz])
        if len(self.corr.backgrounds) >= 1:
            self.AmplitudeInfo[1][0].SetValue(
                self.corr.backgrounds[0].countrate)
        else:
            self.AmplitudeInfo[1][0].SetValue(0)
            self.AmplitudeInfo[1][1].SetValue(0)

        if len(self.corr.backgrounds) == 2:
            self.AmplitudeInfo[1][1].SetValue(
                self.corr.backgrounds[1].countrate)
        else:
            self.AmplitudeInfo[1][1].SetValue(0)
        # Disable the second line in amplitude correction, if we have
        # autocorrelation only.
        boolval = self.corr.is_cc
        for item in self.WXAmplitudeCCOnlyStuff:
            item.Enable(boolval)

    def OnBGSpinChanged(self, e):
        """ Calls tools.background.ApplyAutomaticBackground
            to update background information
        """
        # Quick reminder:
        # self.AmplitudeInfo = [ [intlabel1, intlabel2],
        #                       [bgspin1, bgspin2],
        #                       normtoNDropdown, textnor]
        if self.corr.is_cc:
            # update both self.bgselected and self.bg2selected
            bg = [float(self.AmplitudeInfo[1][0].GetValue()),
                  float(self.AmplitudeInfo[1][1].GetValue())]
            sig = [float(self.AmplitudeInfo[0][0].GetValue()),
                   float(self.AmplitudeInfo[0][1].GetValue())]
            # Make sure bg < sig
            for ii in range(len(bg)):
                if sig[ii] != 0:
                    if bg[ii] > .99*sig[ii]:
                        bg[ii] = .99*sig[ii]
                        self.AmplitudeInfo[1][ii].SetValue(bg[ii])
        else:
            # Only update self.bgselected
            bg = float(self.AmplitudeInfo[1][0].GetValue())
            sig = float(self.AmplitudeInfo[0][0].GetValue())
            # Make sure bg < sig
            if sig != 0:
                if bg > .99*sig:
                    bg = .99*sig
                    self.AmplitudeInfo[1][0].SetValue(bg)
        tools.background.ApplyAutomaticBackground(self, bg,
                                                  self.parent)
        e.Skip()

    def OnTitleChanged(self, e):
        modelid = self.corr.fit_model.id
        pid = self.parent.notebook.GetPageIndex(self)
        if self.tabtitle.GetValue() == "":
            text = self.counter + mdls.modeldict[modelid][1]
        else:
            # How many characters of the the page title should be displayed
            # in the tab? We choose 9: AC1-012 plus 2 whitespaces
            text = self.counter + self.tabtitle.GetValue()[-9:]
        self.parent.notebook.SetPageText(pid, text)

    def OnSetRange(self, e):
        """ Open a new Frame where the parameter range can be set.
            Rewrites self.parameter_range
            Parameter ranges are treated like parameters: They are saved in
            sessions and applied in batch mode.
        """
        # TODO:
        # - make range selector work with new class

        # We write a separate tool for that.
        # This tool does not show up in the Tools menu.
        if self.parent.RangeSelector is None:
            self.parent.RangeSelector = tools.RangeSelector(self)
            self.parent.RangeSelector.Bind(wx.EVT_CLOSE,
                                           self.parent.RangeSelector.OnClose)
        else:
            try:
                self.parent.RangeSelector.OnClose()
            except:
                pass
            self.parent.RangeSelector = None

    def OnSize(self, event):
        """ Resize the fitting Panel, when Window is resized. """
        size = self.parent.notebook.GetSize()
        tabsize = 33
        size[1] = size[1] - tabsize
        self.sp.SetSize(size)

    def PlotAll(self, event=None, trigger=None):
        """
        This function plots the whole correlation and residuals canvas.
        We do:
        - Channel selection
        - Background correction
        - Apply Parameters (separate function)
        - Drawing of plots

        The `event` is usually just an event from buttons or similar
        wx objects. It can be "init", then some initial plotting is
        done before the data is handled.

        The `trigger` is passed to `self.parent.OnFNBPageChanged` so
        that tools can update their content accordingly. For more
        information on triggers, have a look at the doctring of the
        `tools` submodule.
        """
        if event == "init":
            # We use this to have the page plotted at least once before
            # readout of parameters (e.g. startcrop, endcrop)
            # This is a performence tweak.
            if self.InitialPlot:
                return
            else:
                self.InitialPlot = True
        # Enable/Disable, set values frontend normalization
        self.OnAmplitudeCheck()
        # Apply parameters
        self.apply_parameters()
        # Calculate correlation function from parameters
        # Drawing of correlation plot
        # Plots corr.correlation_fit and the calcualted correlation function
        # self.datacorr into the upper canvas.
        # Create a line @ y=zero:
        zerostart = self.corr.lag_time_fit[0]
        zeroend = self.corr.lag_time_fit[-1]
        datazero = [[zerostart, 0], [zeroend, 0]]
        # Set plot colors
        width = 1
        colexp = "grey"
        colfit = "blue"
        colweight = "cyan"
        lines = list()
        linezero = plot.PolyLine(datazero, colour='orange', width=width)
        lines.append(linezero)
        if self.corr.correlation is not None:
            if self.corr.is_weighted_fit and \
               self.parent.MenuShowWeights.IsChecked():
                try:
                    weights = self.corr.fit_results["fit weights"]
                except:
                    weights = self.corr.fit_weight_data

                if isinstance(weights, np.ndarray):
                    # user might have selected a new weight type and
                    # presses apply, do not try to display weights

                    # if weights are from average or other, make sure that the
                    # dimensions are correct
                    if weights.shape[0] == self.corr.correlation.shape[0]:
                        weights = weights[self.corr.fit_ival[0]:self.corr.fit_ival[1]]

                    # perform some checks
                    if np.allclose(weights, np.ones_like(weights)):
                        weights = 0
                    elif weights.shape[0] != self.corr.modeled_fit.shape[0]:
                        # non-matching weigths
                        warnings.warn(
                            "Unmatching weights found. Probably from previous data set.")
                        weights = 0

                    # Add the weights to the graph.
                    # This is done by drawing two lines.
                    w = 1*self.corr.modeled_fit
                    w1 = 1*w
                    w2 = 1*w

                    w1[:, 1] = w[:, 1] + weights
                    w2[:, 1] = w[:, 1] - weights
                    # crop w1 and w2 if corr.correlation_fit does not include all
                    # data points.
                    if np.all(w[:, 0] == self.corr.correlation_fit[:, 0]):
                        pass
                    else:
                        raise ValueError(
                            "This should not have happened: size of weights is wrong.")
                    # Normalization with self.normfactor
                    w1[:, 1] *= self.corr.normalize_factor
                    w2[:, 1] *= self.corr.normalize_factor
                    self.weights_plot_fill_area = [w1, w2]
                    lineweight1 = plot.PolyLine(w1, legend='',
                                                colour=colweight, width=width)
                    lines.append(lineweight1)
                    lineweight2 = plot.PolyLine(w2, legend='',
                                                colour=colweight, width=width)
                    lines.append(lineweight2)
            else:
                self.weights_plot_fill_area = None

            # Plot Correlation curves
            # Plot both, experimental and calculated data
            # Normalization with self.normfactor, new feature in 0.7.8
            datacorr_norm = self.corr.modeled_plot
            linecorr = plot.PolyLine(datacorr_norm, legend='', colour=colfit,
                                     width=width)
            dataexp_norm = self.corr.correlation_plot
            lineexp = plot.PolyLine(dataexp_norm, legend='', colour=colexp,
                                    width=width)
            # Draw linezero first, so it is in the background
            lines.append(lineexp)
            lines.append(linecorr)
            PlotCorr = plot.PlotGraphics(lines,
                                         xLabel=u'lag time τ [ms]', yLabel=u'G(τ)')

            self.canvascorr.Draw(PlotCorr)
            # Calculate residuals
            resid_norm = self.corr.residuals_plot
            lineres = plot.PolyLine(resid_norm, legend='', colour=colfit,
                                    width=width)

            # residuals or weighted residuals?
            if self.corr.is_weighted_fit:
                yLabelRes = "weighted \nresiduals"
            else:
                yLabelRes = "residuals"
            PlotRes = plot.PlotGraphics([linezero, lineres],
                                        xLabel=u'lag time τ [ms]',
                                        yLabel=yLabelRes)
            self.canvaserr.Draw(PlotRes)
        else:
            # Amplitude normalization, new feature in 0.7.8
            datacorr_norm = self.corr.modeled_plot
            linecorr = plot.PolyLine(datacorr_norm, legend='', colour='blue',
                                     width=1)
            PlotCorr = plot.PlotGraphics([linezero, linecorr],
                                         xLabel=u'Lag time τ [ms]', yLabel=u'G(τ)')
            self.canvascorr.Draw(PlotCorr)
        self.Refresh()
        self.parent.OnFNBPageChanged(trigger=trigger)

    def settings(self):
        """ Here we define, what should be displayed at the left side
            of the fitting page/tab.
            Parameters:
        """
        modelid = self.corr.fit_model.id
        horizontalsize = self.sizepanelx-10
        # Title
        # Create empty tab title
        mddat = mdls.modeldict[modelid]
        modelshort = mdls.GetModelType(modelid)
        titlelabel = u"Data set {} ({} {})".format(
            self.counter.strip(" :"), modelshort, mddat[1])
        boxti = wx.StaticBox(self.panelsettings, label=titlelabel)
        sizerti = wx.StaticBoxSizer(boxti, wx.VERTICAL)
        sizerti.SetMinSize((horizontalsize, -1))
        self.tabtitle = wx.TextCtrl(self.panelsettings, value="",
                                    size=(horizontalsize-20, -1))
        self.Bind(wx.EVT_TEXT, self.OnTitleChanged, self.tabtitle)
        sizerti.Add(self.tabtitle, 0, wx.EXPAND, 0)
        # Create StaticBoxSizer
        box1, check, spin = self.MakeStaticBoxSizer("Model parameters")
        # Make the check boxes and spin-controls available everywhere
        self.checkboxes = check
        self.spincontrol = spin
        #
        # As of version 0.7.5: we want the units to be displayed
        # human readable - the way they are displayed
        # in the Page info tool.
        #
        labels, parameters = mdls.GetHumanReadableParms(modelid,
                                                        self.active_parms[1])
        parameterstofit = self.active_parms[2]
        # Set initial values given by user/programmer for Diffusion Model
        for i in np.arange(len(labels)):
            self.checkboxes[i].SetValue(bool(parameterstofit[i]))
            self.spincontrol[i].SetValue(parameters[i])
        # Put everything together
        self.panelsettings.sizer = wx.BoxSizer(wx.VERTICAL)
        self.panelsettings.sizer.Add(sizerti, 0, wx.EXPAND, 0)
        self.panelsettings.sizer.Add(box1, 0, wx.EXPAND, 0)
        # checkbox "Protect from batch control"
        self.wxCBPreventBatchParms = wx.CheckBox(self.panelsettings,
                                                 -1,
                                                 "prevent batch modification")
        box1.Add(self.wxCBPreventBatchParms)
        # buttons "Apply" and "Set range"
        horzs = wx.BoxSizer(wx.HORIZONTAL)
        buttonapply = wx.Button(self.panelsettings, label="Apply")
        self.Bind(wx.EVT_BUTTON, self.PlotAll, buttonapply)
        horzs.Add(buttonapply)
        buttonrange = wx.Button(self.panelsettings, label="Set range")
        self.Bind(wx.EVT_BUTTON, self.OnSetRange, buttonrange)
        horzs.Add(buttonrange)
        box1.Add(horzs)
        # Set horizontal size
        box1.SetMinSize((horizontalsize, -1))
        # More info
        normbox = wx.StaticBox(
            self.panelsettings, label="Amplitude corrections")
        miscsizer = wx.StaticBoxSizer(normbox, wx.VERTICAL)
        miscsizer.SetMinSize((horizontalsize, -1))
        # Intensities and Background
        sizeint = wx.FlexGridSizer(rows=3, cols=3, vgap=5, hgap=5)
        sizeint.Add(wx.StaticText(self.panelsettings, label="[kHz]"))
        sizeint.Add(wx.StaticText(self.panelsettings,
                                  label="Intensity"))
        sizeint.Add(wx.StaticText(self.panelsettings,
                                  label="Background"))
        sizeint.Add(wx.StaticText(self.panelsettings, label="Ch1"))
        intlabel1 = wx.TextCtrl(self.panelsettings)
        bgspin1 = wxutils.PCFFloatTextCtrl(self.panelsettings)
        bgspin1.Bind(wx.EVT_KILL_FOCUS, self.OnBGSpinChanged)
        bgspin1.Bind(wx.EVT_TEXT_ENTER, self.OnBGSpinChanged)
        sizeint.Add(intlabel1)
        intlabel1.SetEditable(False)
        sizeint.Add(bgspin1)
        chtext2 = wx.StaticText(self.panelsettings, label="Ch2")
        sizeint.Add(chtext2)
        intlabel2 = wx.TextCtrl(self.panelsettings)
        intlabel2.SetEditable(False)
        bgspin2 = wxutils.PCFFloatTextCtrl(self.panelsettings)
        bgspin2.Bind(wx.EVT_KILL_FOCUS, self.OnBGSpinChanged)
        sizeint.Add(intlabel2)
        sizeint.Add(bgspin2)
        miscsizer.Add(sizeint)
        # Normalize to n?
        textnor = wx.StaticText(self.panelsettings, label="Plot normalization")
        miscsizer.Add(textnor)
        normtoNDropdown = wx.ComboBox(self.panelsettings)
        self.Bind(wx.EVT_COMBOBOX, self.PlotAll, normtoNDropdown)
        miscsizer.Add(normtoNDropdown)
        self.AmplitudeInfo = [[intlabel1, intlabel2],
                              [bgspin1, bgspin2],
                              normtoNDropdown, textnor]
        self.WXAmplitudeCCOnlyStuff = [chtext2, intlabel2, bgspin2]
        self.panelsettings.sizer.Add(miscsizer, 0, wx.EXPAND, 0)
        # Add fitting Box
        fitbox = wx.StaticBox(self.panelsettings, label="Fitting options")
        fitsizer = wx.StaticBoxSizer(fitbox, wx.VERTICAL)
        fitsizer.SetMinSize((horizontalsize, -1))
        # Add a checkbox for weighted fitting
        weightedfitdrop = wx.ComboBox(self.panelsettings)
        self.weightlist = ["no weights", "spline (5 knots)", "model function"]
        weightedfitdrop.SetItems(self.weightlist)
        weightedfitdrop.SetSelection(0)
        fitsizer.Add(weightedfitdrop)
        # WeightedFitCheck() Enables or Disables the variance part
        weightedfitdrop.Bind(wx.EVT_COMBOBOX, self.Fit_WeightedFitCheck)
        # Add the variance part.
        # In order to do a weighted fit, we need to calculate the variance
        # at each point of the experimental data array.
        # In order to do that, we need to know how many data points from left
        # and right of the interesting data point we want to include in that
        # calculation.
        fittext = wx.StaticText(self.panelsettings,
                                label="Calculation of the variance")
        fitsizer.Add(fittext)
        fittext2 = wx.StaticText(self.panelsettings,
                                 label="from 2j+1 data points")
        fitsizer.Add(fittext2)
        fitsizerspin = wx.BoxSizer(wx.HORIZONTAL)
        fittextvar = wx.StaticText(self.panelsettings, label="j = ")
        fitspin = wx.SpinCtrl(self.panelsettings, -1,
                              initial=3, min=1, max=100)
        fitsizerspin.Add(fittextvar)
        fitsizerspin.Add(fitspin)
        fitsizer.Add(fitsizerspin)
        # Add algorithm selection
        textalg = wx.StaticText(self.panelsettings, label="Algorithm")
        fitsizer.Add(textalg)
        self.AlgorithmDropdown = wx.ComboBox(self.panelsettings)
        items = fit.GetAlgorithmStringList()[1]
        self.AlgorithmDropdown.SetItems(items)
        self.Bind(wx.EVT_COMBOBOX, self.apply_parameters,
                  self.AlgorithmDropdown)
        fitsizer.Add(self.AlgorithmDropdown)
        self.AlgorithmDropdown.SetMaxSize(weightedfitdrop.GetSize())
        # Add button "Fit" and chi2 display
        fitbuttonsizer = wx.BoxSizer(wx.HORIZONTAL)
        buttonfit = wx.Button(self.panelsettings, label="Fit")
        self.Bind(wx.EVT_BUTTON, self.Fit_function, buttonfit)
        fitbuttonsizer.Add(buttonfit)

        # add shortcut
        acctbl = wx.AcceleratorTable(
            [(wx.ACCEL_CTRL, ord('F'), buttonfit.GetId())])
        self.SetAcceleratorTable(acctbl)
        ##

        self.WXTextChi2 = wx.StaticText(self.panelsettings)
        # this StaticText is updated by `self.updateChi2()`
        fitbuttonsizer.Add(self.WXTextChi2, flag=wx.ALIGN_CENTER)
        fitsizer.Add(fitbuttonsizer)
        self.panelsettings.sizer.Add(fitsizer, 0, wx.EXPAND, 0)
        # Squeeze everything into the sizer
        self.panelsettings.SetSizer(self.panelsettings.sizer)
        # This is also necessary in Windows
        self.panelsettings.Layout()
        self.panelsettings.Show()
        # Make all the stuff available for everyone
        self.Fitbox = [fitbox, weightedfitdrop, fittext, fittext2,
                       fittextvar, fitspin, buttonfit, textalg,
                       self.AlgorithmDropdown]
        # Disable Fitting since no data has been loaded yet
        for element in self.Fitbox:
            element.Disable()
        self.panelsettings.sizer.Fit(self.panelsettings)
        self.parent.Layout()

    def updateChi2(self):
        """
        updates the self.WXTextChi2 text control
        """
        label = u""
        if hasattr(self.corr, "fit_results"):
            if "chi2" in self.corr.fit_results:
                chi2 = self.corr.fit_results["chi2"]
                chi2str = wxutils.float2string_nsf(chi2, n=3)
                chi2str = wxutils.nice_string(chi2str)
                label = u"  χ²={}".format(chi2str)
        # This does not work with wxPython 2.8.12:
        # self.WXTextChi2.SetLabelMarkup(u"<b>{}</b>".format(label))
        self.WXTextChi2.SetLabel(u"{}".format(label))
