'''
Created on 12-Jul-2021

@author: bthakur
'''
import random
import inspect
import traceback
import re
from sympy import (Eq, symbols, Symbol, sqrt, latex, factor,
                   Rational, solve, log, simplify, E, exp,  
                   sin, cos, I, series, pi)
from sympy.abc import theta
import sympy

from xv.km import CustomLogarithm as Log
from xv.km.util import latex_fraction, latex_exponent

class GeometricSeries:
    def __init__(self, a, r, n, **kwargs):
        self.verbose = kwargs.get('verbose', False)
        self.a = a
        self.r = r
        self.n = n

        self.alpha = symbols(r'\alpha') # 1+r
        self.beta = symbols(r'\beta') # 1/(1+r)
        pass

    def get_alpha_value(self, r = None, to_latex = False):
        if r is None:
            r = self.r

        if not to_latex:
            return 1 + r
        
        r_latex = self.get_var_latex(r)
        return f"1 + {r_latex}"
    
    def get_beta_value(self, r = None, to_latex = False):
        if r is None:
            r = self.r
        
        if not to_latex:
            return 1 / (1 + r)
        
        r_latex = self.get_var_latex(r)
        return latex_fraction(1, f"1 + {r_latex}")
    
    def get_term(self, index, to_latex = False):
        if not to_latex:
            return self.a * self.r ** index
        
        r_latex = self.get_var_latex(self.r)
        a_latex = self.get_var_latex(self.a)
        
        return f"{a_latex}*{r_latex}^{{{latex(index)}}}"
        pass
    
    def get_future_value(self, index, to_latex = False):
        '''
        This returns the future value at the beginning of index time period,
        at the end of index + 1 time periods
        '''
        if not to_latex:
            return self.a * (1 + self.r) ** index
        
        r_latex = self.get_var_latex(self.r)
        a_latex = self.get_var_latex(self.a)

        return f"{a_latex}* \\left(1 + {r_latex}\\right)^{{{latex(index)}}}"
        pass
    
    def get_present_value(self, fv, index, to_latex = False):
        '''
        This returns the present value of a given future value of at
        the beginning of index
        '''
        if not to_latex:
            return fv / ((1 + self.r) ** index)

        r_latex = self.get_var_latex(self.r)
        
        p = f"{latex(fv)}"
        q = f"\\left(1 + {r_latex}\\right)^{{{latex(index)}}}"
        
        return latex_fraction(p, q)
        pass
    
    def get_future_annuity_value(self, 
                                 equal_installment = None, 
                                 r = None, 
                                 n = None,
                                 as_alpha = False,
                                 to_latex = False):
        '''
        future value at the end of n time periods, of equal installment deposited 
        at the beginning of every time period compounded @ rate of interest r per time period.
        '''
        if equal_installment is None:
            equal_installment = self.a
            
        if r is None:
            r = self.r
            
        if n is None:
            n = self.n
            
        if as_alpha:
            alpha = self.alpha
        else:
            alpha = self.get_alpha_value(r = r, to_latex = False)
        
        if not to_latex:
            return equal_installment * (alpha) * ( (1 - (alpha) ** n) / (1 - (alpha))  )

        r_latex = self.get_var_latex(self.r)

        if as_alpha:
            alpha_latex = f"{latex(self.alpha)}"
            p = f"1 - {alpha_latex}^{{{latex(n)}}}"
            q = f"1 - {alpha_latex}"
            frc = latex_fraction(p, q)
        else:
            alpha_latex = "\\left(" +  self.get_alpha_value(
                                            r = r, to_latex = True) + "\\right)"
            p = f"{alpha_latex}^{{{latex(n)}}} - 1"
            q = f"{latex(r)}"
            frc = latex_fraction(p, q)
            pass
        
        expr = f"{latex(equal_installment)} * {alpha_latex} * {frc}"
        return expr
        pass

    def get_present_annuity_value(self, 
                                 equal_installment = None, 
                                 r = None, 
                                 n = None,
                                 as_beta = False,
                                 to_latex = False):
        '''
        present value of of n equal installments deposited at the beginning of 
        at the beginning of every time period discounted @ rate of interest r per time period.
        '''
        if equal_installment is None:
            equal_installment = self.a
            
        if r is None:
            r = self.r
            
        if n is None:
            n = self.n
            
        if as_beta:
            beta = self.beta
        else:
            beta = self.get_beta_value(r = r, to_latex = False)
        
        if not to_latex:
            return equal_installment * ( (1 - (beta) ** n) / (1 - (beta))  )

        r_latex = self.get_var_latex(self.r)

        if as_beta:
            beta_latex = f"{latex(self.beta)}"
        else:
            beta_latex = "\\left(" +  self.get_beta_value(r = r, to_latex = True) + "\\right)"
        
        
        p = f"1 - {beta_latex}^{{{latex(n)}}}"
        q = f"1 - {beta_latex}"
        frc = latex_fraction(p, q)
        
        expr = f"{latex(equal_installment)} * {frc}"
        
        return expr
        pass
    
    
    def get_sum(self, n = None, to_latex = False):
        if n is None:
            n = self.n
            
        if not to_latex:
            return self.a * ( (1 - self.r ** n) / (1 - self.r) )

        r_latex = self.get_var_latex(self.r)
        
        frc = latex_fraction(f"1 - {r_latex}^{{{latex(n)}}}",
                             f"1 - {r_latex}")
        return f"{latex(self.a)} * {frc}" 
        pass
    
    def get_terms(self, count = None, to_latex = False):
        if count is None:
            count = self.n
        
        is_second_lot = True
        first_lot_count = 3
        if isinstance(count, int):
            if count < 6:
                is_second_lot = False
                first_lot_count = count
                pass
        
        r_latex = self.get_var_latex(self.r)
        a_latex = self.get_var_latex(self.a)
        #n_latex = self.get_var_latex(self.n)
        
        terms = { i:self.a * self.r ** i  for i in range(first_lot_count)}
        terms_latex = { i:f"{a_latex}*{r_latex}^{{{i}}}"  for i in range(first_lot_count)}
        
        if is_second_lot:
            terms[count-2] = self.a * self.r ** (count-2)
            terms_latex[count-2] = f"{a_latex}*{r_latex}^{{{latex(count-2)}}}" 

            terms[count-1] = self.a * self.r ** (count-1)
            terms_latex[count-1] = f"{a_latex}*{r_latex}^{{{latex(count-1)}}}" 
        
        if to_latex:
            return terms_latex
        
        return terms
        pass
    
    def get_summtion_latex(self, a = None, r = None, n = None):
        if a is None:
            a = self.a
            
        if r is None:
            r = self.r
            
        if n is None:
            n = self.n

        r_latex = self.get_var_latex(r)
        a_latex = self.get_var_latex(a)
            
        s = f"""
        {{\\large\\sum\\limits_{{k=0}}^{{{latex(n-1)}}}}} {{ {a_latex}*{r_latex}^{{k}} }}  
        """
        return s.strip()    
    
    
    def get_series(self, terms, to_str = False):
        include_dots = self._include_dots(terms)
        
        terms = list(terms.values())
        if include_dots:
            terms.insert(-2, r"\cdots")
        
        s = [f"{t}" for t in terms]
        if not to_str:
            return s
        return ' + '.join(s)
        pass
    
    
    def _include_dots(self, terms):
        keys =  list(terms.keys())
        if len(keys) != 5:
            return False
        try:
            if keys[-3] + 1 != keys[-2]:
                return True
            else:
                return False
        except:
            pass
        return True
        pass
    
    def get_var_latex(self, v):
        if len(f"{v}") == 1:
            v_latex = f"{v}"
            return v_latex
        
        v_latex = f"\\left({latex(v)}\\right)"
        if isinstance(v, int):
            if v > 0:
                v_latex = f"{v}"
            pass

        return v_latex  
    
    pass
#class GeometricSeries
