# -*- coding: utf-8 -*-
"""RAWAPItry

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1_BbR5MsqLVTNUNaIzJrZUXnZEww0mG9R
"""

from __future__ import absolute_import, division, print_function, unicode_literals

"""
Created on June 11, 2019

@author: Jesse B. Hopkins

##############################################################################
 This file is part of RAW.

RAW is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RAW is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with RAW.  If not, see <http://www.gnu.org/licenses/>.

##############################################################################

The purpose of this module is to provide an API for calling RAW functions from
other python programs. This is to provide easy access to RAW's functionality
in any data processing program you want to write in python
"""

from builtins import object, range, map, zip
# from io import open

# import os
import copy

# import tempfile
import traceback
import copy
import threading
import logging

import numpy as np
import pandas as pd
from scipy.optimize import curve_fit
from scipy import stats
from sklearn.metrics import r2_score

# raw_path = os.path.abspath(os.path.join('.', __file__, '..', '..'))
# if raw_path not in os.sys.path:
#     os.sys.path.append(raw_path)


from . import sascalc as SASCalc  # import bioxtasraw.sascalc as SASCalc
from . import (
    sasexceptions as SASExceptions,
)  # import bioxtasraw.SASExceptions as SASExceptions

# import bioxtasraw.SASFileIO as SASFileIO
# import bioxtasraw.SASMask as SASMask
from . import sasm as SASM  # import bioxtasraw.SASM as SASM

# import bioxtasraw.SASProc as SASProc
# import bioxtasraw.RAWSettings as RAWSettings
# import bioxtasraw.RAWGlobals as RAWGlobals
# import bioxtasraw.SECM as SECM
from . import bift as BIFT  # import bioxtasraw.BIFT as BIFT
# import bioxtasraw.DENSS as DENSS
# import bioxtasraw.SASUtils as SASUtils
# import bioxtasraw.RAWReport as RAWReport


def make_profile(q, i, err, name, q_err=None):
    """
    Makes a profile (:class:`bioxtasraw.SASM.SASM`) from q, I, and uncertainty
    vectors. All three input vectors must be the same length.

    Parameters
    ----------
    q: iterable
        The q vector for the scattering profile. Should be an iterable that can
        be cast to a :class:`numpy.array`, such as a list or :class:`numpy.array`.
    i: iterable
        The intensity vector for the scattering profile. Should be an iterable
        that can be cast to a :class:`numpy.array`, such as a list or
        :class:`numpy.array`.
    err: iterable
        The uncertainty vector for the scattering profile. Should be an iterable
        that can be cast to a :class:`numpy.array`, such as a list or
        :class:`numpy.array`.
    name: str
        The name of the profile. When loading a profile from a file, this is
        by default set as the filename (without the full path).
    q_err: iterable, optional
        The uncertainty vector in q for the scattering profile. Should be
        either None or an iterable that can be cast to a :class:`numpy.array',
        such as a list or :class:`numpy.array`. Default is None. Typically only
        used for SANS data.

    Returns
    -------
    profile: :class:`bioxtasraw.SASM.SASM`
        A scattering profile object that can be used with the other functions in
        the API.
    """

    profile = SASM.SASM(i, q, err, {"filename": name}, q_err)

    return profile


