#!/usr/bin/python
"""A [GNU Units][gnu-units] compatible exchange rates currencies updater

If you have ever used [GNU units][gnu-units], you may have enjoyed
it's very smooth user interface and command line arguments
interpretation. `units` also provides exchange rates currency
conversions. It does so by reading the file `currency.units`
(usually located in `/usr/share/units/` in most distributions). The
`units` program usually is distributed with a python script
`units_cur` which updates the file `currency.units` with data from
[https://finance.yahoo.com/](https://finance.yahoo.com/)'s free API.

The purpose of this script is to provide an alternative
yet compatible currency units updater for GNU units using
[https://openexchangerates.org](https://openexchangerates.org) (also
free with registration) API. The advantages in using this script over
the original `units_cur` are:

- More currencies including digital currencies as well:
https://docs.openexchangerates.org/docs/supported-currencies
- Faster and more accurate exchange rates
using base currencies according to [your
choice](https://docs.openexchangerates.org/docs/set-base-currency#default-base-currency).

[gnu-units]: https://gnu.org/software/units

*** ANY PROBLEMS WITH THIS SCRIPT SHOULD BE REPORTED AT THE GITLAB REPO ***
***        https://gitlab.com/doronbehar/units-openexchangerates        ***
***                       -------------------                           ***

"""

# For Python 2 & 3 compatibility
from __future__ import absolute_import, division, print_function

# Normal imports
from argparse import ArgumentParser
from datetime import date
from os import linesep
import sys
import codecs
import requests

VERSION = '4.1'

