"""SULT - Loads and uses a standard ultimate life table

MIT License. Copyright 2022-2023 Terence Lim
"""
import math
import numpy as np
import pandas as pd
from typing import Dict, Callable
from actuarialmath.lifetable import LifeTable

def faml_survival(x, t, A = 0.00022, B = 0.0000027, c = 1.124):
    """SULT default survival function from SOA's `Excel Workbook for FAM-L Tables`"""
    A, B, c = 0.00022, 0.0000027, 1.124
    return math.exp(-A*t - (B*c**x*(c**t - 1))/math.log(c))

class SULT(LifeTable):
    """Generates and uses a standard ultimate life table

    Args:
      i : interest rate
      radix : initial number of lives
      minage : minimum age
      maxage : maximum age
      S : survival function, default is Makeham with SOA FAM-L parameters
    """
    
    def __init__(self, i: float = 0.05, radix: int = 100000, 
                 S: Callable[[float, float], float] = faml_survival,
                 minage: int = 20, maxage: int = 130, **kwargs):
        """Construct SULT"""
        super().__init__(**kwargs)
        l = {t+minage: radix * S(minage, t) for t in range(1+maxage-minage)}
        self.set_interest(i=i).set_table(l=l, minage=minage, maxage=maxage)

    def __getitem__(self, col: str) -> Dict[int, float]:
        """Returns a column of the sult table

        Args:
          col : name of life table column to return
        """
        funs = {'q': (self.q_x, dict()),
                'p': (self.p_x, dict()),                
                'a': (self.whole_life_annuity, dict()),
                'A': (self.whole_life_insurance, dict())}
        assert col[0].lower() in funs, f"must be one of {list(funs.keys())}"
        f, args = funs[col[0]]
        return {x: f(x, **args) for x in range(self._MINAGE, self._MAXAGE)}

    def frame(self, minage: int = 20, maxage: int = 100):
        """Derive FAM-L exam table columns of SULT as a DataFrame

        Args:
          minage : first age to display row
          maxage : large age to display row
        """
        # specify methods and arguments for computing columns of FAM-L exam table
        funs = {'q_x': (self.q_x, dict()),
                'a_x': (self.whole_life_annuity, dict()),
                'A_x': (self.whole_life_insurance, dict()),
                '2A_x': (self.whole_life_insurance, dict(moment=2)),
                'a_x:10': (self.temporary_annuity, dict(t=10)),
                'A_x:10': (self.endowment_insurance, dict(t=10)),
                'a_x:20': (self.temporary_annuity, dict(t=20)),
                'A_x:20': (self.endowment_insurance, dict(t=20)),
                '5_E_x': (self.E_x, dict(t=5)),
                '10_E_x': (self.E_x, dict(t=10)),
                '20_E_x': (self.E_x, dict(t=20))}
        t = {col: {x: f(x, **args) for x in range(self._MINAGE, self._MAXAGE)}
             for col, (f, args) in funs.items()}
        tab = pd.DataFrame(dict(l_x=self._table['l'])).sort_index()
        tab = tab.join(pd.DataFrame.from_dict(t).set_index(tab.index[:-1]))
        for digits, col in zip([1, 6, 4, 5, 5, 4, 5, 4, 5, 5, 5, 5], tab.columns):
             tab[col] = tab[col].map(f"{{:.{digits}f}}".format)
        return tab.loc[minage:maxage]