def bift(
    profile,
    idx_min=None,
    idx_max=None,
    pr_pts=100,
    alpha_min=150,
    alpha_max=1e10,
    alpha_pts=16,
    dmax_min=10,
    dmax_max=400,
    dmax_pts=10,
    mc_runs=300,
    use_guinier_start=True,
    single_proc=True,
    nprocs=None,
    settings=None,
):
    #### Maybe increase Dmax max and also maybe the minimum

    # def bift(profile, idx_min=None, idx_max=None, pr_pts=100, alpha_min=150,
    # alpha_max=1e10, alpha_pts=16, dmax_min=10, dmax_max=400, dmax_pts=10,
    # mc_runs=300, use_guinier_start=True, single_proc=True, nprocs=None,
    # settings=None):
    """
    Calculates the Bayesian indirect Fourier transform (BIFT) of a scattering
    profile to generate a P(r) function and determine the maximum dimension
    Dmax. Returns None and -1 values if BIFT fails.

    Parameters
    ----------
    profile: :class:`bioxtasraw.SASM.SASM`
        The profile to calculate the BIFT for.
    idx_min: int, optional
        The index of the q vector that corresponds to the minimum q point
        to be used in the IFT. Default is to use the first point of the q
        vector, unless use_guinier_start is set.
    idx_max: int, optional
        The index of the q vector that corresponds to the maximum q point
        to be used in the IFT. Default is to use the last point of the
        q vector.
    pr_pts: int, optional
        The number of points in the calculated P(r) function. This should
        be less than the number of points in the scattering profile.
        If settings are provided, this is overridden by the value in the
        settings.
    alpha_min: float, optional
        The minimum value of alpha for the parameter search step. If settings
        are provided, this is overridden by the value in the settings. The
        value of alpha can go beyond this bound in the optimization step,
        so this is not a hard limit on alpha.
    alpha_max: float, optional
        The maximum value of alpha for the parameter search step. If settings
        are provided, this is overridden by the value in the settings. The
        value of alpha can go beyond this bound in the optimization step,
        so this is not a hard limit on alpha.
    alpha_pts: int, optional
        The number of points in the alpha search space, which will be
        logarithmically spaced between alpha_min and alpha_max. If settings
        are provided, this is overridden by the value in the settings.
    dmax_min: float, optional
        The minimum value of Dmax for the parameter search step. If settings
        are provided, this is overridden by the value in the settings. The
        value of Dmax can go beyond this bound in the optimization step,
        so this is not a hard limit on Dmax.
    dmax_max: float, optional
        The maximum value of Dmax for the parameter search step. If settings
        are provided, this is overridden by the value in the settings. The
        value of Dmax can go beyond this bound in the optimization step,
        so this is not a hard limit on Dmax.
    dmax_pts: int, optional
        The number of points in the Dmax search space, which will be linearly
        spaced between dmax_min and dmax_max. If settings are provided, this
        is overridden by the value in the settings.
    mc_runs: int, optional
        The number of monte carlo runs used to generate the uncertainty
        estimates for the P(r) function.
    use_guiner_start: bool, optional
        If set to True, and no idx_min idx_min is provided, if a Guinier fit has
        been done for the input profile, the start point of the Guinier fit is
        used as the start point for the IFT.
    single_proc: bool, optional
        Whether to use one or multiple processors. Defaults to True. In limited
        testing the single processor version has been found to be 2-3x faster
        than the multiprocessor version, but actual results may depend on
        the computer and the number of gird search points.
    nprocs: int, optional
        If specified, and single_proc is False, determines the number of processors
        to use for BIFT. Otherwise defaults to number of processors in the computer
        -1 (minimum 1).
    settings: :class:`bioxtasraw.RAWSettings.RAWSettings`, optional
        RAW settings containing relevant parameters. If provided, the
        pr_Pts, alpha_min, alpha_max, alpha_pts, dmax_min, dmax_max, dmax_pts,
        and mc_runs parameters will be overridden with the values in the
        settings. Default is None.

    Returns
    -------
    ift: :class:`bioxtasraw.SASM.IFTM`
        The IFT calculated by BIFT from the input profile.
    dmax: float
        The maximum dimension of the P(r) function found by BIFT.
    rg: float
        The real space radius of gyration (Rg) from the P(r) function.
    i0: float
        The real space scattering at zero angle (I(0)) from the P(r) function.
    dmax_err: float
        The uncertainty in the maximum dimension of the P(r) function found
        by BIFT.
    rg_err: float
        The uncertainty in the real space radius of gyration (Rg) from the P(r)
        function.
    i0_err: float
        The uncertainty in the real space scattering at zero angle (I(0)) from
        the P(r) function.
    chi_sq: float
        The chi squared value of the fit of the scattering profile calculated
        from the P(r) function to the input scattering profile.
    log_alpha: float
        Log base 10 of the alpha value for the IFT.
    log_alpha_err: float
        Log base 10 of the uncertainty in the alpha value for the IFT.
    evidence: float
        The Bayesian evidence of the IFT.
    evidence_err: float
        The uncertainty in the Bayesian evidence of the IFT.
    """

    if settings is not None:
        pr_pts = settings.get("PrPoints")
        alpha_min = settings.get("minAlpha")
        alpha_max = settings.get("maxAlpha")
        alpha_pts = settings.get("AlphaPoints")
        dmax_min = settings.get("maxDmax")
        dmax_max = settings.get("minDmax")
        dmax_pts = settings.get("DmaxPoints")
        mc_runs = settings.get("mcRuns")

    q = profile.getQ()
    i = profile.getI()
    err = profile.getErr()
    filename = profile.getParameter("filename")

    if idx_min is None and use_guinier_start:
        analysis_dict = profile.getParameter("analysis")
        if "guinier" in analysis_dict:
            guinier_dict = analysis_dict[
                "guinier"
            ]  # **has a dictionary having a dictionary as key/value analysis_dict['guinier'] =info_dict
            idx_min = max(0, int(guinier_dict["nStart"]) - profile.getQrange()[0])
        else:
            idx_min = 0

    elif idx_min is None:
        idx_min = 0

    if idx_max is not None:
        q = q[idx_min : idx_max + 1]
        i = i[idx_min : idx_max + 1]
        err = err[idx_min : idx_max + 1]
    else:
        q = q[idx_min:]
        i = i[idx_min:]
        err = err[idx_min:]

    if nprocs is None:
        nprocs = 0

    bift_settings = {
        "npts": pr_pts,
        "alpha_max": alpha_max,
        "alpha_min": alpha_min,
        "alpha_n": alpha_pts,
        "dmax_min": dmax_min,
        "dmax_max": dmax_max,
        "dmax_n": dmax_pts,
        "mc_runs": mc_runs,
        "single_proc": single_proc,
        "nprocs": nprocs,
    }

    ift = BIFT.doBift(q, i, err, filename, **bift_settings)

    if ift is not None:
        dmax = float(ift.getParameter("dmax"))
        dmax_err = float(ift.getParameter("dmaxer"))
        rg = float(ift.getParameter("rg"))
        rg_err = float(ift.getParameter("rger"))
        i0 = float(ift.getParameter("i0"))
        i0_err = float(ift.getParameter("i0er"))
        chi_sq = float(ift.getParameter("chisq"))
        log_alpha = float(ift.getParameter("alpha"))
        log_alpha_err = float(ift.getParameter("alpha_er"))
        evidence = float(ift.getParameter("evidence"))
        evidence_err = float(ift.getParameter("evidence_er"))
        qmin = q[0]  # uncommented these 2
        qmax = q[-1]
        ##################################### NEED TO ADD TO GL GUINIER For Maybe do possible more fits

        results_dict = {}
        results_dict["Dmax"] = str(dmax)
        results_dict["Dmax_Err"] = str(dmax_err)
        results_dict["Real_Space_Rg"] = str(rg)
        results_dict["Real_Space_Rg_Err"] = str(rg_err)
        results_dict["Real_Space_I0"] = str(i0)
        results_dict["Real_Space_I0_Err"] = str(i0_err)
        results_dict["ChiSquared"] = str(chi_sq)
        results_dict["LogAlpha"] = str(log_alpha)
        results_dict["LogAlpha_Err"] = str(log_alpha_err)
        results_dict["Evidence"] = str(evidence)
        results_dict["Evidence_Err"] = str(evidence_err)
        results_dict["qStart"] = str(qmin)
        results_dict["qEnd"] = str(qmax)

        analysis_dict = profile.getParameter("analysis")
        analysis_dict["BIFT"] = results_dict
        profile.setParameter("analysis", analysis_dict)

    else:
        dmax = -1
        dmax_err = -1
        rg = -1
        rg_err = -1
        i0 = -1
        i0_err = -1
        chi_sq = -1
        log_alpha = -1
        log_alpha_err = -1
        evidence = -1
        evidence_err = -1
        qmin = q[0]
        qmax = q[-1]

    return (
        ift,
        dmax,
        rg,
        i0,
        dmax_err,
        rg_err,
        i0_err,
        chi_sq,
        log_alpha,
        log_alpha_err,
        evidence,
        evidence_err,
    )