KNOWN_CURRENCIES = dict(
    AED='uaedirham',
    AFN='afghanafghani',
    ALL='albanialek',
    AMD='armeniadram',
    ANG='netherlandsguilder',  # duplicated in KNOWN_EURO_RATES
    AOA='angolakwanza',
    ARS='argentinapeso',
    ATS='austriaschilling',
    AUD='australiadollar',
    AWG='arubaflorin',
    AZN='azerbaijanmanat',
    BAM='bosniaconvertiblemark',  # duplicated in KNOWN_EURO_RATES
    BBD='barbadosdollar',
    BDT='bangladeshtaka',
    BEF='belgiumfranc',
    BGN='bulgarialev',  # duplicated in KNOWN_EURO_RATES
    BHD='bahraindinar',
    BIF='burundifranc',
    BMD='bermudadollar',
    BND='bruneidollar',
    BOB='boliviaboliviano',
    BRL='brazilreal',
    BSD='bahamasdollar',
    BTC='bitcoin',
    BTN='bhutanngultrum',
    BTS='bitshare',
    BWP='botswanapula',
    BYN='belarusruble',
    BYR='oldbelarusruble',
    BZD='belizedollar',
    CAD='canadadollar',
    CDF='drcfranccongolais',
    CHF='swissfranc',
    CLF='ufchileanunitofaccount',
    CLP='chilepeso',
    CNH='offshorechinayuan',
    CNY='chinayuan',
    COP='colombiapeso',
    CRC='costaricacolon',
    CUC='cubanconvertiblepeso',
    CUP='cubapeso',
    CVE='capeverdeescudo',  # duplicated in KNOWN_EURO_RATES
    CYP='cypruspound',
    CZK='czechkoruna',
    DASH='dash',
    DEM='germanymark',
    DJF='djiboutifranc',
    DKK='denmarkkrona',
    DOGE='dogecoin',
    DOP='dominicanrepublicpeso',
    DZD='algeriadinar',
    EAC='earthcoin',
    EEK='estoniakroon',
    EGP='egyptpound',
    EMC='emercoin',
    ERN='eritreanakfa',
    ESP='spainpeseta',
    ETB='ethiopianbirr',
    ETH='ethereum',
    EUR='euro',
    FCT='factom',
    FIM='finlandmarkka',
    FJD='fijidollar',
    FKP='falklandislandspound',
    FRF='francefranc',
    FTC='feathercoin',
    GBP='ukpound',
    GEL='georgialari',
    GGP='guernseypound',
    GHS='ghanacedi',
    GIP='gibraltarpound',
    GMD='gambiadalasi',
    GNF='guineafranc',
    GRD='greecedrachma',
    GTQ='guatemalaquetzal',
    GYD='guyanadollar',
    HKD='hongkongdollar',
    HNL='honduraslempira',
    HRK='croatiakuna',
    HTG='haitigourde',
    HUF='hungariaforint',
    IDR='indonesiarupiah',
    IEP='irelandpunt',
    ILS='israelnewshekel',
    IMP='manxpound',
    INR='indiarupee',
    IQD='iraqdinar',
    IRR='iranrial',
    ISK='icelandkrona',
    ITL='italylira',
    JEP='jerseypound',
    JMD='jamaicadollar',
    JOD='jordandinar',
    JPY='japanyen',
    KES='kenyaschilling',
    KGS='kyrgyzstansom',
    KHR='cambodiariel',
    KMF='comorosfranc',  # duplicated in KNOWN_EURO_RATES
    KPW='northkoreawon',
    KRW='southkoreawon',
    KWD='kuwaitdinar',
    KYD='caymanislandsdollar',
    KZT='kazakhstantenge',
    LAK='laokip',
    LBP='lebanonpound',
    LD='lindendollar',
    LKR='srilankanrupee',
    LRD='liberiadollar',
    LSL='lesotholoti',
    LTC='litecoin',
    LTL='lithuanialitas',
    LUF='luxembourgfranc',
    LVL='latvialats',
    LYD='libyadinar',
    MAD='moroccodirham',
    MDL='moldovaleu',
    MGA='madagascarariary',
    MKD='macedoniadenar',
    MMK='myanmarkyat',
    MNT='mongoliatugrik',
    MOP='macaupataca',
    MRO='mauritaniaouguiya',
    MRU='mauritanian',
    MTL='maltalira',
    MUR='mauritiusrupee',
    MVR='maldiverufiyaa',
    MWK='malawikwacha',
    MXN='mexicopeso',
    MYR='malaysiaringgit',
    MZN='mozambicanmetical',
    NAD='namibiadollar',
    NGN='nigerianaira',
    NIO='nicaraguacordobaoro',
    NLG='netherlandsguilder',  # duplicated in KNOWN_EURO_RATES
    NMC='namecoin',
    NOK='norwaykrone',
    NPR='nepalrupee',
    NVC='novacoin',
    NXT='nxt',
    NZD='newzealanddollar',
    OMR='omanrial',
    PAB='panamabalboa',
    PEN='perunuevosol',
    PGK='papuanewguineakina',
    PHP='philippinepeso',
    PKR='pakistanrupee',
    PLN='polandzloty',
    PPC='peercoin',
    PTE='portugalescudo',
    PYG='paraguayguarani',
    QAR='qatarrial',
    RON='romanianewlei',
    RSD='serbiadinar',
    RUB='russiaruble',
    RWF='rwandafranc',
    SAR='saudiarabiariyal',
    SBD='solomonislandsdollar',
    SCR='seychellesrupee',
    SDG='sudanpound',
    SEK='swedenkrona',
    SGD='singaporedollar',
    SHP='sainthelenapound',
    SIT='sloveniatolar',
    SKK='slovakiakornua',
    SLL='sierraleoneleone',
    SOS='somaliaschilling',
    SRD='surinamedollar',
    SSP='southsudanesepound',
    STD='saotome&principedobra',
    STN='saotomeandprincipedobra',
    STR='stellar',
    SVC='elsalvadorcolon',
    SYP='syriapound',
    SZL='swazilandlilangeni',
    THB='thailandbaht',
    TJS='tajikistansomoni',
    TMT='turkmenistanmanat',
    TND='tunisiadinar',
    TOP="tongapa'anga",
    TRY='turkeylira',
    TTD='trinidadandtobagodollar',
    TWD='taiwandollar',
    TZS='tanzaniashilling',
    UAH='ukrainehryvnia',
    UGX='ugandaschilling',
    USD='unitedstatesdollar',
    UYU='uruguaypeso',
    UZS='uzbekistansum',
    VEB='venezuelaoldbolivar',
    VEF_BLKMKT='blackmarketvenezuelanbolivar',
    VEF_DICOM='dicomvenezuelanbolivar',
    VEF_DIPRO='diprovenezuelanbolivar',
    VEF='venezuelabolivar',
    VND='vietnamdong',
    VTC='vertcoin',
    VUV='vanuatuvatu',
    WST='samoatala',
    XAF='centralafricancfafranc',  # duplicated in KNOWN_EURO_RATES
    XAG='silverounce',  # already defined in definitions.units
    XAU='goldounce',  # already defined in definitions.units
    XCD='eastcaribbeandollar',
    XDR='specialdrawingrights',
    XMR='monero',
    XOF='westafricanfranc',  # duplicated in KNOWN_EURO_RATES
    XPD='palladiumounce',  # already defined in definitions.units
    XPF='cfpfranc',  # duplicated in KNOWN_EURO_RATES
    XPM='primecoin',
    XPT='platinumounce',  # already defined in definitions.units
    XRP='ripple',
    YER='yemenrial',
    ZAR='southafricarand',
    ZMW='zambiakwacha',
    ZWL='zimbabwedollar'
)

KNOWN_EURO_RATES = dict(
    ATS=13.7603,
    BEF=40.3399,
    CYP=0.585274,
    EEK=15.6466,  # Equals 1|8 germanymark
    FIM=5.94573,
    FRF=6.55957,
    DEM=1.95583,
    GRD=340.75,
    IEP=0.787564,
    ITL=1936.27,
    LVL=0.702804,
    LTL=3.4528,
    LUF=40.3399,
    MTL=0.4293,
    SKK=30.1260,
    SIT=239.640,
    ESP=166.386,
    NLG=2.20371,
    PTE=200.482,
    CVE=110.265,
    BGN=1.9558,
    KMF=491.96775,
    XOF=655.957,
    XPF=119.33,
    XAF=655.957,
)

KNOWN_ALIAS_CURRENCIES = dict(
    BAM='germanymark'
)

DEFAULT_OUTFILE_NAME = '/usr/share/units/currency.units'