if __name__ == "__main__":
    print("SOA Question 6.52:  (D) 50.80")
    sult = SULT()
    a = sult.temporary_annuity(45, t=10)
    other_cost = 10 * sult.deferred_annuity(45, u=10)
    P = sult.gross_premium(a=a, A=0, benefit=0, 
                           initial_premium=1.05, renewal_premium=0.05,
                           initial_policy=100 + other_cost, renewal_policy=20)
    print(a, P)
    print()
    

    print("SOA Question 6.47:  (D) 66400")
    sult = SULT()
    a = sult.temporary_annuity(70, t=10)
    A = sult.deferred_annuity(70, u=10)
    P = sult.gross_premium(a=a, A=A, benefit=100000, initial_premium=0.75,
                           renewal_premium=0.05)
    print(P)
    print()
    

    print("SOA Question 6.43:  (C) 170")
    sult = SULT()
    a = sult.temporary_annuity(30, t=5)
    A = sult.term_insurance(30, t=10)
    other_expenses = 4 * sult.deferred_annuity(30, u=5, t=5)
    P = sult.gross_premium(a=a, A=A, benefit=200000, initial_premium=0.35,
                           initial_policy=8 + other_expenses, renewal_policy=4,
                           renewal_premium=0.15)
    print(P)
    print()

    print("SOA Question 6.39:  (A) 29")
    sult = SULT()
    P40 = sult.premium_equivalence(sult.whole_life_insurance(40), b=1000)
    P80 = sult.premium_equivalence(sult.whole_life_insurance(80), b=1000)
    p40 = sult.p_x(40, t=10)
    p80 = sult.p_x(80, t=10)
    P = (P40 * p40 + P80 * p80) / (p80 + p40)
    print(P)
    print()
    

    print("SOA Question 6.37:  (D) 820")
    sult = SULT()
    benefits = sult.whole_life_insurance(35, b=50000 + 100)
    expenses = sult.immediate_annuity(35, b=100)
    a = sult.temporary_annuity(35, t=10)
    print(benefits, expenses, a)
    print((benefits + expenses) / a)
    print()
    

    print("SOA Question 6.35:  (D) 530")
    sult = SULT()
    A = sult.whole_life_insurance(35, b=100000)
    a = sult.whole_life_annuity(35)
    print(sult.gross_premium(a=a, A=A, initial_premium=.19, renewal_premium=.04))
    print()
    

    print("SOA Question 5.8: (C) 0.92118")
    sult = SULT()
    a = sult.certain_life_annuity(55, u=5)
    print(sult.p_x(55, t=math.floor(a)))
    print()
    

    print("SOA Question 5.3:  (C) 6.239")
    sult = SULT()
    t = 10.5
    print(t * sult.E_r(40, t=t))
    print()
    

    print("SOA Question 4.17:  (A) 1126.7")
    sult = SULT()
    median = sult.Z_t(48, prob=0.5, discrete=False)
    benefit = lambda x,t: 5000 if t < median else 10000
    print(sult.A_x(48, benefit=benefit))
    print()
    

    print("SOA Question 4.14:  (E) 390000    ")
    sult = SULT()
    p = sult.p_x(60, t=85-60)
    mean = sult.bernoulli(p)
    var = sult.bernoulli(p, variance=True)
    F = sult.portfolio_percentile(mean=mean, variance=var, prob=.86, N=400)
    print(F * 5000 * sult.interest.v_t(85-60))
    print()
    
    from actuarialmath.interest import Interest
    print("SOA Question 4.5:  (C) 35200")
    sult = SULT(udd=True).set_interest(delta=0.05)
    Z = 100000 * sult.Z_from_prob(45, prob=0.95, discrete=False)
    print(Z)

    print("SOA Question 3.9:  (E) 3850")
    sult = SULT()
    p1 = sult.p_x(20, t=25)
    p2 = sult.p_x(45, t=25)
    mean = sult.bernoulli(p1) * 2000 + sult.bernoulli(p2) * 2000
    var = (sult.bernoulli(p1, variance=True) * 2000 
           + sult.bernoulli(p2, variance=True) * 2000)
    print(sult.portfolio_percentile(mean=mean, variance=var, prob=.99))
    print()
    

    print("SOA Question 3.8:  (B) 1505")
    sult = SULT()
    p1 = sult.p_x(35, t=40)
    p2 = sult.p_x(45, t=40)
    mean = sult.bernoulli(p1) * 1000 + sult.bernoulli(p2) * 1000
    var = (sult.bernoulli(p1, variance=True) * 1000 
           + sult.bernoulli(p2, variance=True) * 1000)
    print(sult.portfolio_percentile(mean=mean, variance=var, prob=.95))
    print()
    

    print("SOA Question 3.4:  (B) 815")
    sult = SULT()
    mean = sult.p_x(25, t=95-25)
    var = sult.bernoulli(mean, variance=True)
    print(sult.portfolio_percentile(N=4000, mean=mean, variance=var, prob=.1))
    print()
    
    print("Other usage examples")
    print(sult.temporary_annuity(80, t=10)*20000) # ~130770

    E = sult.E_x(60, t=5)
    print(E, E*sult.a_x(65, benefit=(lambda x,t: 1000 + .05*t)))

    print(sult.whole_life_annuity(60))  # 14.9041
    print(sult.whole_life_annuity(60, discrete=False))  # ~13.9041
    print(sult.deferred_annuity(60, u=10))  #6.9485
    print(sult.temporary_annuity(60, t=10))  # 7.9555
    print(sult.temporary_annuity(60, t=15))  # 10.5282
    print(sult.temporary_annuity(60, t=10))
    print(sult.E_x(60, t=10))  #
    print(sult.temporary_annuity(60, t=10, discrete=False))  # ~7.5341
    print(sult.certain_life_annuity(60, u=10))  # 15.0563
    print(sult.whole_life_annuity(60, variance=True, discrete=False)) # ~10.6182
    print(sult.endowment_insurance(60, t=10)) # .62116
    print(sult.whole_life_insurance(60, moment=2)) # .10834
    print(sult.whole_life_insurance(70, moment=2)) # .21467
    print(sult.endowment_insurance(60, t=10, moment=2)) # .38732
    print(sult.temporary_annuity(60, t=10, variance=True)) #.6513


    print(sult.p_x(70)) # 0.989587
    print(math.log(sult.p_x(70, t=2)) / -2)  # 0.011103

    A1 = sult.whole_life_insurance(20, discrete=False)
    #print(A1, sult.whole_life(20))
    A2 = sult.whole_life_insurance(50, discrete=False)
    #print(A2, sult.whole_life(50))
    A3 = sult.whole_life_insurance(70, discrete=False)
    E2 = sult.E_x(20, t=30)
    E3 = sult.E_x(20, t=50)
    print(A1, E2, A2, E3, A3, 125*A1 + E2*175*A2 - E3*250*A3)  # 5,335

    print(sult.whole_life_insurance(50)) # .18931
    print(sult.term_insurance(50, t=20)) # .0402
    print(sult.endowment_insurance(50, t=20))  # ~.31471
    print(sult.E_x(50, t=20), 
          sult.whole_life_insurance(70),
          sult.deferred_insurance(50, u=20)) # .14911

    print(sult.whole_life_insurance(50, discrete=False)) # .19400
    print(sult.term_insurance(50, t=20, discrete=False)) # .0412
    print(sult.endowment_insurance(50, t=20, discrete=False))  #.38944
    print(sult.E_x(50, t=20), 
          sult.whole_life_insurance(70, discrete=False),
          sult.deferred_insurance(50, u=20, discrete=False)) # .15281
  
    print("Standard Ultimate Life Table at i=0.05")
    print(sult.frame())
    print()
