'''
Created on 20-Jan-2022

@author: bthakur
'''

import codecs
import inspect
import traceback
import random
import numpy as np
import pandas as pd
from sympy import (latex, Symbol, symbols,
                   Matrix, Identity, nsimplify,
                    Rational,
                    sin, cos, tan,
                    asin, acos, atan,
                    sinh, cosh, tanh,
                    log, exp, sqrt,
                   diff, factor, 
                   Function, Eq,
                   Add, 
                   simplify, solve, factorial,
                   UnevaluatedExpr as ueval
                   )
from sympy import Sum, Indexed, pi, oo, Integral
import sympy
from sympy.core.numbers import One, Integer
from sympy.series import series

from xv.km.util import latex_fraction


from xv.km.ui import SvgHelper
from xv.util.data import CacheUtil

import matplotlib.pyplot as plt
from xv.util import CategorizedSampleData, list_to_str
from xv.km.util._latex import latex_color


class FourierHelper:
    
    def __init__(self, **kwargs):
        self.verbose = kwargs.get('verbose', False)
        pass
    
    
    def get_fourier_expression(self):
        '''
        https://medium.com/@kamil2000budaqov/fourier-series-fourier-transform-ae748fcd424a
        
        https://www.bragitoff.com/2021/05/fourier-series-coefficients-and-visualization-python-program/
        
        http://192.168.1.208:8080/user/thakur/notebooks/published-docs/python-language/sympy_1_9/057-002-Concrete_-_SymPy_1-9_documentation.ipynb
        
        '''
        
        from sympy import Sum, Indexed, pi, oo, Integral
        
        
        problem_s = f"""
        Write expression for Fourier series
        """
        
        soln_items = []
        
        t = symbols('t')
        f = Function('f')(t)
        a0 = Symbol("a_0")

        T0 = Symbol("T_0", positive = True)
        omega0_expr = 2 * pi / T0
        omega0 = Symbol(latex(omega0_expr))

        m, n = symbols('m, n', integer = True, positive = True)

        n_theta = omega0  * n * t

        cos_expr = ueval(Indexed('a',n))  * ueval(cos(n_theta))
        sin_expr = ueval(Indexed('b',n))  * ueval(sin(n_theta))

        term_lhs = f
        
        term0 = a0       
        cos_term = Sum(cos_expr, (n, 1, oo))
        sin_term = Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term

        eq0 = Eq(term_lhs, term_rhs)
        
        soln_item = f"""
        Fourier series is defined as 
        
        $\\displaystyle {latex(eq0)} $
        """
        
        soln_items.append(soln_item.strip())


        soln_items.append("<h3>Part I: Compute expression for $a_0$</h3>")
        stmt_items, eq_a0 = self._expr_for_coeff_a0(f, m, n, t, a0, T0)
        soln_items.extend(stmt_items)

        soln_items.append("<h3>Part II: Compute expression for $a_n$ for $n>0$</h3>")
        stmt_items, eq_a_n = self._expr_for_coeff_a_n(f, m, n, t, a0, T0)
        soln_items.extend(stmt_items)

        soln_items.append("<h3>Part III: Compute expression for $b_n$ for $n>0$</h3>")
        stmt_items, eq_b_n = self._expr_for_coeff_b_n(f, m, n, t, a0, T0)
        soln_items.extend(stmt_items)

        soln_items.append("<h3>Part IV: Join the results</h3>")
        stmt_item = f"""
        $\\displaystyle {latex(eq0)} $

        Where:
        
        $\\displaystyle {latex(eq_a0)} $

        $\\displaystyle {latex(eq_a_n)} 
        \\qquad \\qquad \\text{{ for }} 
        {latex(n)} > 0
        $

        $\\displaystyle {latex(eq_b_n)} 
        \\qquad \\qquad \\text{{ for }} 
        {latex(n)} > 0
        $

        """
        
        soln_items.append(stmt_item.strip())

        
        soln_s = "\n\n".join(soln_items)
        
        answer_s = f"""
        """
        
        result = {
            'problem_s': problem_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        return result
        pass
    
    def _expr_for_coeff_a0(self, f, m, n, t, a0, T0):
        soln_items = []


        omega0_expr = 2 * pi / T0
        omega0 = Symbol(latex(omega0_expr))

        n_theta = omega0  * n * t

        cos_expr = ueval(Indexed('a',n))  * ueval(cos(n_theta))
        sin_expr = ueval(Indexed('b',n))  * ueval(sin(n_theta))

        term_lhs = f
        
        term0 = a0       
        cos_term = Sum(cos_expr, (n, 1, oo))
        sin_term = Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term

        eq0 = Eq(term_lhs, term_rhs)
        
        soln_item = f"""
        $\\displaystyle {latex(eq0)} $
        """
        soln_items.append(soln_item.strip())

        term0 = a0     
        cos_term = Sum(cos_expr, (n, 1, oo))
        sin_term = Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term
        term_rhs_symb = Symbol(f"""
        {latex(term0)} + {latex(cos_term)} + {latex(sin_term)}
        """)

        eq1 = Eq(term_lhs, term_rhs_symb)
        
        soln_item = f"""
        $\\displaystyle \\implies {latex(eq1)} $
        """
        
        #soln_items.append(soln_item.strip())
        
        lhs = Integral(term_lhs, (t, -T0/2, T0/2))
        
        rhs_t0 = Integral(term0, (t, -T0/2, T0/2))
        rhs_t1 = Integral(cos_term, (t, -T0/2, T0/2))
        rhs_t2 = Integral(sin_term, (t, -T0/2, T0/2))
        
        colors = ['red', 'green', 'blue']
        rhs_t0_colored = self._expr_to_symbol(rhs_t0, colors[0])
        rhs_t1_colored = self._expr_to_symbol(rhs_t1, colors[1])
        rhs_t2_colored = self._expr_to_symbol(rhs_t2, colors[2])
        
        rhs_str = f""" 
        {latex(rhs_t0_colored)} + {latex(rhs_t1_colored)} + {latex(rhs_t2_colored)} 
        """
        rhs = Symbol(rhs_str.strip())
        eq2 = Eq(lhs, rhs)
        
        I, I0, I1, I2 = symbols('I I_0 I_1 I_2')
        I0_colored = self._expr_to_symbol(I0, colors[0])
        I1_colored = self._expr_to_symbol(I1, colors[1])
        I2_colored = self._expr_to_symbol(I2, colors[2])

        I_rhs_str = f""" 
        {latex(I0_colored)} + {latex(I1_colored)} + {latex(I2_colored)} 
        """
        I_rhs = Symbol(I_rhs_str.strip())
        I_eq = Eq(I, I_rhs)
        
        soln_item = f"""
        $\\displaystyle \\implies {latex(eq2)} $
        
        $\\displaystyle \\implies {latex(I_eq)} 
        \\qquad \\qquad \\cdots (1.1)
        $
        
        """
        
        soln_items.append(soln_item.strip())


        I0_eq = Eq(I0, rhs_t0)
        rhs_t0_expr = a0
        rhs_t0_expr = Integral(rhs_t0_expr, (t, -T0/2, T0/2))
        I0_eq_soln = Eq(I0, rhs_t0_expr.doit())
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I0_eq.lhs)} $:
        <div style="color:{colors[0]};">
        
        $\\displaystyle {latex(I0_eq)} $
        
        $\\displaystyle \\implies {latex(I0_eq_soln)} 
        \\qquad \\qquad \\cdots (1.2)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())

        I1_eq = Eq(I1, rhs_t1)
        cos_term_mult1 =  ueval(Indexed('a',n)) * ueval(cos(n_theta))
        expr_intgr = Integral(cos_term_mult1, (t, -T0/2, T0/2))
        rhs_t1_expr = Sum(expr_intgr, (n, 1, oo))
        
        rhs_t1_expr_intgr_doit = 0 # rhs_t1_expr.doit()

        I1_eq_soln = Eq(I1, rhs_t1_expr_intgr_doit)
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I1_eq.lhs)} $:
        <div style="color:{colors[1]};">
        
        $\\displaystyle {latex(I1_eq)} $
        
        $\\displaystyle = {latex(rhs_t1_expr)} $
        
        Value of the above expression is zero. 

        $\\displaystyle \\implies {latex(I1_eq_soln)} 
        \\qquad \\qquad \\cdots (1.3)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())



        I2_eq = Eq(I2, rhs_t2)
        sin_term_mult1 =  ueval(Indexed('b',n)) * ueval(sin(n_theta))
        expr_intgr = Integral(sin_term_mult1, (t, -T0/2, T0/2))
        rhs_t2_expr = Sum(expr_intgr, (n, 1, oo))
        
        rhs_t2_expr_intgr_doit = rhs_t2_expr.doit()

        I2_eq_soln = Eq(I2, rhs_t2_expr_intgr_doit)
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I2_eq.lhs)} $:
        <div style="color:{colors[2]};">
        
        $\\displaystyle {latex(I2_eq)} $
        
        $\\displaystyle = {latex(rhs_t2_expr)} $
        
        Value of the above expression is zero. 

        $\\displaystyle \\implies {latex(I2_eq_soln)} 
        \\qquad \\qquad \\cdots (1.4)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())

        I_eq_soln_rhs = f"""
        {latex(I0_eq_soln.rhs)} + {latex(I1_eq_soln.rhs)} + {latex(I2_eq_soln.rhs)}
        """
        I_eq_soln_rhs = Symbol(I_eq_soln_rhs.strip())
        
        I_eq_soln_ueval = Eq(eq2.lhs, I_eq_soln_rhs)
        
        I_eq_soln = Eq(eq2.lhs, I0_eq_soln.rhs + I1_eq_soln.rhs + I2_eq_soln.rhs)
        
        a0_eq = Eq(ueval(Indexed('a', 0)), ueval(1/T0) * ueval(I_eq_soln.lhs))
        
        soln_item = f"""
        Hence, from (1.1), (1.2), (1.3) and (1.4):
        
        $\\displaystyle {latex(I_eq)} $
        
        $\\displaystyle \\implies {latex(I_eq_soln_ueval)} $
        
        $\\displaystyle \\implies {latex(I_eq_soln)} $
        
        $\\displaystyle \\implies {latex(a0_eq)}  
        \\qquad \\qquad \\cdots (1.5)
        $
        
        """
        
        soln_items.append(soln_item.strip())
        
        return soln_items, a0_eq
        pass
    
    def _expr_for_coeff_a_n(self, f, m, n, t, a0, T0):
        soln_items = []

        omega0_expr = 2 * pi / T0
        omega0 = Symbol(latex(omega0_expr))

        n_theta = omega0  * n * t
        m_theta = ueval(omega0_expr)  * ueval(m * t)

        cos_expr = ueval(Indexed('a',n))  * ueval(cos(n_theta))
        sin_expr = ueval(Indexed('b',n))  * ueval(sin(n_theta))

        mult1 = cos(m_theta)
        mult1_symb = self._expr_to_symbol(mult1, 'brown')
        
        term_lhs = ueval(f)
        
        term0 = a0       
        cos_term = Sum(cos_expr, (n, 1, oo))
        sin_term = Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term

        eq0 = Eq(term_lhs, term_rhs)
        
        soln_item = f"""
        $\\displaystyle {latex(eq0)} $
        """
        soln_items.append(soln_item.strip())


        term0 = ueval(mult1_symb) * ueval(a0)      
        cos_term = ueval(mult1_symb) * Sum(cos_expr, (n, 1, oo))
        sin_term = ueval(mult1_symb) * Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term
        term_rhs_symb = Symbol(f"""
        {latex(term0)} + {latex(cos_term)} + {latex(sin_term)}
        """)

        eq1_1 = Eq(term_lhs, term_rhs_symb)
        
        soln_item = f"""
        Multiply both sides with $\\displaystyle {latex(mult1_symb)} $:
        
        $\\displaystyle \\implies {latex(eq1_1)} $
        """
        
        soln_items.append(soln_item.strip())

        # remove color        
        mult1_symb = self._expr_to_symbol(mult1)
        
        term_lhs = mult1_symb * ueval(f)
        
        term0 = ueval(mult1_symb) * ueval(a0)      
        cos_term = Sum(ueval(mult1_symb) * cos_expr, (n, 1, oo))
        sin_term = Sum(ueval(mult1_symb) * sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term
        term_rhs_symb = Symbol(f"""
        {latex(term0)} + {latex(cos_term)} + {latex(sin_term)}
        """)

        eq1_2 = Eq(term_lhs, term_rhs_symb)
        
        soln_item = f"""
        $\\displaystyle \\implies {latex(eq1_2)} $
        """
        
        soln_items.append(soln_item.strip())
        
        lhs = Integral(term_lhs, (t, -T0/2, T0/2))
        
        rhs_t0 = Integral(term0, (t, -T0/2, T0/2))
        rhs_t1 = Integral(cos_term, (t, -T0/2, T0/2))
        rhs_t2 = Integral(sin_term, (t, -T0/2, T0/2))
        
        colors = ['red', 'green', 'blue']
        rhs_t0_colored = self._expr_to_symbol(rhs_t0, colors[0])
        rhs_t1_colored = self._expr_to_symbol(rhs_t1, colors[1])
        rhs_t2_colored = self._expr_to_symbol(rhs_t2, colors[2])
        
        rhs_str = f""" 
        {latex(rhs_t0_colored)} + {latex(rhs_t1_colored)} + {latex(rhs_t2_colored)} 
        """
        rhs = Symbol(rhs_str.strip())
        eq2 = Eq(lhs, rhs)
        
        I, I0, I1, I2 = symbols('I I_3 I_4 I_5')
        I0_colored = self._expr_to_symbol(I0, colors[0])
        I1_colored = self._expr_to_symbol(I1, colors[1])
        I2_colored = self._expr_to_symbol(I2, colors[2])

        I_rhs_str = f""" 
        {latex(I0_colored)} + {latex(I1_colored)} + {latex(I2_colored)} 
        """
        I_rhs = Symbol(I_rhs_str.strip())
        I_eq = Eq(I, I_rhs)
        
        soln_item = f"""
        $\\displaystyle \\implies {latex(eq2)} $
        
        $\\displaystyle \\implies {latex(I_eq)} 
        \\qquad \\qquad \\cdots (2.1)
        $
        
        """
        
        soln_items.append(soln_item.strip())


        I0_eq = Eq(I0, rhs_t0)
        rhs_t0_expr = a0 * mult1
        rhs_t0_expr = Integral(rhs_t0_expr, (t, -T0/2, T0/2))
        I0_eq_soln = Eq(I0, rhs_t0_expr.doit())
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I0_eq.lhs)} $:
        <div style="color:{colors[0]};">
        
        $\\displaystyle {latex(I0_eq)} $
        
        $\\displaystyle = {latex(rhs_t0_expr)} $
        
        Value of the above expression is zero. 
        
        $\\displaystyle \\implies {latex(I0_eq_soln)} 
        \\qquad \\qquad \\cdots (2.2)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())

        I1_eq = Eq(I1, rhs_t1)
        cos_term_mult1 =  ueval(Indexed('a',n)) * ueval(mult1) * ueval(cos(n_theta))
        expr_intgr = Integral(cos_term_mult1, (t, -T0/2, T0/2))
        rhs_t1_expr = Sum(expr_intgr, (n, 1, oo))
        
        rhs_t1_expr_reduced = ueval(Indexed('a',m)) * ueval(mult1) * ueval(cos(omega0 * m * t))
        rhs_t1_expr_reduced = Integral(rhs_t1_expr_reduced, (t, -T0/2, T0/2))
        
        rhs_t1_expr_reduced_doit = ueval(Indexed('a',m)) * ueval(T0/2)
        
        I1_eq_soln = Eq(I1, rhs_t1_expr_reduced_doit)
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I1_eq.lhs)} $:
        <div style="color:{colors[1]};">
        
        $\\displaystyle {latex(I1_eq)} $
        
        $\\displaystyle = {latex(rhs_t1_expr)} $

        $\\displaystyle = {latex(rhs_t1_expr_reduced)} 
        \\qquad \\text{{ The value of all other terms for }} 
        {latex(m)} \\ne {latex(n)} = 0
        $

        $\\displaystyle = {latex(rhs_t1_expr_reduced_doit)} $
        
        $\\displaystyle \\implies {latex(I1_eq_soln)} 
        \\qquad \\qquad \\cdots (2.3)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())


        I2_eq = Eq(I2, rhs_t2)
        sin_term_mult1 =  ueval(Indexed('b',n)) * ueval(mult1) * ueval(sin(n_theta))
        expr_intgr = Integral(sin_term_mult1, (t, -T0/2, T0/2))
        rhs_t2_expr = Sum(expr_intgr, (n, 1, oo))
        
        rhs_t2_expr_intgr_doit = 0

        I2_eq_soln = Eq(I2, rhs_t2_expr_intgr_doit)
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I2_eq.lhs)} $:
        <div style="color:{colors[2]};">
        
        $\\displaystyle {latex(I2_eq)} $
        
        $\\displaystyle = {latex(rhs_t2_expr)} $
        
        Each term in the above expression is zero. 

        $\\displaystyle \\implies {latex(I2_eq_soln)} 
        \\qquad \\qquad \\cdots (2.4)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())

        I_eq_soln_rhs = f"""
        {latex(I0_eq_soln.rhs)} + {latex(I1_eq_soln.rhs)} + {latex(I2_eq_soln.rhs)}
        """
        I_eq_soln_rhs = Symbol(I_eq_soln_rhs.strip())
        
        I_eq_soln_ueval = Eq(eq2.lhs, I_eq_soln_rhs)
        
        I_eq_soln = Eq(eq2.lhs, I0_eq_soln.rhs + I1_eq_soln.rhs + I2_eq_soln.rhs)
        
        a_m_eq = Eq(ueval(Indexed('a',m)), ueval(2/T0) * eq2.lhs)
        
        mult2 = cos(ueval(omega0_expr) * ueval(n * t))
        mult2_symb = Symbol(latex(ueval(f) * mult2))
        a_n_eq_rhs = ueval(2/T0) * ueval(Integral(mult2_symb, (t, -T0/2, T0/2)))

        a_n_eq = Eq(ueval(Indexed('a',n)), a_n_eq_rhs)
        
        soln_item = f"""
        Hence, from (2.1), (2.2), (2.3) and (2.4):
        
        $\\displaystyle {latex(I_eq)} $
        
        $\\displaystyle \\implies {latex(I_eq_soln_ueval)} $
        
        $\\displaystyle \\implies {latex(I_eq_soln)} $
        
        $\\displaystyle \\implies {latex(a_m_eq)} $
        
        Using variable ${latex(n)}$ for ${latex(m)}$, it can be written as:
        
        $\\displaystyle \\implies {latex(a_n_eq)}  
        \\qquad \\qquad \\cdots (2.5)
        $
        
        """
        
        soln_items.append(soln_item.strip())
        
        return soln_items, a_n_eq
        pass
    
    def _expr_for_coeff_b_n(self, f, m, n, t, a0, T0):
        soln_items = []

        omega0_expr = 2 * pi / T0
        omega0 = Symbol(latex(omega0_expr))

        n_theta = omega0  * n * t
        m_theta = ueval(omega0_expr)  * ueval(m * t)

        cos_expr = ueval(Indexed('a',n))  * ueval(cos(n_theta))
        sin_expr = ueval(Indexed('b',n))  * ueval(sin(n_theta))

        mult1 = sin(m_theta)
        mult1_symb = self._expr_to_symbol(mult1, 'brown')
        
        term_lhs = ueval(f)
        

        term0 = a0       
        cos_term = Sum(cos_expr, (n, 1, oo))
        sin_term = Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term

        eq0 = Eq(term_lhs, term_rhs)
        
        soln_item = f"""
        $\\displaystyle {latex(eq0)} $
        """
        soln_items.append(soln_item.strip())

        term0 = ueval(mult1_symb) * ueval(a0)      
        cos_term = ueval(mult1_symb) * Sum(cos_expr, (n, 1, oo))
        sin_term = ueval(mult1_symb) * Sum(sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term
        term_rhs_symb = Symbol(f"""
        {latex(term0)} + {latex(cos_term)} + {latex(sin_term)}
        """)

        eq1_1 = Eq(term_lhs, term_rhs_symb)
        
        soln_item = f"""
        Multiply both sides with $\\displaystyle {latex(mult1_symb)} $:
        
        $\\displaystyle \\implies {latex(eq1_1)} $
        """
        
        soln_items.append(soln_item.strip())

        # remove color        
        mult1_symb = self._expr_to_symbol(mult1)
        
        term_lhs = mult1_symb * ueval(f)
        
        term0 = ueval(mult1_symb) * ueval(a0)      
        cos_term = Sum(ueval(mult1_symb) * cos_expr, (n, 1, oo))
        sin_term = Sum(ueval(mult1_symb) * sin_expr, (n, 1, oo))
        term_rhs = term0 + cos_term + sin_term
        term_rhs_symb = Symbol(f"""
        {latex(term0)} + {latex(cos_term)} + {latex(sin_term)}
        """)

        eq1_2 = Eq(term_lhs, term_rhs_symb)
        
        soln_item = f"""
        $\\displaystyle \\implies {latex(eq1_2)} $
        """
        
        soln_items.append(soln_item.strip())
        
        lhs = Integral(term_lhs, (t, -T0/2, T0/2))
        
        rhs_t0 = Integral(term0, (t, -T0/2, T0/2))
        rhs_t1 = Integral(cos_term, (t, -T0/2, T0/2))
        rhs_t2 = Integral(sin_term, (t, -T0/2, T0/2))
        
        colors = ['red', 'green', 'blue']
        rhs_t0_colored = self._expr_to_symbol(rhs_t0, colors[0])
        rhs_t1_colored = self._expr_to_symbol(rhs_t1, colors[1])
        rhs_t2_colored = self._expr_to_symbol(rhs_t2, colors[2])
        
        rhs_str = f""" 
        {latex(rhs_t0_colored)} + {latex(rhs_t1_colored)} + {latex(rhs_t2_colored)} 
        """
        rhs = Symbol(rhs_str.strip())
        eq2 = Eq(lhs, rhs)
        
        I, I3, I4, I5 = symbols('I I_6 I_7 I_8')
        I3_colored = self._expr_to_symbol(I3, colors[0])
        I4_colored = self._expr_to_symbol(I4, colors[1])
        I5_colored = self._expr_to_symbol(I5, colors[2])

        I_rhs_str = f""" 
        {latex(I3_colored)} + {latex(I4_colored)} + {latex(I5_colored)} 
        """
        I_rhs = Symbol(I_rhs_str.strip())
        I_eq = Eq(I, I_rhs)
        
        soln_item = f"""
        $\\displaystyle \\implies {latex(eq2)} $
        
        $\\displaystyle \\implies {latex(I_eq)} 
        \\qquad \\qquad \\cdots (3.1)
        $
        
        """
        
        soln_items.append(soln_item.strip())


        I3_eq = Eq(I3, rhs_t0)
        rhs_t0_expr = a0 * mult1
        rhs_t0_expr = Integral(rhs_t0_expr, (t, -T0/2, T0/2))
        I3_eq_soln = Eq(I3, rhs_t0_expr.doit())
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I3_eq.lhs)} $:
        <div style="color:{colors[0]};">
        
        $\\displaystyle {latex(I3_eq)} $
        
        $\\displaystyle = {latex(rhs_t0_expr)} $
        
        Value of the above expression is zero. 
        
        $\\displaystyle \\implies {latex(I3_eq_soln)} 
        \\qquad \\qquad \\cdots (3.2)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())


        I4_eq = Eq(I4, rhs_t2)
        cos_term_mult1 =  ueval(Indexed('a',n)) * ueval(mult1) * ueval(cos(n_theta))
        expr_intgr = Integral(cos_term_mult1, (t, -T0/2, T0/2))
        rhs_t2_expr = Sum(expr_intgr, (n, 1, oo))
        
        rhs_t2_expr_intgr_doit = 0

        I4_eq_soln = Eq(I4, rhs_t2_expr_intgr_doit)
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I4_eq.lhs)} $:
        <div style="color:{colors[1]};">
        
        $\\displaystyle {latex(I4_eq)} $
        
        $\\displaystyle = {latex(rhs_t2_expr)} $

        Each term in the above expression is zero. 
        
        $\\displaystyle \\implies {latex(I4_eq_soln)} 
        \\qquad \\qquad \\cdots (3.3)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())



        I5_eq = Eq(I5, rhs_t1)
        sin_term_mult1 =  ueval(Indexed('b',n)) * ueval(mult1) * ueval(sin(n_theta))
        expr_intgr = Integral(sin_term_mult1, (t, -T0/2, T0/2))
        rhs_t1_expr = Sum(expr_intgr, (n, 1, oo))
        
        rhs_t1_expr_reduced = ueval(Indexed('b',m)) * ueval(mult1) * ueval(sin(omega0 * m * t))
        rhs_t1_expr_reduced = Integral(rhs_t1_expr_reduced, (t, -T0/2, T0/2))
        
        rhs_t1_expr_reduced_doit = ueval(Indexed('b',m)) * ueval(T0/2)
        
        I5_eq_soln = Eq(I5, rhs_t1_expr_reduced_doit)
        
        soln_item = f"""
        Solving for $\\displaystyle {latex(I5_eq.lhs)} $:
        <div style="color:{colors[2]};">
        
        $\\displaystyle {latex(I5_eq)} $
        
        $\\displaystyle = {latex(rhs_t1_expr)} $

        $\\displaystyle = {latex(rhs_t1_expr_reduced)} 
        \\qquad \\text{{ The value of all other terms for }} 
        {latex(m)} \\ne {latex(n)} = 0
        $

        $\\displaystyle = {latex(rhs_t1_expr_reduced_doit)} $
        
        $\\displaystyle \\implies {latex(I5_eq_soln)} 
        \\qquad \\qquad \\cdots (3.4)
        $
        </div>
        """
        
        soln_items.append(soln_item.strip())


        I_eq_soln_rhs = f"""
        {latex(I3_eq_soln.rhs)} + {latex(I4_eq_soln.rhs)} + {latex(I5_eq_soln.rhs)}
        """
        I_eq_soln_rhs = Symbol(I_eq_soln_rhs.strip())
        
        I_eq_soln_ueval = Eq(eq2.lhs, I_eq_soln_rhs)
        
        I_eq_soln = Eq(eq2.lhs, I3_eq_soln.rhs + I4_eq_soln.rhs + I5_eq_soln.rhs)
        
        b_m_eq = Eq(ueval(Indexed('b',m)), ueval(2/T0) * eq2.lhs)
        
        mult2 = sin(ueval(omega0_expr) * ueval(n * t))
        mult2_symb = Symbol(latex(ueval(f) * mult2))
        b_n_eq_rhs = ueval(2/T0) * ueval(Integral(mult2_symb, (t, -T0/2, T0/2)))

        b_n_eq = Eq(ueval(Indexed('b',n)), b_n_eq_rhs)
        
        soln_item = f"""
        Hence, from (3.1), (3.2), (3.3) and (3.4):
        
        $\\displaystyle {latex(I_eq)} $
        
        $\\displaystyle \\implies {latex(I_eq_soln_ueval)} $
        
        $\\displaystyle \\implies {latex(I_eq_soln)} $
        
        $\\displaystyle \\implies {latex(b_m_eq)} $
        
        Using variable ${latex(n)}$ for ${latex(m)}$, it can be written as:
        
        $\\displaystyle \\implies {latex(b_n_eq)}  
        \\qquad \\qquad \\cdots (3.5)
        $
        
        """
        
        soln_items.append(soln_item.strip())


        
        return soln_items, b_n_eq
        pass
    
    def _expr_to_symbol(self, expr, color = None, evaluate = False):
        symb = Symbol(latex(expr))
        if color is None:
            if evaluate:
                return symb
            return ueval(symb)
        
        colored_symb = Symbol(latex_color(latex(symb), color))
        if evaluate:
            return colored_symb
        return ueval(colored_symb)
                

    def get_wave(self, A, f, theta, phi):
        x = theta
        y = A*np.sin(2*np.pi*f*theta - phi)
        return x, y

    def get_circle(self, A, f, theta, phi):
        x = A*np.cos(2*np.pi*f*theta - phi)
        y = A*np.sin(2*np.pi*f*theta - phi)
        return x, y

    def plot_wave(self, data, time):
        A = data[0]
        f = data[1]
        phi = data[2]
        x, y = self.get_wave(A, f, time, phi)

        ax = plt.gca()
        plt.plot(x, y, color = 'blue')

        ax.set_xlabel('Time', fontsize = 20, color = 'red')
        ax.tick_params(axis='x', colors='red')
        ax.tick_params(axis='y', colors='red')

    def plot_multiple_waves(self, data, time):
        no_of_plots = len(data)
        fig, axes = plt.subplots(no_of_plots, figsize = (16, 9))
        for i in range(no_of_plots):
            ax = axes[i]
            w_data = data[i] 
            A = w_data[0]
            f = w_data[1]
            phi = w_data[2]
            x, y = self.get_wave(A, f, time, phi)
            ax.plot(x, y, color = 'blue', alpha = 1)
            ax.set_ylabel(f'Wave {i + 1}', color = 'blue', fontsize = 15)

            fig.suptitle('Time', fontsize = 20, color = 'red')
            ax.tick_params(axis='x', colors='red')
            ax.tick_params(axis='y', colors='red')
            ax.set_ylim(-15, 15)

    def plot_a_fourier_waves(self, data, time):
        no_of_plots = len(data)
        ys = np.zeros(len(time))
        for i in range(no_of_plots):
    #         ax = axes[i]
            w_data = data[i] 
            A = w_data[0]
            f = w_data[1]
            phi = w_data[2]
            x, y = self.get_wave(A, f, time, phi)
            ys += y
            
        fig = plt.figure()
        ax = plt.gca()
        x = time
        plt.plot(x, y, color = 'blue', alpha = 1)
        ax.set_ylabel(f'Wave {i + 1}', color = 'blue', fontsize = 15)

        fig.suptitle('Time', fontsize = 20, color = 'red')
        ax.tick_params(axis='x', colors='red')
        ax.tick_params(axis='y', colors='red')
        ax.set_ylim(-15, 15)

    def plot_fourier_waves(self, data, time, alpha = 1):
        no_of_plots = len(data) + 2
        fig, axes = plt.subplots(no_of_plots, figsize = (16, 9))
        xs = np.zeros(len(time))
        ys = np.zeros(len(time))
        for i in range(no_of_plots):
            ax = axes[i]
            if i < no_of_plots - 2:
                w_data = data[i] 
                A = w_data[0]
                f = w_data[1]
                phi = w_data[2]
                x, y = self.get_wave(A, f, time, phi)
                ax.plot(x, y, color = 'blue', alpha = alpha)
                xs += x
                ys += y
                ax.set_ylabel(f'Wave {i + 1}', color = 'blue', fontsize = 15)

            elif i == no_of_plots - 2:
                for j in range(no_of_plots-2):
                    w1_data = data[j]
                    A = w1_data[0]
                    f = w1_data[1]
                    phi = w1_data[2]
                    x1, y1 = self.get_wave(A, f, time, phi)
                    ax.plot(x1, y1, color = 'green', alpha = alpha)
                    ax.set_ylabel(f'All waves', color = 'green', fontsize = 15)

            else:
                ax.plot(time, ys, color = 'red', alpha = alpha)
                ax.set_ylabel(f'Fourier Wave', color = 'red', fontsize = 15)
                
                
            ax.set_xlabel('Time', fontsize = 20, color = 'red')
            ax.tick_params(axis='x', colors='red')
            ax.tick_params(axis='y', colors='red')
            ax.set_ylim(-15, 15)

    def plot_circular_wave(self, data, time):
        A = data[0]
        f = data[1]
        phi = data[2]
        x, y = self.get_circle(A, f, time, phi)
        
        plt.figure(figsize = (13, 6))
        ax = plt.gca()
        plt.plot(x, y, color = 'blue')

        ax.set_xlabel('Time', fontsize = 20, color = 'red')
        ax.tick_params(axis='x', colors='red')
        ax.tick_params(axis='y', colors='red')
        ax.set_aspect('equal')    
        
    def plot_multiple_circular_waves(self, data, time):
        no_of_plots = len(data)
        fig, axes = plt.subplots(ncols = no_of_plots, figsize = (16, 9))
        for i in range(no_of_plots):
            ax = axes[i]
            w_data = data[i] 
            A = w_data[0]
            f = w_data[1]
            phi = w_data[2]
            x, y = self.get_circle(A, f, time, phi)
            ax.plot(x, y, color = 'blue')           
                
            ax.set_xlim(-12, 12)
            ax.set_ylim(-12, 12)
            ax.set_aspect('equal')

            ax.set_title(f'Wave {i + 1}', fontsize = 20, color = 'red')
            ax.tick_params(axis='x', colors='red')
            ax.tick_params(axis='y', colors='red')

    def plot_circular_a_fourier_wave(self, data, time, alpha = 1):
        x = 0
        y = 0
        for A, f, phi in data:
            x_temp, y_temp = self.get_circle(A, f, time, phi)
            x += x_temp
            y += y_temp
        plt.plot(x, y, color = 'red', alpha = alpha)
        plt.gca().set_title(f'Fourier Wave', color = 'blue', fontsize = 20)
        plt.gca().set_aspect('equal')

    def plot_circular_fourier_wave(self, data, time, alpha = 1):
        no_of_plots = len(data) + 2
        fig, axes = plt.subplots(ncols = no_of_plots, figsize = (16, 9))
        xs = np.zeros(len(time))
        ys = np.zeros(len(time))
        for i in range(no_of_plots):
            ax = axes[i]
            if i < no_of_plots - 2:
                w_data = data[i] 
                A = w_data[0]
                f = w_data[1]
                phi = w_data[2]
                x, y = self.get_circle(A, f, time, phi)
                ax.plot(x, y, color = 'blue', alpha = alpha)
                xs += x
                ys += y
                ax.set_title(f'Wave {i + 1}', color = 'blue', fontsize = 20)
            elif i == no_of_plots - 2:
                for j in range(no_of_plots-2):
                    w1_data = data[j]
                    A = w1_data[0]
                    f = w1_data[1]
                    phi = w1_data[2]
                    x1, y1 = self.get_circle(A, f, time, phi)
                    ax.plot(x1, y1, color = 'green', alpha = alpha)
                ax.set_title(f'All waves', color = 'green', fontsize = 20)

            else:
                ax.plot(xs, ys, color = 'red', alpha = alpha)
                ax.set_title(f'Fourier wave', color = 'red', fontsize = 20)
                

            ax.set_xlim(-15, 15)
            ax.set_ylim(-15, 15)
            ax.tick_params(axis='x', colors='red')
            ax.tick_params(axis='y', colors='red')
            ax.set_aspect('equal')
            
        pass
    
    pass
#class FourierHelper:
