#!/usr/bin/env python
"""
    Minimal backend for an mcash powered store with unlimited supplies.
    Our main persona is a friendly pizza shop at the corner.
"""
import functools
import json
import logging
import md5
import os
import random
import time
import urlparse
import uuid

import requests

import tornado.ioloop
import tornado.options
import tornado.web

JSON_CONTENT = 'application/vnd.api+json'
ORDER_EXPIRES_SEC = 600

shops = {}
transactions = {}

def memoize_singleton(func):
    cache = []

    @functools.wraps(func)
    def memoizer(*args, **kwargs):
        if cache:
            return cache[0]
        rtv = func(*args, **kwargs)
        if rtv is not None:
            cache.append(rtv)
        return rtv

    return memoizer

def memoize(func):
    cache = {}

    @functools.wraps(func)
    def memoizer(*args, **kwargs):
        key = '|'.join(map(str, args) + map(str, kwargs))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]

    return memoizer

@memoize_singleton
def mcash_headers():
    O = tornado.options.options
    headers = {}
    headers['X-Mcash-Merchant'] = O.mcash_merchant
    headers['X-Mcash-User'] = O.mcash_user
    headers['Authorization'] = O.mcash_secret
    headers['X-Testbed-Token'] = O.mcash_token
    return headers

@memoize_singleton
def base_url(request):
    base = urlparse.urlparse(tornado.options.options.mcash_callback_uri or request.full_url())
    return '%s://%s' % (base.scheme, base.netloc)

@memoize_singleton
def register_shortlink(request):
    O = tornado.options.options
    data = {'callback_uri': '%s/api/callback/shortlink/' % base_url(request)}
    if O.mcash_serial_number:
        data['serial_number'] = O.mcash_serial_number

    r = requests.post(O.mcash_endpoint + 'shortlink/', headers=mcash_headers(), data=data)
    if r.ok:
        shortlink_id = r.json()['id']
        logging.info('Shortlink generated: %s %s' % (data['callback_uri'], shortlink_id))
        return shortlink_id
    else:
        logging.error('Error creating a shortlink %s %s %s %s' % (r.status_code, r.url, r.headers, data), exc_info=True)

def generate_inventory(shopid):
    if shopid not in shops:
        selection = ['Roma', 'Milan', 'Bologna', 'Parma', 'Venice', 'Pomodoro',\
                    'Quattro Stagioni', 'Vegan', 'of %s' % shopid.capitalize()]
        shops[shopid] = {'pizzas': {}, 'toppings': {}, 'sizes': {}}

        for (pid, ingred) in enumerate(['garlic', 'extra cheese', 'pepperoni'], 1):
            shops[shopid]['toppings'][pid] = {'id': pid, 'name': ingred, 'price': random.randrange(2, 12)}

        for (pid, size) in enumerate([28, 32, 36], 0):
            shops[shopid]['sizes'][size] = {'id': size, 'name': '%s cm' % size, 'price': pid * 5}

        for (pid, pizza) in enumerate(random.sample(selection, random.randrange(4, len(selection))), 1):
            image = 'images/%s/%s.jpg' % (shopid, pizza.lower().replace(' ', '_'))
            shops[shopid]['pizzas'][pid] = {'id': pid, 'name': 'Pizza %s' % pizza,\
                                            'image': image, 'price': random.randrange(35, 55)}

@memoize
def get_shop_selection(shopid, category, pid=None):
    if shopid not in shops:
        generate_inventory(shopid)

    content = shops[shopid][category]
    # ember-data requires {'pizza': {'id': ...}} or {'pizza': [{'id': 1, ..}, ...]}
    return json.dumps({category[:-1]: content[pid] if pid is not None and pid in content else content.values()})

class MessageBuffer(object):
    def __init__(self):
        self.waiters = {}
        self.cache = []
        self.cache_size = 200

    def register_callback(self, unique_order, callback):
        if unique_order not in self.waiters:
            self.waiters[unique_order] = set([callback])
        else:
            self.waiters[unique_order].add(callback)

    def cancel_wait(self, unique_order, callback):
        if unique_order in self.waiters:
            self.waiters[unique_order].remove(callback)
            if not self.waiters[unique_order]:
                del self.waiters[unique_order]

    def payment_arrived(self, unique_order):