#!/usr/bin/env python3
# Usage {{{1
"""
Time Value of Money

Usage:
    tvm [options] [{outputs}]

Options:
    -f <val>, --fv <val>     future value
    -p <val>, --pv <val>     present value
    -P <val>, --pmt <val>    payment per period
    -y <val>, --years <val>  total number of years
    -n <val>, --freq <val>   number of payments per year
    -r <val>, --rate <val>   annual discount rate
    -i, --ignore             ignore any previously specified values

If a value is not given it is recalled from the previous invocation.
Specify --ignore to use the default values for all unspecified options, which 
are: pv=0, fv=0, pmt=0, years=30, freq=12.
"""

# Imports {{{1
from appdirs import user_cache_dir
from docopt import docopt
from inform import conjoin, display, fatal, os_error, Color
from json import loads, dumps
from math import log
from pathlib import Path
from quantiphy import Quantity, QuantiPhyError
Quantity.set_prefs(prec=2, strip_zeros=False, spacer='')

# Globals {{{1
outputs = 'fv pv pmt years'.split()
currency = '$'
__version__ = '0.3.0'
__released__ = '2019-04-04'

# load params {{{1
cache_dir = Path(user_cache_dir('tvm'))
cache = Path(cache_dir, 'data.json')
try:
    raw = cache.read_text()
    params = loads(raw)
except FileNotFoundError:
    params = {}
except OSError as e:
    fatal(os_error(e))

# read command line {{{1
cmdline = docopt(__doc__.format(outputs='|'.join(outputs)), options_first=False)
if cmdline['--ignore']:
    params = {}
for name, value in cmdline.items():
    if name.startswith('--') and value and name != '--ignore':
        name = name[2:]
        try:
            params[name] = Quantity(value)
        except QuantiPhyError as e:
            fatal(e, culprit=name)
if 'freq' not in params:
    params['freq'] = 12
if 'years' not in params:
    params['years'] = 30
if 'rate' not in params:
    fatal('discount rate is missing: specify with --rate.')

for k in outputs:
    if cmdline[k]:
        compute = k
        break
else:
    compute = params.get('compute')
if not compute:
    fatal(
        conjoin(outputs, ' or '),
        template='must specify the value you desire ({}).'
    )
params['compute'] = compute

# write out params {{{1
try:
    cache_dir.mkdir(exist_ok=True)
    raw = dumps(params)
    cache.write_text(raw)
except OSError as e:
    fatal(os_error(e))

# utility function {{{1
def periods():
    return params['years']*params['freq']

def rate():
    return params['rate']/params['freq']/100

try:
    # compute future value {{{1
    if compute == 'fv':
        r = rate()
        pv = params.get('pv', 0)
        pmt = params.get('pmt', 0)
        N = periods()
        growth = (1 + r)**N
        fv = pv * growth + pmt * (growth - 1)/r
        results = dict(
            fv = Quantity(fv, currency).fixed(show_commas=True),
            pv = Quantity(pv, currency).fixed(show_commas=True),
            pmt = Quantity(pmt, currency).fixed(show_commas=True),
            r = Quantity(params['rate'], '%').fixed(strip_zeros=True),
            N = Quantity(N).fixed(strip_zeros=True),
        )

    # compute present value {{{1
    elif compute == 'pv':
        r = rate()
        fv = params.get('fv', 0)
        pmt = params.get('pmt', 0)
        N = periods()
        growth = (1 + r)**N
        pv = fv / growth + pmt * (1 - 1/growth)/r
        results = dict(
            pv = Quantity(pv, currency).fixed(show_commas=True),
            fv = Quantity(fv, currency).fixed(show_commas=True),
            pmt = Quantity(pmt, currency).fixed(show_commas=True),
            r = Quantity(params['rate'], '%').fixed(strip_zeros=True),
            N = Quantity(N).fixed(strip_zeros=True),
        )

    # compute payment {{{1
    elif compute == 'pmt':
        r = rate()
        fv = params.get('fv', 0)
        pv = params.get('pv', 0)
        N = periods()
        growth = (1 + r)**N
        pmt = fv / ((growth - 1)/r) - pv / ((1 - 1/growth)/r)
        results = dict(
            pmt = Quantity(pmt, currency).fixed(show_commas=True),
            pv = Quantity(pv, currency).fixed(show_commas=True),
            fv = Quantity(fv, currency).fixed(show_commas=True),
            r = Quantity(params['rate'], '%').fixed(strip_zeros=True),
            N = Quantity(N).fixed(strip_zeros=True),
        )

    # compute years {{{1
    elif compute == 'years':
        r = rate()
        fv = params.get('fv', 0)
        pv = params.get('pv', 0)
        pmt = params.get('pmt', 0)
        freq = params['freq']
        try:
            N = log((fv*r + pmt)/(pv*r + pmt))/log(1+r)
        except ValueError:
            fatal('cannot be computed.', culprit=compute)
        results = dict(
            years = Quantity(N/freq).fixed(strip_zeros=True),
            pv = Quantity(pv, currency).fixed(show_commas=True),
            pmt = Quantity(pmt, currency).fixed(show_commas=True),
            fv = Quantity(fv, currency).fixed(show_commas=True),
            r = Quantity(params['rate'], '%').fixed(strip_zeros=True),
            N = Quantity(N).fixed(strip_zeros=True),
        )

    else:
        raise AssertionError

except KeyError as e:
    fatal('missing:', str(e))

# output results {{{1
computed = Color('white', Color.isTTY())
given = Color('green', Color.isTTY())
for k, v in results.items():
    colorize = computed if k == compute else given
    display(k, v, template=colorize('{} = {}'))

