'''
Created on 01-Apr-2021
@author: rishi_priyansh
'''

from sympy import ( Eq,
                    Limit,
                    Pow, Mul, Add, exp,
                    Function, latex, Integral, solve, 
                    UnevaluatedExpr, S, oo, log, logcombine)


import numpy as np
import random
import inspect
import traceback
from sympy import (symbols, Symbol)

from xv.km.util import latex_fraction, latex_atom
from ...helper import BaseHelper

from xv.km.managers import BaseManager
from xv.km.kelements import KProblemTemplate

# _thermodynamics_manager
# ThermodynamicsManager
        
class ThermodynamicsManager(BaseManager):
    '''
    Thermodynamics Fundamentals
    Example:
        ke = ThermodynamicsManager()
        ke
        
        ke.printProblemTypes()
        
        ke.getRandomProblem()
        ke.getRandomProblem(problem_type = 0)
        ke.getRandomProblem(problem_type = 1)
        ...
        
        ke.printProblem()
        ke.printAnswer()
        ke.printSolution()
        
        
    '''    
    def __new__(cls, *args, **kwargs):
        instance = super(ThermodynamicsManager, cls).__new__(cls, *args, **kwargs)
        for base_class in ThermodynamicsManager.__bases__:
            base_class.__init__(instance, *args, **kwargs)
        return instance
    
    def __init__(self, *args, **kwargs):
        self.base_helper = BaseHelper(**kwargs)
        pass

    def _init_problem_types(self, *args, **kwargs):
        self._append_problem_types([
            '_problem_overview_questions_laws_of_thermodynamics',
            '_problem_iit_thermodynamics',
            '_problem_the_first_law_of_thermodynamics',
            '_problem_the_second_law_of_thermodynamics',
            '_problem_the_third_law_of_thermodynamics',
            '_problem_rel_onsager_reciprocal_rels_and_thermo_flows',
            '_problem_molar_heat_capacities',
            '_problem_molar_heat_capacity_and_universal_gas_constant',
            '_problem_derive_molar_heat_capacity_of_polytropic_process',
            '_problem_polytropic_process',
            '_problem_polytropic_process_equation',
            '_problem_adiabatic_process_equation',
            '_problem_polytropic_process_work_done',
            '_problem_polytropic_process_graph',
            '_problem_calculate_force_bouncing_off_the_wall',
            '_problem_pressure_on_the_wall_of_cube'
             ])
        pass
    
    
    def _problem_overview_questions_laws_of_thermodynamics(self, *args, **kwargs):
        """
        """
        
        folder_path = "question_bank/physics/thermodynamics"
        search_file_paths = [
            "overview_questions_laws_of_thermodynamics.txt"
            ]
        
        file_paths = self.get_q_bank_factory().search_question_bank_files(folder_path, search_file_paths)
        return self.get_q_bank_factory().PROBLEM_TEMPLATE_Q_BANK_RANDOM_QUESTION(file_paths, *args, **kwargs)

    def _problem_iit_thermodynamics(self, *args, **kwargs):
        """
        """
        
        folder_path = "question_bank/physics/thermodynamics"
        search_file_paths = [
            "iit_thermodynamics.txt"
            ]
        
        file_paths = self.get_q_bank_factory().search_question_bank_files(folder_path, search_file_paths)
        return self.get_q_bank_factory().PROBLEM_TEMPLATE_Q_BANK_RANDOM_QUESTION(file_paths, *args, **kwargs)


    def _problem_the_first_law_of_thermodynamics(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_the_first_law_of_thermodynamics(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_the_second_law_of_thermodynamics(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_the_second_law_of_thermodynamics(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_the_third_law_of_thermodynamics(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_the_third_law_of_thermodynamics(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_rel_onsager_reciprocal_rels_and_thermo_flows(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_rel_onsager_reciprocal_rels_and_thermo_flows(
            **kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_molar_heat_capacities(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_molar_heat_capacities(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_molar_heat_capacity_and_universal_gas_constant(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_molar_heat_capacity_and_universal_gas_constant(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass


    def _problem_derive_molar_heat_capacity_of_polytropic_process(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_derive_molar_heat_capacity_of_polytropic_process(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_polytropic_process(self, *args, **kwargs):
        """
        """
        
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_polytropic_process(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate
        pass

    def _problem_polytropic_process_equation(self, *args, **kwargs):
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_polytropic_process_equation(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

        pass
    
    def _problem_adiabatic_process_equation(self, *args, **kwargs):
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_adiabatic_process_equation(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

        pass
    

    def _problem_polytropic_process_work_done(self, *args, **kwargs):
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_polytropic_process_work_done(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

        pass
    
    def _problem_polytropic_process_graph(self, *args, **kwargs):
        td_helper = self.base_helper.get_thermodynamics_helper()
        
        result = td_helper.get_problem_polytropic_process_graph(**kwargs)
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        prob_s = f"""
        {result['prob_s'].strip()}
        """
        problemTemplate.add_problem(prob_s.strip(), 'text/html')
        
        soln_s = f"""
        {result['soln_s'].strip()}
        """
        problemTemplate.add_solution(soln_s.strip(), 'text/html')
        
        answer_s = f"""
        {result['answer_s'].strip()}
        """
        problemTemplate.add_answer(answer_s.strip(), 'text/html')
        
        return problemTemplate

        pass
    
    

    def _eq_subs(self, eq, *args, **kwargs):
        if len(args) != 1:
            try:
                return eq.subs(*args)
                pass
            except Exception as e:
                print(f"Error _eq_subs: {e}")
                return eq
                pass
            pass

        try:
            if isinstance(args[0], Eq):
                swap_sides = kwargs.get('swap_sides', False)
                if not swap_sides:
                    return eq.subs(args[0].lhs, args[0].rhs)
                else:
                    return eq.subs(args[0].rhs, args[0].lhs)
            else:
                return eq.subs(args[0])
                
        except Exception as e:
            print(f"Error _eq_subs: {e}")
            return eq
            pass
        pass    

       
    
    def _first_law(self, *args, **kwargs):
            
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""State the Equation of the first law of Thermodynamics in mathematical form."""
        expr = f''
        problemTemplate.add_problem(statement)
        problemTemplate.add_problem(expr, 'text/html')
        
        answer = '''$dU = dQ - dW$'''
        problemTemplate.add_answer(answer, 'text/html')
        solution = answer + '''
            where,
                \t$dU$ = Change in internal energy
                \t$dQ$ = Change in Heat
                \t$dW$= Work done
        '''
        problemTemplate.add_solution(solution, 'text/html')
        
        return problemTemplate
        pass

          
    def _second_law(self, *args, **kwargs):
            
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""State the Equation of the second law of Thermodynamics in mathematical form."""
        expr = f''
        problemTemplate.add_problem(statement)
        problemTemplate.add_problem(expr, 'text/html')
        
        #https://www.sciencedirect.com/topics/chemistry/second-law-of-thermodynamics#:~:text=One%20of%20the%20statements%20of,(1.6%2D2)     page no.78
        answer = '''$dS_{{universe}} = dS_{{sys}} + dS_{{surr}} \geq 0 $'''
        problemTemplate.add_answer(answer, 'text/html')
        
        solution = answer + '''
        where,
            \t$dS_{{universe}}$ = Change in entropy of the universe
            \t$dS_{{sys}}$ = Change in entropy of the system
            \t$dS_{{surr}}$ = Change in entropy of the surrounding
        '''
        problemTemplate.add_solution(solution, 'text/html')
        
        #https://www.youtube.com/watch?v=xJf6pHqLzs0
        answer = '''$\Delta S_{{u}} \geq 0 $'''
        problemTemplate.add_answer(answer, 'text/html')
        
        solution = answer + '''
        where,
            \t$\Delta S_{{u}}$ = Change in entropy of the universe
            
            '''
        problemTemplate.add_solution(solution, 'text/html')
        
        #Introductory Chemical Engineering Thermodynamics 2ed by J. Richard Elliott Carl T. Lira page no.161
        answer = '''$S = k ln(W) $
                    $\Delta S_{{u}} = N k ln(V_2/V_1) $
        '''
        problemTemplate.add_answer(answer, 'text/html')
        
        solution = answer + '''
        where,
            \t$S$ = Entropy
            \t$W$ = Don't know
            \t$k$ = Boltzmann's constant
            \t$N$ = Avogadro's number
            \t$V_1$ = Initial volume
            \t$V_2$ = Final volume
            
        '''
        problemTemplate.add_solution(solution, 'text/html')
        
        
        return problemTemplate
        pass
        
    
    
    def _problem_calculate_force_bouncing_off_the_wall(self, *args, **kwargs):
        from sympy.vector import CoordSys3D
        m, v, P, P_i, P_f, t, F, l = symbols('m, v, P, P_i, P_f, t, F, l')
        delta = Function('\Delta')
        D = CoordSys3D('D')
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""Calculate force of a particle bouncing off the wall travelling at velocity v and from distance l."""
        expr = f''
        problemTemplate.add_problem(statement)
        problemTemplate.add_problem(expr, 'text/html')
        
        answer = Eq(F, ((-m*v*D.i) - (m*v*D.i))/(2*l/v), evaluate = False)
        problemTemplate.add_answer(f'${latex(answer)}$', 'text/html')
        
        
        expr_init_momentum = Eq(P_i, m*v*D.i, evaluate = False)
        problemTemplate.add_solution('Initial momentum before particle bouncing off the wall.', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_init_momentum)}$', 'text/html')
        
        expr_final_momentum = Eq(P_f, -m*v*D.i, evaluate = False)
        problemTemplate.add_solution('Final momentum after particle bouncing off the wall.', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_final_momentum)}$', 'text/html')
        
        expr_diff_momentum = Eq(delta(P), P_f - P_i, evaluate = False)
        problemTemplate.add_solution('Difference in momentum.', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_diff_momentum)}$', 'text/html')
        
        expr_diff_momentum = Eq(delta(P), (-m*v*D.i) - (m*v*D.i), evaluate = False)
        problemTemplate.add_solution(f'${latex(expr_diff_momentum)}$', 'text/html')
        
        expr_diff_time = Eq(delta(t), 2*l/v, evaluate = False)
        problemTemplate.add_solution('Difference in time of the particle.', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_diff_time)}$', 'text/html')
        
        expr_force = Eq(F, delta(P)/delta(t), evaluate = False)
        problemTemplate.add_solution("Force is difference in momentum with respect to time(Newton's second law).", 'text/html')
        problemTemplate.add_solution(f'${latex(expr_force)}$', 'text/html')
        
        
        problemTemplate.add_solution(f'${latex(answer)}$', 'text/html')
        
        return problemTemplate
        pass
        
                    

    def _problem_pressure_on_the_wall_of_cube(self, *args, **kwargs):
        from sympy.vector import CoordSys3D
        m, v, P, P_i, P_f, t, F, l, N, A, V = symbols('m, v, P, P_i, P_f, t, F, l, N, A, V')
        univec_v = symbols('{\widehat{{v}}}', vector = True, real = True)
        delta = Function('\Delta')
        D = CoordSys3D('D')
        
        problemTemplate = KProblemTemplate()
        problemTemplate.problem_type = kwargs.get('problem_type')
        
        statement = f"""In a cube of length L particles are moving with a average speed v. Find an expression for pressure on the wall of the cube.."""
        expr = f''
        problemTemplate.add_problem(statement)
        problemTemplate.add_problem(expr, 'text/html')
        
        answer = Eq(P, (((N / 3)*(- m*v**2) / l)/(l**2))*univec_v, evaluate = False).subs({l**3: V})
        problemTemplate.add_answer(f'${latex(answer)}$', 'text/html')
        
        expr_force = Eq(F, ((-m*v**2)/l)*D.i, evaluate = False)
        problemTemplate.add_solution(f'From the last question we know that the force of a particle moving in ${latex(D.i)}$ direction is...', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_force)}$', 'text/html')
        
        expr_force = Eq(F, ((N / 3)*(- m*v**2) / l)*univec_v, evaluate = False)
        problemTemplate.add_solution(f'The force of N number of particle moving in all direction is...', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_force)}$ where ${latex(univec_v)}$` is unit vector of velocity $v$', 'text/html')
        
        expr_area = Eq(A, l**2, evaluate = False)
        problemTemplate.add_solution(f'The area of the surface is...', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_area)}$', 'text/html')
        
        expr_pressure = Eq(P, F/A, evaluate = False)
        problemTemplate.add_solution(f'The pressure on a surface is...', 'text/html')
        problemTemplate.add_solution(f'${latex(expr_pressure)}$', 'text/html')
        
        expr_pressure = Eq(P, (((N / 3)*(- m*v**2) / l))*univec_v/(l**2), evaluate = False)
        problemTemplate.add_solution(f'${latex(expr_pressure)}$', 'text/html')
        
        problemTemplate.add_solution(f'${latex(answer)}$', 'text/html')
        
        
        return problemTemplate
        pass
        