#!/usr/bin/env python2.7

DUOLINGO = 'https://www.duolingo.com/'

import argparse
import functools
import json
import re
import requests
import signal
import sys
import time
import traceback

p = argparse.ArgumentParser(
    description='Automatically equip streak freeze and enter wager in Duolingo'\
    'when you forget.',
)

p.add_argument(
    '--username', '-u',
    metavar='<username>',
    help='Account username',
    required=True,
)

p.add_argument(
    '--password', '-p',
    metavar='<password>',
    help='Account password',
    required=True,
)

p.add_argument(
    '--streak-freeze', '-f',
    action='store_true',
    help='Equip streak freeze',
    default=True,
)
p.add_argument(
    '--no-streak-freeze', '-F',
    action='store_false',
    dest='streak_freeze',
    help='Do not equip streek freeze',
)

p.add_argument(
    '--wager', '-w',
    action='store_true',
    help='Wager lingots',
    default=True,
)
p.add_argument(
    '--no-wager', '-W',
    action='store_false',
    dest='wager',
    help='Do not wager lingots',
)

args = p.parse_args()

def login():
    global S
    S = requests.Session()
    r = S.post(DUOLINGO + 'login',
               {'login'    : args.username,
                'password' : args.password,
               },
               timeout=30,
    )
    assert r.ok
    try:
        resp = json.loads(r.text)
    except:
        raise ValueError('Server returned invalid JSON: %r' % r.text)
    if 'response' not in resp or resp['response'].lower() != 'ok':
        raise RuntimeError('Could not log in: %r' % resp)

    S.loggedin = True
    S.username = resp.get('username', args.username)
    S.user_id  = int(resp.get('user_id'))
    print 'Logged in as %s (UID: %d)' % (S.username, S.user_id)

def pp(data):
    print json.dumps(data, sort_keys=True,indent=4, separators=(',', ': '))

def get_user_data():
    r = S.get(DUOLINGO + 'users/%s' % S.username,
              timeout=30,
    )
    data = json.loads(r.text)
    S.user_data = data

    # Select fields
    S.lingots = int(data['rupees'])
    S.streak  = int(data['site_streak'])
    S.streak_extended = data['streak_extended_today']
    S.language = data['learning_language']
    S.inventory = data['inventory']
    S.streak_freeze = 'streak_freeze' in S.inventory
    S.wager = 'rupee_wager' in S.inventory

    # pp(data)

def get_items():
    r = S.get(DUOLINGO + 'api/1/store/get_items',
              timeout=30,
    )
    data = json.loads(r.text)
    S.items = {}
    for items in data.values():
        S.items.update({i['name']: i for i in items})

    # pp(S.items)

def buy_item(item):
    if item not in S.items:
        raise ValueError('No such item for sale: %s' % item)
    item = S.items[item]
    if S.lingots < item['price']:
        raise RuntimeError('Not enough lingots')

    r = S.post(DUOLINGO + 'store/purchase_item',
               {'item_name': item['name'],
                'learning_language': S.language,
               },
               timeout=30,
    )

    if not r.ok:
        raise RuntimeError('Could not buy item: %s' % item['title'])

def streak_freeze():
    if S.streak_freeze:
        return
    buy_item('streak_freeze')

def wager():
    if S.wager:
        return
    buy_item('rupee_wager')

def doit():
        login()
        get_user_data()
        get_items()

        print 'Goal reached today: %s' % ('yes' if S.streak_extended else 'no')
        print 'Lingots           : %d' % S.lingots
        print 'Streak            : %d days' % S.streak
        print 'Streak freeze     : %sequipped' % \
            ('not ' if not S.streak_freeze else '')
        print 'Wager             : %sequipped' % ('not ' if not S.wager else '')

        if args.streak_freeze and not S.streak_freeze:
            try:
                print 'Buying streak freeze'
                streak_freeze()
            except Exception as e:
                print e

        if args.wager and not S.wager:
            try:
                print 'Waging lingots'
                wager()
            except Exception as e:
                print e

while True:
    while True:
        try:
            print ' AUTODUO [%s] ' % \
                time.strftime('%F %T', time.localtime(time.time()))

            # Better safe than sorry; we send SIGALRM after one minute to
            # prevent any hangs
            def handler(*_):
                raise Exception('Alarm clock')
            signal.signal(signal.SIGALRM, handler)
            signal.alarm(60)

            doit()

            # Disable alarm
            signal.alarm(0)

            break
        except KeyboardInterrupt:
            sys.exit(1)
        except:
            traceback.print_exc()
            print 'An error occured.  Retrying in 10s.'
            time.sleep(10)
    time.sleep(10*60)
