"""
This module is used to fit a set of x,y data to a model passed to it. It is
used to calculate the slope and intercepts for the linearized fits.  Two things
should be noted:

First, this fitting module uses the NLLSQ module of SciPy rather than a linear
fit.  This along with a few other modules could probably be removed if we
move to a linear regression approach.

Second, this infrastructure does not allow for resolution smearing of the 
the models.  Hence the results are not that accurate even for pinhole
collimation of SANS but may be good for SAXS.  It is completely wrong for 
slit smeared data. 

"""
from __future__ import print_function

from scipy import optimize


class Parameter(object):
    """
    Class to handle model parameters - sets the parameters and their
    initial value from the model based to it.
    """
    def __init__(self, model, name, value=None):
        self.model = model
        self.name = name
        if value is not None:
            self.model.setParam(self.name, value)

    def set(self, value):
        """
            Set the value of the parameter
        """
        self.model.setParam(self.name, value)

    def __call__(self):
        """
            Return the current value of the parameter
        """
        return self.model.getParam(self.name)


def sasfit(model, pars, x, y, err_y, qmin=None, qmax=None):
    """
    Fit function

    :param model: sas model object
    :param pars: list of parameters
    :param x: vector of x data
    :param y: vector of y data
    :param err_y: vector of y errors
    """
    def f(params):
        """
        Calculates the vector of residuals for each point
        in y for a given set of input parameters.

        :param params: list of parameter values
        :return: vector of residuals
        """
        i = 0
        for p in pars:
            p.set(params[i])
            i += 1

        residuals = []
        for j in range(len(x)):
            if x[j] >= qmin and x[j] <= qmax:
                residuals.append((y[j] - model.runXY(x[j])) / err_y[j])
        return residuals

    def chi2(params):
        """
        Calculates chi^2

        :param params: list of parameter values

        :return: chi^2

        """
        sum = 0
        res = f(params)
        for item in res:
            sum += item * item
        return sum

    p = [param() for param in pars]
    out, cov_x, info, mesg, success = optimize.leastsq(f, p, full_output=1)
    # Calculate chi squared
    if len(pars) > 1:
        chisqr = chi2(out)
    elif len(pars) == 1:
        chisqr = chi2([out])

    return chisqr, out, cov_x


def calcCommandline(event):
    # Testing implementation
    # Fit a Line model
    from LineModel import LineModel
    line = LineModel()
    cstA = Parameter(line, 'A', event.cstA)
    cstB = Parameter(line, 'B', event.cstB)
    y = line.run()
    chisqr, out, cov = sasfit(line, [cstA, cstB], event.x, y, 0)
    # print "Output parameters:", out
    print("The right answer is [70.0, 1.0]")
    print(chisqr, out, cov)

scipy.optimize.leastsq