OUTFILE_TEMPLATE = """# ISO Currency Codes
{codestr}
# Currency exchange rates from Open Exchange Rates (openexchangerates.org)
!message Currency exchange rates from openexchangerates.org on {datestr}

{ratestr}
# Precious metals prices from Packetizer (services.packetizer.com/spotprices)
{ozzystr}
"""
if sys.version_info[0] == 2:
    OUTFILE_TEMPLATE = unicode(OUTFILE_TEMPLATE)


def metal_prices():
    """Get only the metals prices"""
    try:
        req = requests.get('http://services.packetizer.com/spotprices/?f=json')
        req.raise_for_status()
        metals = req.json()
    except requests.exceptions.RequestException as err:
        exit('Error connecting to spotprices server:\n{}\n'
             .format(err))

    del metals['date']

    ozzystr = '\n'.join('{:19}{} US$/troyounce'.format(
        metal + 'price',
        price,
        ) for metal, price in metals.items())
    return ozzystr


def main():
    """
    main
    """
    argp = ArgumentParser(
        description="GNU units compatible currencies information updater"
        "into the specified filename or if no filename is "
        "given, the default: '{}'.  The special filename '-' "
        "will send the currency data to stdout.".format(DEFAULT_OUTFILE_NAME),
    )

    argp.add_argument(
        'output_file',
        default=DEFAULT_OUTFILE_NAME,
        help='the file to update',
        metavar='filename',
        nargs='?',
        type=str,
    )

    argp.add_argument(
        '-V', '--version',
        action='version',
        version='%(prog)s version ' + VERSION,
        help='display units_cur version',
    )

    argp.add_argument(
        '-k', '--apikey',
        metavar='APIKEY',
        nargs=1,
        required=True,
        help='API key to use when querying openexchangerates.org'
    )

    argp.add_argument(
        '-a', '--alternative',
        action='store_true',
        help=(
            'Add alternative unofficial, black market and digital '
            'currencies as well'
        )
    )

    api_base = 'https://openexchangerates.org/api'
    api_params = '?app_id=' + argp.parse_args().apikey[0]
    if argp.parse_args().alternative:
        api_params = api_params + '&show_alternative=1'

    try:
        res = requests.get(api_base + '/latest.json' + api_params)
        res.raise_for_status()
        rates = res.json()["rates"]
    except requests.exceptions.RequestException as err:
        exit('Error connecting openexchangerates.org server:\n{}.'
             '\n'
             .format(err))

    try:
        res = requests.get(api_base + '/currencies.json' + api_params)
        res.raise_for_status()
        api_currencies = res.json()
    except requests.exceptions.RequestException as err:
        exit('Error connecting openexchangerates.org server:\n{}.'
             '\n'
             .format(err))

    codestr = ""
    for currency_code in api_currencies:
        if currency_code not in ("XAG", "XAU", "XPD", "XPT", "ANG",
                                 "BAM", "BGN", "CVE", "KMF",
                                 "NLG", "XAF", "XOF", "XPF"):
            if currency_code in KNOWN_CURRENCIES:
                codestr = codestr + \
                          currency_code + "\t" + \
                          KNOWN_CURRENCIES[currency_code] + \
                          "\n"
            else:
                del rates[currency_code]
                sys.stderr.write(
                    'recieved unknown 3 letter currency code: {} - {}\n'
                    .format(
                        currency_code,
                        api_currencies[currency_code]
                    ))
        else:
            del rates[currency_code]

    ratestr = ""
    for rate in rates:
        if rate not in ("XAG", "XAU", "XPD", "XPT", "ANG",
                        "BAM", "BGN", "CVE", "KMF",
                        "NLG", "XAF", "XOF", "XPF"):
            ratestr = ratestr + \
                      KNOWN_CURRENCIES[rate] + "\t" + \
                      "1|" + str(rates[rate]) + " dollar" + \
                      '\t# ' + api_currencies[rate] + \
                      "\n"

    for rate in KNOWN_EURO_RATES:
        ratestr = ratestr + \
                  KNOWN_CURRENCIES[rate] + "\t" + \
                  "1|" + str(KNOWN_EURO_RATES[rate]) + " euro" + \
                  "\n"

    for rate in KNOWN_ALIAS_CURRENCIES:
        ratestr = ratestr + \
                  KNOWN_CURRENCIES[rate] + "\t" + \
                  KNOWN_ALIAS_CURRENCIES[rate] + \
                  "\n"

    outstr = OUTFILE_TEMPLATE.format(
        codestr=codestr,
        datestr=date.today().isoformat(),
        ratestr=ratestr,
        ozzystr=metal_prices()
    ).replace('\n', linesep)

    output_file = argp.parse_args().output_file
    try:
        if output_file == '-':
            outfile = codecs.StreamReader(sys.stdout, codecs.getreader('utf8'))
        else:
            outfile = codecs.open(output_file, 'w', 'utf8')
        outfile.write(outstr)
    except IOError as err:
        sys.stderr.write('Unable to write to output file:\n{}\n'.format(err))
        exit(1)


if __name__ == '__main__':
    main()
