# ============================================================================== #
    # mainwindow.py - AcouSed                                   #
    # Copyright (C) 2024  INRAE                                              #
    #                                                                        #
    # This program 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.                                    #
    #                                                                        #
    # This program 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 this program.  If not, see <https://www.gnu.org/licenses/>. #

    #                                                      by Brahim MOUDJED #
# ============================================================================== #

# -*- coding: utf-8 -*-


import numpy as np
import settings as stg
from Model.GrainSizeTools import demodul_granulo, mix_gaussian_model


class AcousticInversionMethodHighConcentration():

    """ Thi class compute acoustic inversion method adapted for high suspended sediments concentration
     For instance, the case of dam flush downstream Rhone-Isere confluence (07/01/2018) evaluated at ~ 10g/L  """

    def __init__(self):

        pass

    # ==========================================
    #                 Functions
    # ==========================================

    # ---------- Computing sound speed ------------- #
    def water_velocity(self, T):
        """Computing sond speed from Bilaniuk and Wong 1993"""
        C = 1.40238744 * 1e3 + 5.03836171 * T - 5.81172916 * 1e-2 * T ** 2 + 3.34638117 * 1e-4 * T ** 3 - \
            1.48259672 * 1e-6 * T ** 4 + 3.16585020 * 1e-9 * T ** 5
        return C

    # -------- Computing water attenuation coefficient ----------- #
    def water_attenuation(self, freq, T):
        """Computing attenuation from François and Garrison 1982"""
        if T > 20:
            alpha = (3.964 * 1e-4 - 1.146 * 1e-5 * T + 1.45 * 1e-7 * T ** 2 - 6.5 * 1e-10 * T ** 3) * 1e-3 * \
                    (np.log(10) / 20) * (freq * 1e-3) ** 2
        else:
            alpha = (4.937 * 1e-4 - 2.59 * 1e-5 * T + 9.11 * 1e-7 * T ** 2 - 1.5 * 1e-8 * T ** 3) * 1e-3 * \
                    (np.log(10) / 20) * (freq * 1e-3) ** 2
        return alpha

    # --- Gaussian mixture ---
    def compute_particle_size_distribution_in_number_of_particles(self, num_sample, r_grain, frac_vol_cumul):
        min_demodul = 1e-6
        max_demodul = 500e-6

        sample_demodul = demodul_granulo(r_grain, frac_vol_cumul[num_sample], min_demodul, max_demodul)

        resampled_log_array = np.log(np.logspace(-10, -2, 3000))
        proba_vol_demodul = mix_gaussian_model(resampled_log_array,
                                               sample_demodul.demodul_data_list[2].mu_list,
                                               sample_demodul.demodul_data_list[2].sigma_list,
                                               sample_demodul.demodul_data_list[2].w_list)

        proba_vol_demodul = proba_vol_demodul / np.sum(proba_vol_demodul)
        ss = np.sum(proba_vol_demodul / np.exp(resampled_log_array) ** 3)
        proba_num = proba_vol_demodul / np.exp(resampled_log_array) ** 3 / ss

        return proba_num

    # ------------- Computing ks ------------- #
    def form_factor_function_MoateThorne2012(self, a, freq, C):
        """This function computes the form factor based on the equation of
        Moate and Thorne (2012)"""
        # computing the wave number
        k = 2 * np.pi * freq / C
        x = k * a
        f = (x ** 2 * (1 - 0.25 * np.exp(-((x - 1.5) / 0.35) ** 2)) * (1 + 0.6 * np.exp(-((x - 2.9) / 1.15) ** 2))) / (
                    42 + 28 * x ** 2)
        return f

    def ks(self, proba_num, freq, C):
        # --- Compute k_s by dividing two integrals ---
        resampled_log_array = np.log(np.logspace(-10, -2, 3000))
        a2f2pdf = 0
        a3pdf = 0
        for i in range(len(resampled_log_array)):
            a = np.exp(resampled_log_array)[i]
            a2f2pdf += a**2 * self.form_factor_function_MoateThorne2012(a, freq, C)**2 * proba_num[i]
            a3pdf += a**3 * proba_num[i]

        ks = np.sqrt(a2f2pdf / a3pdf)

        return ks

    # ------------- Computing sv ------------- #
    def sv(self, ks, M_sand):
        sv = (3 / (16 * np.pi)) * (ks ** 2) * M_sand
        return sv

    # ------------- Computing X ------------- #
    def X_exponent(self, freq1, freq2, sv_freq1, sv_freq2):
        X = np.log(sv_freq1 / sv_freq2) / np.log(freq1 / freq2)
        return X

    # -------------- Computing Kt -------------- #
    def kt_corrected(self, r, water_velocity, RxGain, TxGain, kt_ref):
        """Computing the instrument constant Kt that depends on gain and temperature"""
        # Cell size
        delta_r = r[1] - r[0]
        # Pulse length
        tau = 2 * delta_r / 1500
        # Sound speed
        cel = water_velocity
        # Reference pulse length
        tau_ref = 13.33333e-6
        # Reference sound speed
        c_ref = 1475
        # Gain
        gain = 10 ** ((RxGain + TxGain) / 20)
        # Computing Kt
        kt = kt_ref * gain * np.sqrt(tau * cel / (tau_ref * c_ref))    # 1D numpy array
        return kt

    # ------------- Computing J_cross_section ------------- #
    def j_cross_section(self, BS, r2D, kt):
        J_cross_section = (3 / (16 * np.pi)) * ((BS**2 * r2D**2) / kt**2)
        return J_cross_section

    # ------------- Computing alpha_s ------------- #
    def alpha_s(self, sv, j_cross_section, depth, alpha_w):
        alpha_s = (np.log(sv / j_cross_section) / (4 * depth)) - alpha_w
        return alpha_s

    # ------------- Computing interpolation of fine SSC -------------

    def M_profile_SCC_fine_interpolated(self, sample_depth, M_profile, range_cells, r_bottom):
        '''Computing interpolation of fine SSC data obtained from water sampling
        collected at various depth in the vertical sample'''
        res = np.zeros((len(range_cells),)) * np.nan
        l0 = sample_depth
        l1 = [l0.index(x) for x in sorted(l0)]
        l2 = [l0[k] for k in l1]
        c1 = [list(M_profile)[j] for j in l1]
        for i in range(len(c1) - 1):
            # print("i = ", i)
            r_ini = l2[i]
            c_ini = c1[i]
            r_end = l2[i + 1]
            c_end = c1[i + 1]
            # Computing the linear equation
            a = (c_end - c_ini) / (r_end - r_ini)
            b = c_ini - a * r_ini

            # Finding the indices of r_ini and r_end in the interpolated array
            loc = (range_cells >= r_ini) * (range_cells < r_end)
            # Filling the array with interpolation values
            res[loc] = range_cells[loc] * a + b

        # Filling first and last values
        i = 0
        while np.isnan(res[i]):
            res[i] = c1[0]
            i += 1

        # Filling the last values
        i = -1
        while np.isnan(res[i]):
            res[i] = c1[-1]
            i += -1

        if r_bottom.size != 0:
            res[np.where(range_cells > r_bottom)] = np.nan

        loc_point_lin_interp0 = range_cells[np.where((range_cells > l2[0]) & (range_cells < l2[-1]))]
        res0 = res[np.where((range_cells > l2[0]) & (range_cells < l2[-1]))]

        loc_point_lin_interp = loc_point_lin_interp0[np.where(loc_point_lin_interp0 > l2[0])]
        res = res0[np.where(loc_point_lin_interp0 > l2[0])]

        return (loc_point_lin_interp, res)

    # ------------- Computing zeta ------------- #
    def zeta(self, alpha_s, r, M_profile_fine):

        delta_r = r[1] - r[0]
        zeta = alpha_s / (np.sum(np.array(M_profile_fine)*delta_r))

        return zeta

    # ------------- Computing VBI ------------- #
    def VBI_cross_section(self, freq1, freq2,
                          zeta_freq1, zeta_freq2,
                          j_cross_section_freq1, j_cross_section_freq2,
                          r2D,
                          water_attenuation_freq1, water_attenuation_freq2,
                          X):

        logVBI = ((zeta_freq2 *
                  np.log(j_cross_section_freq1 * np.exp(4 * r2D * water_attenuation_freq1) /
                         (freq1 ** X)) -
                  zeta_freq1 *
                  np.log(j_cross_section_freq2 * np.exp(4 * r2D * water_attenuation_freq2) /
                         (freq2 ** X))) /
                  (zeta_freq2 - zeta_freq1))

        return np.exp(logVBI)

    # ------------- Computing SSC fine ------------- #
    def SSC_fine(self, zeta, r2D, VBI, freq, X, j_cross_section, alpha_w):
        SSC_fine = (1/zeta) * ( 1/(4 * r2D) * np.log((VBI * freq**X) / j_cross_section) - alpha_w)
        return SSC_fine

    # ------------- Computing SSC sand ------------- #
    def SSC_sand(self, VBI, freq, X, ks):
        SSC_sand = (16 * np.pi * VBI * freq ** X) / (3 * ks**2)
        return SSC_sand