def auto_guinier(profile, error_weight=True, single_fit=True, settings=None):
    """
    Automatically calculates the Rg and I(0) values from the Guinier fit by
    determining the best range for the Guinier fit.

    Parameters
    ----------
    profile: :class:`bioxtasraw.SASM.SASM`
        The profile to perform the Guineir fit on.
    error_weight: bool, optional
        If True (default), then the Guinier fit is calculated in an error
        weighted fashion. If not, the Guinier fit is calculated without
        error weight. This is overridden by the value in the settings if
        a settings object is provided.
    single_fit: bool, optional
        If True (default), then after the correct range for the Guinier fit
        is found a traditional Guinier fit is performed using that range. If
        False, currently the same is true. In the future, if False then the Rg
        and I(0) values may be averages over some range of best Guinier fit
        intervals.
    settings: :class:`bioxtasraw.RAWSettings.RAWSettings`, optional
        RAW settings containing relevant parameters. If provided, the
        error_weight parameter will be overridden with the value in the
        settings. Default is None.

    Returns
    -------
    rg: float
        The Rg value of the fit.
    i0: float
        The I(0) value of the fit.
    rg_err: float
        The uncertainty in Rg. This is calculated as the largest
        of the uncertainty returned from autorg and the uncertatiny as
        calculated from the covariance of the Guinier fit with the autorg
        determined ranges.
    i0_err: float
        The uncertainty in I(0). This is calculated as the largest
        of the uncertainty returned from autorg and the uncertatiny as
        calculated from the covariance of the Guinier fit with the autorg
        determined ranges.
    qmin: float
        The minimum q value of the Guinier fit.
    qmax: float
        The maximum q value of the Guinier fit.
    qRg_min: float
        The q*Rg value at the minimmum q value of the Guinier fit.
    qRg_max: float
        The q*Rg value at the maximum q value of the Guinier fit.
    idx_min: int
        The minimum index of the q vector used for Guinier fit.
    idx_max: int
        The maximum index of the q vector used for the GUinier fit.
    r_sqr: float
        The r^2 value of the fit.
    """

    if settings is not None:
        error_weight = settings.get("errorWeight")

    rg_auto, rger_auto, i0_auto, i0er_auto, idx_min, idx_max = SASCalc.autoRg(
        profile, single_fit, error_weight
    )

    if rg_auto != -1:
        q = profile.getQ()
        i = profile.getI()
        err = profile.getErr()

        rg_fit, i0_fit, rger_fit, i0er_fit, a, b = SASCalc.calcRg(
            q[idx_min : idx_max + 1],
            i[idx_min : idx_max + 1],
            err[idx_min : idx_max + 1],
            transform=True,
            error_weight=error_weight,
        )

        if single_fit:
            rg = float(rg_fit)
            i0 = float(i0_fit)
        else:
            rg = float(rg_auto)
            i0 = float(i0_auto)

        rg_err = max(float(rger_fit), float(rger_auto))
        i0_err = max(float(i0er_fit), float(i0er_auto))

        qmin = q[idx_min]
        qmax = q[idx_max]
        qRg_min = qmin * rg
        qRg_max = qmax * rg

        # Get fit r squared:
        x = np.square(q[idx_min : idx_max + 1])
        y = np.log(i[idx_min : idx_max + 1])
        y_fit = SASCalc.linear_func(x, a, b)
        error = y - y_fit
        r_sqr = 1 - np.square(error).sum() / np.square(y - y.mean()).sum()

        nmin_offset, _ = profile.getQrange()
        ##############ADDD TO G GUINIER #######################################################################
        info_dict = {}
        info_dict["Rg"] = rg
        info_dict["I0"] = i0
        info_dict["nStart"] = idx_min + nmin_offset
        info_dict["nEnd"] = idx_max + nmin_offset
        info_dict["qStart"] = qmin
        info_dict["qEnd"] = qmax
        info_dict["qRg_min"] = qRg_min
        info_dict["qRg_max"] = qRg_max
        info_dict["Rg_fit_err"] = rger_fit
        info_dict["I0_fit_err"] = i0er_fit
        info_dict["Rg_est_err"] = -1
        info_dict["I0_est_err"] = -1
        info_dict["Rg_autorg_err"] = rger_auto
        info_dict["I0_autorg_err"] = i0er_auto
        info_dict["Rg_err"] = rg_err
        info_dict["I0_err"] = i0_err
        info_dict["rsq"] = r_sqr

        analysis_dict = profile.getParameter("analysis")
        analysis_dict["guinier"] = info_dict
        profile.setParameter("analysis", analysis_dict)
    #############################################################################
    else:
        rg = -1.0
        i0 = -1.0
        rg_err = -1.0
        i0_err = -1.0
        qmin = -1.0
        qmax = -1.0
        qRg_min = -1.0
        qRg_max = -1.0
        idx_min = -1
        idx_max = -1
        r_sqr = -1.0
        # **I wonder why not return the dictionary (did they forget)- maybe that would make more effient ?
        # ** ANS don't need to bc they call the setParameter() method to object class SASM to add

    return (
        rg,
        i0,
        rg_err,
        i0_err,
        qmin,
        qmax,
        qRg_min,
        qRg_max,
        idx_min,
        idx_max,
        r_sqr,
    )


def Guiner_Data_Process(raw_data):
    """
    Gets the data in the format to be able to do the fit, like
    getting the ln(i) from a data frame made in GL guinier function call
    """
    q = np.array(raw_data["q"])
    i = np.array(raw_data["I"])
    q2 = np.power(q, 2)
    log_i = np.log(i)
    raw_data["q2"] = q2
    raw_data["ln(I)"] = log_i
    raw_data["II"] = i
    raw_data["q"] = q
    return raw_data


def Guiner_Fitting_V1(processed_data):
    line_fit = stats.linregress(xdata, ydata)  # does the fit
    slope = line_fit.slope  # finds slope
    yint.append(line_fit.intercept)
    r2 = line_fit.rvalue**2
    rg = (-slope * 3) ** (1 / 2)


def Guinier_GL(
    profile, settings=None
):  # P sure dont need settings here but just in cae
    """
    Runs Guinier method used at GL lab to match outputs with preferences from lab
    manual analysis, calculating Guinier fit by varying window size as well as using filtering
    to threshold solutions to be from the first low q points (qmin<0.005)
    """
    q = profile.getQ()
    i = profile.getI()
    err = profile.getErr()
    DF = pd.DataFrame()
    DF["q"] = q
    DF["I"] = i
    DF["Error"] = err
    # This function just performs math with q and I
    Test_Data = Guiner_Data_Process(DF)
    # filtering out the q2 regions above 0.005
    Test_Data = Test_Data.loc[DF["q2"] <= 0.005]  # Revise.
    ## Extending window fitting
    Rgs = []
    r2s = []
    Rg_max_Arr = []
    Rg_min_Arr = []  # added 0624
    N = []
    n_min = []
    n_max = []
    window_length = []
    i0_f = []
    qmin = []
    qmax = []
    # Variable window fitting. Will loop through each combination of starting index and window length for
    # each sample provided. A little time consuming, but not that bad for now.
    # Made window sligtly smaller as this is also done for BIOXTAS and polymer seem to have more an exponential curve so finding linear w large window hard
    for window in range(3, 40):
        for n in range(0, len(Test_Data) - window):
            q2 = Test_Data["q2"][n : n + window]
            q = Test_Data["q"][n : n + window]
            i_0 = Test_Data["II"][n : n + window]  # added 0624
            log_I = Test_Data["ln(I)"][n : n + window]
            res = stats.linregress(q2, log_I)
            slope = res.slope  # finds slope
            rg = (-slope * 3) ** (1 / 2)
            rg_max = rg * (np.array(q2)[-1] ** 0.5)
            Rg_max_Arr.append(rg_max)
            rg_min = rg * (np.array(q2)[0] ** 0.5)
            Rg_min_Arr.append(rg_max)
            Rgs.append(rg / 10)
            r2s.append(res.rvalue**2)
            n_min.append(n)
            n_max.append(n + window - 1)
            qmin.append(q.iloc[0])
            qmax.append(q.iloc[window - 1])
            i0_f.append(i_0.iloc[0])
            window_length.append(window)
            # qmin = q[0]
            # qmax = q[-1]
            # Storing all calculated values in a dictionary, enables easy conversion to a DataFrame

            # qmin = q.iloc[0] #don't need to add else bc defined here and same name as biox
            # qmax = q.iloc[window-1]
    dic = {
        "Rg1": Rgs,
        "R2": r2s,
        "qRg_Max": Rg_max_Arr,
        "N_Min": n_min,
        "N_Max": n_max,
        "Window Length": window_length,
        "i0": i0_f,
        "Qmin": qmin,
        "Qmax": qmax,
        "qRg_Min": Rg_min_Arr,
    }
    df = pd.DataFrame(data=dic)
    print(df)
    # Various filters being applied to the dataframe to downselect our fits.
    try:
        df = df.loc[df["N_Min"] <= 50]
        # df = df.loc[df['R2'] >= 0.90] #1
        print("max R2 ", max(df["R2"]))
        print("minqrmin ", min(df["qRg_Min"]))
        print("min N min ", min(df["N_Min"]))
        print("first filt", df)
        df = df.loc[df["qRg_Min"] <= 1.2]  # Changed Rgmax
        print("2nd filt", df)
        print("max R2 ", max(df["R2"]))
        print("minqrmin ", min(df["qRg_Min"]))
        print("min N min ", min(df["N_Min"]))
        df = df.loc[df["qRg_Max"] <= 1.8]
        print("3rd filt", df)
        print("max R2 ", max(df["R2"]))
        print("minqrmin ", min(df["qRg_Min"]))
        print("min N min ", min(df["N_Min"]))
        df = df.loc[df["R2"] >= 0.70]
        # df = df.loc[df['N_Min'] <= 50] #changed from 16
        df = df.sort_values(by=["R2"], ascending=False)[0:20].reset_index(drop=True)
        print(df)
        print(len(df))
        print(df.iloc[0])
        d_final = df.iloc[0]
    except IndexError:
        df = df.loc[df["N_Min"] <= 50]
        # df = df.loc[df['R2'] >= 0.90] #1
        print("max R2 ", max(df["R2"]))
        print("minqrmin ", min(df["qRg_Min"]))
        print("min N min ", min(df["N_Min"]))
        print("first filt", df)
        df = df.loc[df["qRg_Min"] <= 1.2]  # Changed Rgmax
        print("2nd filt", df)
        print("max R2 ", max(df["R2"]))
        print("minqrmin ", min(df["qRg_Min"]))
        print("min N min ", min(df["N_Min"]))
        df = df.loc[df["qRg_Max"] <= 1.8]
        print("3rd filt", df)
        print("max R2 ", max(df["R2"]))
        print("minqrmin ", min(df["qRg_Min"]))
        print("min N min ", min(df["N_Min"]))
        # df = df.loc[df['R2'] >= 0.70]
        # df = df.loc[df['N_Min'] <= 50] #changed from 16
        df = df.sort_values(by=["R2"], ascending=False)[0:20].reset_index(drop=True)
        print(df)
        print(len(df))
        print(df.iloc[0])
        d_final = df.iloc[0]
        # df = df.loc[df['R2'] >= 0.70]
        # print('first filt',df)
        # df = df.loc[df['qRg_Min'] <= 1.2] #Changed Rgmax
        # print('2nd filt',df)
        # df = df.loc[df['qRg_Max'] <= 1.8]
        # print('3rd filt',df)
        # df = df.loc[df['N_Min'] <= 50] #changed from 16
        # df = df.sort_values(by=['R2'],ascending=False)[0:20].reset_index(drop=True)
        # print(df)
        # print(len(df))
        # print(df.iloc[0])
        # d_final = df.iloc[0]

    d_final = df.iloc[0]
    # Ratchet but just making all the variables agree w bioxtas
    # rg = d_final['Rg1'][0]
    # i0 = d_final['i0'][0]
    # srt = d_final['N_Min'][0]
    # n_end= d_final['N_Max'][0]
    # qRg_max = d_final['qRg_Max'][0]
    # qRg_min = rg*qmin
    # r_sqr = d_final['R2'][0]
    # qmin = d_final['Qmin'][0]
    # qmax =d_final['Qmax'][0]

    rg = d_final["Rg1"]
    i0 = d_final["i0"]
    idx_min = d_final["N_Min"]
    idx_max = d_final[
        "N_Max"
    ]  # used to be called n_end now idx_max to mathc output form of BioXtas
    qRg_max = d_final["qRg_Max"]
    qRg_min = rg * d_final["Qmin"]
    r_sqr = d_final["R2"]
    qmin = d_final["Qmin"]
    qmax = d_final["Qmax"]
    # Window Length':window_length}
    # dic = {'Rg1':Rgs,'R2':r2s,'qRg_Max':Rg_max_Arr,'N_Min':n_min,'N_Max':n_max,'Window Length':window_length}
    info_dict = {}
    info_dict["Rg"] = rg  #
    info_dict["I0"] = i0  #
    info_dict["nStart"] = idx_min  # idx_min + nmin_offset
    info_dict["nEnd"] = idx_max  # idx_max + nmin_offset
    info_dict["qStart"] = qmin  #
    info_dict["qEnd"] = qmax  #
    info_dict["qRg_min"] = qRg_min  #
    info_dict["qRg_max"] = qRg_max  #
    info_dict["rsq"] = r_sqr
    # MIght wanna add these seem like just fit errs can get from fn
    info_dict["Rg_fit_err"] = None
    info_dict["I0_fit_err"] = None
    info_dict["Rg_est_err"] = None
    info_dict["I0_est_err"] = None
    info_dict["Rg_autorg_err"] = None
    info_dict["I0_autorg_err"] = None
    info_dict["Rg_err"] = None
    info_dict["I0_err"] = None
    analysis_dict = profile.getParameter("analysis")
    analysis_dict["guinier"] = info_dict
    profile.setParameter("analysis", analysis_dict)
    # og return from guinier fit bioxtas if errors maybe need to add those
    return (rg, i0, qmin, qmax, qRg_min, qRg_max, idx_min, idx_max, r_sqr)


