'''
Created on 20-Jan-2022

@author: bthakur
'''

import inspect
import traceback
import random
import numpy as np
import sympy as sp
from sympy import (latex, Symbol, symbols,
                    Rational,
                    sin, cos, tan,
                    asin, acos, atan,
                    sinh, cosh, tanh,
                    log, exp, sqrt,
                   diff, factor, 
                   Function, Eq,
                   Add, 
                   simplify, solve, factorial,
                   Sum, binomial
                   )

from sympy import (I, symbols, Symbol, Function, sqrt,
                   latex, Eq, Matrix, latex, sympify)

from sympy.core.numbers import One, Integer
from sympy.series import series

from xv.km.util import latex_fraction
from sympy.core.expr import UnevaluatedExpr

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

import matplotlib.pyplot as plt


from xv.km import ComplexNumber

from xv.util import UtilClass

class ComplexNumberHelper(UtilClass):
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        pass
    
    def get_random_negative_number(self):
        return -1 * self._get_random_positive_number()
        pass

    def get_standard_complex_number(self):
        x = Symbol('x', real = True)
        y = Symbol('y', real = True)
        z = ComplexNumber(x, y)
        
        return z 
        pass

    def get_random_complex_number(self):
        x = 0
        y = 0
        while x == 0 or y == 0:
            #x = self._get_random_positive_number() * random.choice([1, -1])
            #y = sqrt(self._get_random_positive_number()) * random.choice([1, -1])
            x = random.randint(-9, 9)
            y = random.randint(-9, 9)
            
        return ComplexNumber(x, y)
        pass

    def _get_random_positive_number(self):
        n = random.choice([
            random.randint(2, 9) ** 2,
            random.randint(2, 9) ** 2 * 10 ** (- random.randint(1, 4) * 2),
            Rational(random.randint(1, 9) ** 2, random.randint(2, 9) ** 2),
            ])
        return n
        pass


    def get_complex_number_properties(self, z = None):
        if z is None:
            z = self.get_random_complex_number()
        z_std = self.get_standard_complex_number()
        
        z_props = {}
        z_props['Conjugate'] = z.get_conjugate_desc()
        z_props['Real part'] = z.get_real_part_desc()
        z_props['Imaginary part'] = z.get_imaginary_part_desc()
        z_props['Modulus'] = z.get_modulus_desc()
        z_props['Square of Modulus'] = z.get_square_of_modulus_desc()
        z_props['Argument'] = z.get_argument_desc()
        z_props['Euler form'] = z.get_euler_form_desc()
        z_props['Matrix form'] = z.get_matrix_form_desc()

        z_props_keys_str = [f"{k_idx + 1}. {k}" for k_idx, k in enumerate(z_props.keys())]
        z_props_keys_str = "\n".join(z_props_keys_str)

        z_props_str = [f"<b>{k_idx + 1}. {k}</b>:\n{v}" for k_idx, (k,v) in enumerate(z_props.items())]
        z_props_str = "\n\n".join(z_props_str)

        result = {}
        result['z'] = z
        result['z_std'] = z_std
        result['z_props'] = z_props
        result['z_props_str'] = z_props_str
        result['z_props_str'] = z_props_str
        result['z_props_keys_str'] = z_props_keys_str
        return result
        pass


    def get_complex_number_operations(self, z1, z2, n):
        z_props = []
 
        #add       
        prob_s = f"""$\\displaystyle 
        \\left( {z1} \\right) 
        +  \\left( {z2} \\right)$
        """
        soln_s = z1.get_add_desc(z2)
        z_props.append([prob_s.strip(), soln_s.strip()])
        
        #sub       
        prob_s = f"""$\\displaystyle 
        \\left( {z1} \\right) 
        -  \\left( {z2} \\right)$
        """
        soln_s = z1.get_sub_desc(z2)
        z_props.append([prob_s.strip(), soln_s.strip()])

        #mul       
        prob_s = f"""$\\displaystyle 
        \\left( {z1} \\right) 
        * \\left( {z2} \\right)$
        """
        soln_s = z1.get_mul_desc(z2)
        z_props.append([prob_s.strip(), soln_s.strip()])

        #truediv       
        frac1 = latex_fraction(z1, z2)
        prob_s = f"""$\\displaystyle {frac1}$"""
        soln_s = z1.get_truediv_desc(z2)        
        z_props.append([prob_s.strip(), soln_s.strip()])


        #pow   
        pow1 = f""" {{ \\left( {z1} \\right) }}^{{{n}}} """
        prob_s = f"""$\\displaystyle {pow1}$"""
        soln_s = z1.get_pow_desc(n)        
        z_props.append([prob_s.strip(), soln_s.strip()])

        z_props_keys_str = [f"<b>{k_idx + 1}.</b> {k}" 
                            for k_idx, (k,v) in enumerate(z_props)]
        z_props_keys_str = "\n\n".join(z_props_keys_str)

        z_props_str = [f"<b>{k_idx + 1}. </b>{v}" 
                       for k_idx, (k,v) in enumerate(z_props)]
        z_props_str = "\n\n".join(z_props_str)

        result = {}
        result['z1'] = z1
        result['z2'] = z2
        result['z_props'] = z_props
        result['z_props_str'] = z_props_str
        result['z_props_str'] = z_props_str
        result['z_props_keys_str'] = z_props_keys_str
        return result
        pass

    def get_nth_root(self, z, n):
        inv_n  = Rational(1, n)
        #pow   
        pow1 = f""" {{ \\left( {z} \\right) }}^{{{latex(inv_n)}}} """
        prob_s = f"""$\\displaystyle {pow1}$"""
        soln_s = z.get_pow_desc(inv_n)        
        
        answer = z ** inv_n
        
        result = {}
        result['z'] = z
        result['n'] = n
        result['prob_s'] = prob_s
        result['soln_s'] = soln_s
        result['answer'] = answer
        return result
        pass
    
    def get_imaginary_root_solution(self, n):
        
        try:
            if not n < 0:
                return None
        except:
            return None
            pass
        
        soln_s = f"""
        $\\displaystyle \\sqrt{{{latex(n)}}}$
        
        $\\displaystyle = \\sqrt{{{latex(-n)} * {-1}}} $
        
        $\\displaystyle = \\sqrt{{{latex(-n)}}} * \\sqrt{{{-1}}} $
        
        $\\displaystyle = {latex(sqrt(n))}$
        
        Note: $\\displaystyle {latex(sqrt(-1))}$ is symbol of imaginary number. By definition,
        
        $\\displaystyle \\sqrt{{-1}} = {latex(sqrt(-1))}$

        $\\displaystyle \\implies {latex(sqrt(-1))}^2 = -1 $
        """
        
        return soln_s
        pass
    
    
    def get_problem_challenging_complex_number_quesions(self, **kwargs):
        '''
        '''
        sInfo = f"""Processing {self.__class__.__name__}.{inspect.stack()[0][3]}: """
        self._log(sInfo.strip())

        index = kwargs.get('index', None)
        
        try:
            
            if index is None:
                random_choices = list(range(1, 9))
        
                identifier = f"{self.__class__.__name__}{id(self)}.{inspect.stack()[0][3]}.random_choices"
                random_choice = self.get_no_repeat_random_choice(identifier, random_choices)
            else:
                random_choice = index
            
            if random_choice == 1:
                return self._evaluate_complex_expr()
            
            
            return self._evaluate_complex_expr()
        
        except Exception as e:
            sError = f"""Error {self.__class__.__name__}.{inspect.stack()[0][3]}: {e}
            """
            self._log(sError)
            self._log(traceback.format_exc())
            pass
        
        return None
        pass

    def _evaluate_complex_expr(self, **kwargs):
        
        theta = Symbol('theta')
        S = Symbol('S')
        n = Symbol('n')
        x, y = symbols('x, y', real = True)
        z = ComplexNumber(x, y)
        z_n = z ** n
        z_mod = 1
        expr = z_n / (1 - z_n)
        
        expr_symb = Symbol(f""" \\frac{{{latex(z_n)}}}{{1-{latex(z_n)}}} """)
        sum_expr_symb = Symbol(f""" 
        \\sum_{{ {latex(n)} = 1 }}^{{\\infty}} 
        \\operatorname{{Re}}
        \\left( {latex(expr_symb)} \\right) 
        """)
        
         
        prob_s = f"""
        If ${latex(z)}$ is a complex number such that 
        $\\left| {latex(z)} \\right| = {latex(z_mod)} $ 
        and ${latex(z)} \\neq {latex(z_mod)} $, 
        then find the value of 
        $\\displaystyle
        {latex(S)} = {latex(sum_expr_symb)}
        $
        """.replace("\n", " ").strip()
        
        soln_stmts = []

        soln_stmt = f"""
        $\\displaystyle
        \\begin{{aligned}}
        
        \\left| {latex(z)} \\right| 
        = {latex(z_mod)} 
        \\\\ \\\\
        
        \\implies
        {latex(z)} = 
        
        
        \\end{{aligned}}
        $ 
        
        Given $|z|=1$, we can write $z=e^{{i \\theta}}$, where $\\theta$ is a real number.
        """
        soln_stmts.append(soln_stmt.strip())
        
        
        
        soln_stmt = f"""
        $\\displaystyle
        $

        
        
        Solution:
        
        Let's first simplify the expression inside the summation:
        
        $
        \\frac{{z^n}}{{1-z^n}}=\\frac{{e^{{i n \\theta}}}}{{1-e^{{i n \\theta}}}}
        $
        
        
        We want the real part of this expression:
        
        $
        \\operatorname{{Re}}\\left(\\frac{{e^{{i n \\theta}}}}{{1-e^{{i n \\theta}}}}\\right)
        $
        
        To simplify, multiply numerator and denominator by the conjugate of the denominator:
        
        $
        \\frac{{e^{{i n \\theta}}\\left(1-e^{{-i n \\theta}}\\right)}}{{\\left(1-e^{{i n \\theta}}\\right)\\left(1-e^{{-i n \\theta}}\\right)}}=\\frac{{e^{{i n \\theta}}-1}}{{2-\\left(e^{{i n \\theta}}+e^{{-i n \\theta}}\\right)}}
        $
        
        
        Since $e^{{i n \\theta}}+e^{{-i n \\theta}}=2 \\cos (n \\theta)$, this becomes:
        
        $
        \\frac{{e^{{i n \\theta}}-1}}{{2-2 \\cos (n \\theta)}}=\\frac{{e^{{i n \\theta}}-1}}{{4 \\sin ^2\\left(\\frac{{n \\theta}}{{2}}\\right)}}
        $
        
        
        Now, separate the real and imaginary parts. The real part of $e^{{i n \\theta}}-1$ is:
        
        $
        \\operatorname{{Re}}\\left(e^{{i n \\theta}}-1\\right)=\\cos (n \\theta)-1
        $
        
        
        Thus, the real part of our expression is:
        
        $
        \\frac{{\\cos (n \\theta)-1}}{{4 \\sin ^2\\left(\\frac{{n \\theta}}{{2}}\\right)}}=-\\frac{{\\sin ^2\\left(\\frac{{n \\theta}}{{2}}\\right)}}{{4 \\sin ^2\\left(\\frac{{n \\theta}}{{2}}\\right)}}=-\\frac{{1}}{{4}}
        $
        
        So, the sum $S$ becomes:
        
        $
        S=\\sum_{{n=1}}^{{\\infty}}-\\frac{{1}}{{4}}=-\\frac{{1}}{{4}} \\times \\infty
        $
        
        
        However, this infinite series does not seem to converge directly. We must recognize that:
        1. The series inside the real part converges differently when interpreted over the unit circle.
        2. The result for $|z|=1$ takes into consideration that the infinite periodic functions sum to 0 for non-degenerate points.
        
        So correctly, considering convergence and understanding:
        
        $
        \\sum_{{n=1}}^{{\\infty}} \\operatorname{{Re}}\\left(\\frac{{z^n}}{{1-z^n}}\\right)=\\frac{{1}}{{4}}
        $
        
        
        
        """
        soln_stmts.append(soln_stmt.replace('\n', ' ').strip())
        
        soln_stmt = f"""
 
        """
        soln_stmts.append(soln_stmt.strip())

        soln_stmt = f"""
 
        """
        soln_stmts.append(soln_stmt.strip())

        soln_stmt = f"""
 
        """
        soln_stmts.append(soln_stmt.strip())

        answer_s = f"""
        $\\displaystyle
        $
        """
        
        soln_s = "\n\n".join(soln_stmts)
        
                
        result = {
            'prob_s': prob_s.strip(),
            'soln_s': soln_s.strip(),
            'answer_s': answer_s.strip(),
            }
        
        return result
        pass
    


    pass
#class ComplexNumberHelper:
