'''
Created on 12-Jul-2021

@author: rishi_priyansh
'''
import inspect
import traceback
import random

from sympy import (Symbol, symbols, Function, latex, Eq,
                   pi,
                   UnevaluatedExpr,
                   Integral,
                   sin, cos, pi
                   )

from sympy import (pi, latex,
                   Rational, 
                   symbols, Symbol, Function, 
                   UnevaluatedExpr,
                   Eq, dsolve, solve,
                   sqrt)
import sympy


from xv.km.util import PintUnitSystem

from xv.km.ui import SvgHelper         

#_wave_mechanics_helper
from xv.km.helper import BaseEquationHelper


class WaveMechanicsHelper:
    def __init__(self, b = None, e = None, count = 2, **kwargs):
        self.verbose = kwargs.get('verbose', False)
        pass


    def get_relation(self, 
                     method_name,
                     as_strings = False,
                     sep = "\n\n"):
        
        data_method = getattr(self, method_name)
        
        be_helper = BaseEquationHelper(verbose = self.verbose)
        relation_data = be_helper.get_relation(data_method)
        result = be_helper.get_standard_relation_statements(relation_data,
                                                            as_strings = as_strings,
                                                            sep = sep)
        return result
        
    def get_computation(self, 
                     method_name,
                     as_strings = False,
                     sep = "\n\n"):
        
        data_method = getattr(self, method_name)
        
        be_helper = BaseEquationHelper(verbose = self.verbose)
        relation_data = be_helper.get_computation(data_method)
        result = be_helper.get_standard_relation_statements(relation_data,
                                                            as_strings = as_strings,
                                                            sep = sep)
        return result
        
    def get_proportional_computation(self, 
                     method_name,
                     as_strings = False,
                     sep = "\n\n"):
        
        data_method = getattr(self, method_name)
        
        be_helper = BaseEquationHelper(verbose = self.verbose)
        relation_data = be_helper.get_computation(data_method)
        result = be_helper.get_standard_proportional_computation_statements(relation_data,
                                                            as_strings = as_strings,
                                                            sep = sep)
        return result

    def get_frequency(self):

        T, f = symbols(r'T, f')

        label_dict = {
            f: "Frequency",
            T: "Time period",
        }

        ps = PintUnitSystem()
        T_val = random.randint(1, 60)
        f_val = 1/T_val
        
        var_values = {
            f: f_val * ps.convert_to_base_units('1/sec'),
            T: T_val * ps.convert_to_base_units('sec'),
        }


        lhs_symbol = f
        const_symbols = []
        rhs_symbols = [T]

        t_lhs = f
        t_const = 1
        t_rhs = 1/T
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_period(self):

        T, f = symbols(r'T, f')

        label_dict = {
            T: "Time period",
            f: "Frequency",
        }

        ps = PintUnitSystem()
        f_val = random.randint(1, 10) * 10 ** random.randint(0, 18)
        T_val = 1/f_val
        
        var_values = {
            f: f_val * ps.convert_to_base_units('1/sec'),
            T: T_val * ps.convert_to_base_units('sec'),
        }


        lhs_symbol = T
        const_symbols = []
        rhs_symbols = [f]

        t_lhs = T
        t_const = 1
        t_rhs = 1/f
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_wave_in_a_stretched_string(self):

        F, rho, v = symbols(r'F, rho, v')

        label_dict = {
            F: "Tension in the string",
            rho: "Linear density of the string",
            v: "Wave speed",
        }

        ps = PintUnitSystem()
        F_val = random.randint(1, 100)
        rho_val = random.randint(1, 1000) * 10**-3
        v_val = float(sqrt(F_val / rho_val))        
        
        var_values = {
            F: F_val * ps.convert_to_base_units('newton'),
            rho: rho_val * ps.convert_to_base_units('kg/meter'),
            v: v_val * ps.convert_to_base_units('meter/sec'),
        }


        lhs_symbol = v
        const_symbols = []
        rhs_symbols = [F, rho]

        t_lhs = v
        t_const = 1
        t_rhs = sqrt(F / rho)
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_sinusoidal_wave(self):

        x, t, A, k, v = symbols(r'x, t, A, k, v')
        y = Function('y')(x, t)

        label_dict = {
            y: "Transverse displacement at position x and time t",
            x: "Position",
            t: "Time",
            A: "Amplitude",
            k: "Angular wave number",
            v: "Wave speed",
        }

        ps = PintUnitSystem()
        x_val = random.randint(-10, 10)
        t_val = random.randint(1, 60)
        A_val = random.randint(1, 100)
        k_val = float(2*pi* random.randint(1, 10) * 10 ** random.randint(0, 18))
        v_val = random.randint(1, ps.convert_to_base_units('speed_of_light').magnitude)        
        y_val = float(A_val * cos(k_val*(x_val - v_val*t_val)))
        
        var_values = {
            x: x_val * ps.convert_to_base_units('meter'),
            t: t_val * ps.convert_to_base_units('sec'),
            A: A_val,
            k: k_val * ps.convert_to_base_units('1/meter'),
            v: v_val * ps.convert_to_base_units('meter/sec'),
            y: y_val,
        }


        lhs_symbol = y
        const_symbols = []
        rhs_symbols = [x, t, A, k, v]

        t_lhs = y
        t_const = 1
        t_rhs = A * cos(k*(x - UnevaluatedExpr(v*UnevaluatedExpr(t))))
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_sinusoidal_wave_wave_speed(self):

        omega, k, v = symbols(r'omega, k, v')
            

        label_dict = {
            v: "Wave speed",
            omega: "Angular frequecny",
            k: "Wave number",
        }

        ps = PintUnitSystem()
        omega_val = float(2*pi / random.randint(1, 10) * 10 ** random.randint(-12, 6))
        k_val = float(2*pi* random.randint(1, 10) * 10 ** random.randint(0, 18))
        v_val = omega_val/k_val
        
        
        var_values = {
            v: v_val * ps.convert_to_base_units('meter/sec'),
            omega: omega_val * ps.convert_to_base_units('1/sec'),
            k: k_val * ps.convert_to_base_units('1/meter'),
        }


        lhs_symbol = v
        const_symbols = []
        rhs_symbols = [omega, k]

        t_lhs = v
        t_const = 1
        t_rhs = omega / k
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_sinusoidal_wave_period(self):

        omega, T = symbols(r'omega, T')
            

        label_dict = {
            T: "Time period",
            omega: "angular frequecny",
        }

        ps = PintUnitSystem()
        omega_val = float(2*pi* random.randint(1, 10) * 10 ** random.randint(0, 18))
        T_val = float(2*pi / omega_val)
        
        
        var_values = {
            T: T_val * ps.convert_to_base_units('sec'),
            omega: omega_val * ps.convert_to_base_units('1/sec'),
        }


        lhs_symbol = T
        const_symbols = [2*pi]
        rhs_symbols = [omega]

        t_lhs = T
        t_const = 2*pi
        t_rhs = UnevaluatedExpr(t_const) / omega
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_sinusoidal_wave_wavelength(self):

        lamda, k = symbols(r'lamda, k')
            

        label_dict = {
            k: "Wave number",
            lamda: "Wavelength",
        }

        ps = PintUnitSystem()
        lamda_val = random.randint(1, 10) * 10 ** random.randint(-12, 6)
        k_val = float(2*pi / lamda_val)
        
        
        var_values = {
            k: k_val * ps.convert_to_base_units('1/meter'),
            lamda: lamda_val * ps.convert_to_base_units('meter'),
        }


        lhs_symbol = k
        const_symbols = [2*pi]
        rhs_symbols = [lamda]

        t_lhs = k
        t_const = 2*pi
        t_rhs = UnevaluatedExpr(t_const) / lamda
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_sinusoidal_wave_power(self):

        v, omega, A, rho, P_avg = symbols(r'v, omega, A, rho, P_avg')
            

        label_dict = {
            v: "Wave speed",
            A: "Amplitude",
            P_avg: "Average power transmitted by the wave",
            omega: "Angular frequency",
            rho: "Density of a medium",
        }

        ps = PintUnitSystem()
        v_val = random.randint(1, ps.convert_to_base_units('speed_of_light').magnitude)
        A_val = random.randint(1, 100)
        omega_val = float(2*pi* random.randint(1, 10) * 10 ** random.randint(0, 18))
        rho_val = random.randint(1, 1000)
        P_avg_val = (1/2) * omega_val**2 * A_val**2 * rho_val * v_val
        
        
        var_values = {
            v: v_val * ps.convert_to_base_units('m/s'),
            A: A_val,
            omega: omega_val * ps.convert_to_base_units('radian/sec'),
            rho: rho_val * ps.convert_to_base_units('kg/m**3'),
            P_avg: P_avg_val * ps.convert_to_base_units('watt'),
        }


        lhs_symbol = P_avg
        const_symbols = [Rational(1, 2)]
        rhs_symbols = [omega, A, rho, v]

        t_lhs = P_avg
        t_const = Rational(1, 2)
        t_rhs = UnevaluatedExpr(t_const) * UnevaluatedExpr(omega**2 * A**2 * rho * v)
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_sinusoidal_wave_energy(self):

        v, E_l, P_avg = symbols(r'v, E_l, P_avg')
        # c = symbols(r'c', constant = True)
            

        label_dict = {
            v: "Wave speed",
            E_l: "Energy per unit length",
            P_avg: "Average power transmitted by the wave",
            # c: "speed oT light",
        }

        ps = PintUnitSystem()
        # c_val = ps.convert_to_base_units('speed_oT_light').magnitude
        v_val = random.randint(1, ps.convert_to_base_units('speed_of_light').magnitude)
        #todo
        P_avg_val = random.randint(1, 100) 
        E_l_val = P_avg_val / v_val
        
        
        var_values = {
            # c: ps.convert_to_base_units('speed_oT_light'),
            v: v_val * ps.convert_to_base_units('m/s'),
            P_avg: P_avg_val * ps.convert_to_base_units('watt'),
            E_l: E_l_val * ps.convert_to_base_units('joule'),
        }


        lhs_symbol = E_l
        const_symbols = []
        rhs_symbols = [P_avg, v]

        t_lhs = E_l
        t_const = 1
        t_rhs = P_avg / v
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_wave_speed_lamda_time(self):

        v, T, lamda = symbols(r'v, T, lamda')
        # c = symbols(r'c', constant = True)
            

        label_dict = {
            v: "Velocity",
            T: "Time",
            lamda: "Wavelength",
            # c: "speed oT light",
        }

        ps = PintUnitSystem()
        # c_val = ps.convert_to_base_units('speed_oT_light').magnitude
        T_val = random.randint(1, 60)
        lamda_val = random.randint(1, 10) * 10 ** random.randint(-12, 6)
        v_val = lamda_val / T_val
        
        
        var_values = {
            # c: ps.convert_to_base_units('speed_oT_light'),
            T: T_val * ps.convert_to_base_units('second'),
            lamda: lamda_val * ps.convert_to_base_units('meter'),
            v: v_val * ps.convert_to_base_units('m/s'),
        }


        lhs_symbol = v
        const_symbols = []
        rhs_symbols = [lamda, T]

        t_lhs = v
        t_const = 1
        t_rhs = lamda / UnevaluatedExpr(T) 
                                  
        eq = Eq(t_lhs, t_rhs)
                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_wave_speed_lamda_freq(self):

        v, f, lamda = symbols(r'v, f, lamda')
        # c = symbols(r'c', constant = True)
            

        label_dict = {
            v: "Velocity",
            f: "Frequency",
            lamda: "Wavelength",
            # c: "speed of light",
        }

        ps = PintUnitSystem()
        # c_val = ps.convert_to_base_units('speed_of_light').magnitude
        f_val = random.randint(1, 10) * 10 ** random.randint(0, 18)
        lamda_val = random.randint(1, 10) * 10 ** random.randint(-12, 6)
        v_val = lamda_val * f_val
        
        
        var_values = {
            # c: ps.convert_to_base_units('speed_of_light'),
            f: f_val * ps.convert_to_base_units('hertz'),
            lamda: lamda_val * ps.convert_to_base_units('meter'),
            v: v_val * ps.convert_to_base_units('m/s'),
        }


        lhs_symbol = v
        const_symbols = []
        rhs_symbols = [lamda, f]

        t_lhs = v
        t_const = 1
        t_rhs = lamda * UnevaluatedExpr(f) 
                                  
        eq = Eq(t_lhs, t_rhs)
                                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_electromagnetic_energy(self):

        E, f = symbols(r'E, f')
        h = Symbol(r'h', constant = True)
            

        label_dict = {
            E: "Electromagnetic energy",
            h: "Planck's constant",
            f: "frequency",
        }

        ps = PintUnitSystem()
        h_val = ps.convert_to_base_units('planck_constant').magnitude
        f_val = random.randint(1, 10) * 10 ** random.randint(0, 18)
        E_val = h_val * f_val
        
        
        var_values = {
            h: ps.convert_to_base_units('planck_constant'),
            f: f_val * ps.convert_to_base_units('hertz'),
            E: E_val * ps.convert_to_base_units('joule'),
        }


        lhs_symbol = E
        const_symbols = [h]
        rhs_symbols = [f]

        t_lhs = E
        t_const = h
        t_rhs = UnevaluatedExpr(t_const) * f
                                  
        eq = Eq(t_lhs, t_rhs)
                                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_speed_of_sound_in_an_ideal_gas(self):

        v, R, T, M = symbols(r'v, R, T, M')
        gamma = Symbol(r'\gamma')
            

        label_dict = {
            v: "speed of sound",
            R: "universal gas constant",
            T: "absolute temperature",
            M: "molecular mass of gas",
            gamma: "adiabatic constant",
        }

        ps = PintUnitSystem()
        R_val = ps.convert_to_base_units('molar_gas_constant').magnitude
        T_val = random.randint(273, 350)
        M_val = random.randint(20, 200)
        gamma_val = 1.67
        v_val = sqrt(gamma_val * R_val * T_val / M_val)
        
        
        var_values = {
            R: ps.convert_to_base_units('molar_gas_constant'),
            T: T_val * ps.convert_to_base_units('kelvin'),
            M: M_val * ps.convert_to_base_units('kg/mole'),
            gamma: gamma_val,
            v: v_val * ps.convert_to_base_units('m/sec'),
        }


        lhs_symbol = v
        const_symbols = [R]
        rhs_symbols = [gamma, T,  M]

        t_lhs = v ** 2
        t_const = R
        t_rhs = UnevaluatedExpr(gamma) * UnevaluatedExpr(R * T)  / M
                                  
        eq = Eq(t_lhs, t_rhs)
                                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    def get_doppler_effect(self):

        fs, fo, vo, vs, v = symbols(r'f_s, f_o, v_o, v_s, v')
        delta_vo = Symbol(r'\Delta v_o')
        delta_vs = Symbol(r'\Delta v_s')
        label_dict = {
            fs: "the source sound frequency",
            fo: "the observer sound frequency",
            v: "the velocity of sound in this medium, in direction from source to observer",
            vo: "the observer's velocity, in direction from source to observer",
            vs: "the source's velocity, in direction from source to observer",
            delta_vo: "v - vo, relative velocity of sound with respect to the observer",
            delta_vs: "v - vs, relative velocity of sound with respect to the source",
        }
         

        
        ps = PintUnitSystem()
        fs_val = random.randint(20, 20000)
        vo_val = random.choice([1, -1]) * random.randint(0, 5)
        vs_val = random.choice([1, -1]) * random.randint(0, 10)
        v_val = random.randint(300, 400)
        delta_vo_val = v_val - vo_val
        delta_vs_val = v_val - vs_val
        fo_val = fs_val * delta_vo_val / delta_vs_val
        
        
        var_values = {
            fs: fs_val * ps.convert_to_base_units('hertz'),
            fo: fo_val * ps.convert_to_base_units('hertz'),
            v: v_val  * ps.convert_to_base_units('m/sec'), 
            vo: vo_val  * ps.convert_to_base_units('m/sec'),
            vs: vs_val  * ps.convert_to_base_units('m/sec'),
            #delta_vo: delta_vo_val,
            #delta_vs: delta_vs_val,
            delta_vo: v - vo,
            delta_vs: v - vs,
        }


        lhs_symbol = fo
        const_symbols = []
        rhs_symbols = [fs, delta_vo, delta_vs]

        t_lhs = fo 
        t_const = 1
        t_rhs = UnevaluatedExpr(fs) * UnevaluatedExpr(delta_vo / delta_vs) 
                                  
        eq = Eq(t_lhs, t_rhs)
                                  
        data = {
            'prop_symbols': [lhs_symbol, const_symbols, rhs_symbols],
            'label_dict': label_dict,
            'var_values': var_values,
            'constant_value': t_const,
            'eq': eq,
            }

        return data
        pass

    pass
#class WaveMechanicsHelper:

    