def guinier_fit(profile, idx_min, idx_max, error_weight=True, settings=None):
    # This is the second function that can be used to do the fit that u been using before other than auto#

    """
    Calculates the Rg and I(0) values from the Guinier fit defined by the
    input idx_min and idx_max parameters.

    Parameters
    ----------
    profile: :class:`bioxtasraw.SASM.SASM`
        The profile to perform the Guineir fit on.
    idx_min: int
        The index of the q vector that corresponds to the minimum q point
        to be used in the Guinier fit.
    idx_max: int
        The index of the q vector that corresponds to the maximum q point
        to be used in the Guinier fit.
    error_weight: bool, optional
        If True (default), then the Guinier fit is calculated in an error
        weighted fashion. If not, the Guinier fit is calculated without
        error weight. This is overridden by the value in the settings if
        a settings object is provided.
    settings: :class:`bioxtasraw.RAWSettings.RAWSettings`, optional
        RAW settings containing relevant parameters. If provided, the
        error_weight parameter will be overridden with the value in the
        settings. Default is None.

    Returns
    -------
    rg: float
        The Rg value of the fit.
    i0: float
        The I(0) value of the fit.
    rg_err: float
        The uncertainty in Rg. This is calculated as the largest
        of the uncertainty returned from autorg and the uncertainty as
        calculated from the covariance of the Guinier fit with the autorg
        determined ranges.
    i0_err: float
        The uncertainty in I(0). This is calculated as the largest
        of the uncertainty returned from autorg and the uncertainty as
        calculated from the covariance of the Guinier fit with the autorg
        determined ranges.
    qmin: float
        The minimum q value of the Guinier fit.
    qmax: float
        The maximum q value of the Guinier fit.
    qRg_min: float
        The q*Rg value at the minimmum q value of the Guinier fit.
    qRg_max: float
        The q*Rg value at the maximum q value of the Guinier fit.
    r_sqr: float
        The r^2 value of the fit.
    """

    if settings is not None:
        error_weight = settings.get("errorWeight")

    q = profile.getQ()[idx_min : idx_max + 1]
    i = profile.getI()[idx_min : idx_max + 1]
    ierr = profile.getErr()[idx_min : idx_max + 1]

    # Remove NaN and Inf values:
    q = q[np.where(np.isfinite(i))]
    ierr = ierr[np.where(np.isfinite(i))]
    i = i[np.where(np.isfinite(i))]

    x = np.square(q)
    yerr = np.absolute(ierr / i)
    y = np.log(i)

    rg, i0, rger_fit, i0er_fit, a, b = SASCalc.calcRg(
        x, y, yerr, transform=False, error_weight=error_weight
    )

    rger_est, i0er_est = SASCalc.estimate_guinier_error(
        x, y, yerr, transform=False, error_weight=error_weight
    )

    if rger_est is None:
        rg_err = float(rger_fit)
    else:
        rg_err = max(float(rger_fit), float(rger_est))

    if i0er_est is None:
        i0_err = float(i0er_fit)
    else:
        i0_err = max(float(i0er_fit), float(i0er_est))

    # Get fit statistics:
    y_fit = SASCalc.linear_func(x, a, b)
    error = y - y_fit
    r_sqr = 1 - np.square(error).sum() / np.square(y - y.mean()).sum()

    qmin = profile.getQ()[idx_min]
    qmax = profile.getQ()[idx_max]
    qRg_min = qmin * rg
    qRg_max = qmax * rg

    info_dict = {}
    info_dict["Rg"] = rg
    info_dict["I0"] = i0
    info_dict["nStart"] = idx_min
    info_dict["nEnd"] = idx_max
    info_dict["qStart"] = qmin
    info_dict["qEnd"] = qmax
    info_dict["qRg_min"] = qRg_min
    info_dict["qRg_max"] = qRg_max
    info_dict["Rg_fit_err"] = rger_fit
    info_dict["I0_fit_err"] = i0er_fit
    info_dict["Rg_est_err"] = rger_est
    info_dict["I0_est_err"] = i0er_est
    info_dict["Rg_autorg_err"] = -1
    info_dict["I0_autorg_err"] = -1
    info_dict["Rg_err"] = rg_err
    info_dict["I0_err"] = i0_err
    info_dict["rsq"] = r_sqr

    analysis_dict = profile.getParameter("analysis")
    analysis_dict["guinier"] = info_dict
    profile.setParameter("analysis", analysis_dict)

    return rg, i0, rg_err, i0_err, qmin, qmax, qRg_min, qRg_max, r_sqr
