import json,coins
from datetime import datetime
import urllib,urllib2

# base URL, may need to be updated!
base_url = "http://rush.math.utah.edu:8082/" 
# to get a url
def urlget(url):
    response = urllib2.urlopen(url)
    return response.read()

# to post json data to a url
def urlpost(url,data):
    req = urllib2.Request(url,data,{'Content-Type': 'application/json'})
    try:
        res=urllib2.urlopen(req)
    except urllib2.HTTPError as e:
        return e.read(),e.code
    else:
        s,status = res.read(),res.getcode()
        res.close()
        return s,status

def convert_to_builtin_type(obj):
    """
    serialization using JSON (see http://pymotw.com/2/json/)
    transforms a python object into a JSON object
    """
    # Convert objects to a dictionary of their representation
    d = { '__class__':obj.__class__.__name__,
          '__module__':obj.__module__ }
    d.update(obj.__dict__)
    return d

def dict_to_object(d):
    """
    serialization using JSON (see http://pymotw.com/2/json/)
    transforms a JSON object into a Python object
    """
    if '__class__' in d:
        class_name = d.pop('__class__')
        module_name = d.pop('__module__')
        module = __import__(module_name)
        class_ = getattr(module,class_name)
        args = dict( (key.encode('ascii'), value) for key, value in d.items())
        for key, value in args.items():
            if isinstance(value,unicode): args[key] = value.encode('ascii')
        inst = class_(**args)
    else:
        inst = d
    return inst

# recursively encode all strings into ascii
# see http://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-of-unicode-ones-from-json-in-python
def fixunicode(input):
    if isinstance(input, dict):
        return {fixunicode(key): fixunicode(value) for key, value in input.iteritems()}
    elif isinstance(input, list):
        return [fixunicode(element) for element in input]
    elif isinstance(input, unicode):
        return input.encode('ascii')
    else:
        return input

def get_wallet(pubkey):
    """
    Get wallet (i.e. all the unspent transactions) from server

    :param pubkey: public key of party
    :type pubkey: :py:class:`coins.PublicKey`
    :return: the wallet (a list of transaction hash, output number, amount)
    """
    assert isinstance(pubkey,coins.PublicKey)
    r=urlget(base_url + "wallet?obj=1&N=%d&e=%d"%(pubkey.N,pubkey.e))
    w = fixunicode(json.loads(r))
    return dict([  ( (x[0],x[1]), y) for x,y in w ])


def post_tx(tx,stx):
    """
    Post a transaction to the server

    :param tx: the transaction to be sent
    :type tx: :py:class:`coins.Transaction`
    :param int stx: the signature for the transaction
    :return: success,msg. success is True if the transaction was added successfully to the list of pending transactions. If success is False, the msg is set to the error encountered by the server
    :rtype: (bool,str)
    """
    assert isinstance(tx,coins.Transaction)
    assert isinstance(stx,(int,long))
    s = json.dumps([tx,stx],default=convert_to_builtin_type)
    text,code=urlpost(base_url + "txsubmit",s)
    if code == 200:
        return True,"Success\n"+text
    else:
        return False,"Problem submitting transaction\n"+text


def get_newblock(pubkey):
    """
    Get a new block from server

    :param pubkey: public key of party the reward should be assigned to
    :type pubkey: :py:class:`coins.PublicKey`
    :return: a new block
    :rtype: :py:class:`coins.Block`
    """
    assert isinstance(pubkey,coins.PublicKey)
    r=urlget(base_url+"pending?obj=1")
    p=fixunicode(json.loads(r,object_hook=dict_to_object))
    pending = p['pending_tx']
    coinbase = coins.Transaction( 
                            inputs=[ coins.TxIn () ],
                            outputs=[ coins.TxOut(pubkey,p['reward']) ],
                            note="block mined on "+str(datetime.now()) )
    txlist = [ [coinbase,0] ] + [ [tx,sig] for tx,sig,t in pending.values() ]
    # return new block
    return  coins.Block(
            transactions =  txlist,
            nonce = 0,
            difficulty = int(p['difficulty']),
            hash_prev = p['lastblock'])

def post_newblock(b):
    """
    Post a new block to the server
    
    :param b: a new block
    :type b: :py:class:`coins.Block`
    :return: success,msg. success is True if the block was added successfully to the blockchain. If success is False, the msg is set to the error encountered by the server
    :rtype: (bool,str)
    """
    assert isinstance(b,coins.Block)
    s = json.dumps(b,default=convert_to_builtin_type)
    text,code=urlpost(base_url + "blsubmit",data=s)
    if code == 200:
       return True,"Success\n"+text
    else:
        return False,"Problem submitting block \n"+text
