#!/usr/bin/env python2.7
# pybc_coinserver: A generic blockchain server that runs a currency.

import argparse, sys, os, itertools, time, logging
from twisted.internet import reactor

import pybc
import pybc.coin
import pybc.util
import pybc.science

def parse_args(args):
    """
    Takes in the command-line arguments list (args), and returns a nice argparse
    result with fields for all the options.
    Borrows heavily from the argparse documentation examples:
    <http://docs.python.org/library/argparse.html>
    """
    
    # The command line arguments start with the program name, which we don't
    # want to treat as an argument for argparse. So we remove it.
    args = args[1:]
    
    # Construct the parser (which is stored in parser)
    # Module docstring lives in __doc__
    # See http://python-forum.com/pythonforum/viewtopic.php?f=3&t=36847
    # And a formatter class so our examples in the docstring look good. Isn't it
    # convenient how we already wrapped it to 80 characters?
    # See http://docs.python.org/library/argparse.html#formatter-class
    parser = argparse.ArgumentParser(description=__doc__, 
        formatter_class=argparse.RawDescriptionHelpFormatter)
    
    # Now add all the options to it
    parser.add_argument("blockstore",
        help="the name of a file to store blocks in")
    parser.add_argument("keystore",
        help="the name of a file to store blocks in")
    parser.add_argument("peerstore",
        help="the name of a file to store peer addresses in")
    parser.add_argument("--host", default=None,
        help="the host or IP to advertise to other nodes")
    parser.add_argument("--port", type=int, default=8008, 
        help="the port to listen on")
    parser.add_argument("--peer_host", type=str, default=None, 
        help="the hostname of another peer to connect to")
    parser.add_argument("--peer_port", type=int, default=None, 
        help="the port of another peer to connect to")
    parser.add_argument("--transaction_amount", type=int, default=None,
        help="on startup, send a transaction of this size")
    parser.add_argument("--transaction_destination", type=str, default=None,
        help="on startup, send a transaction to this base64 destination")
    parser.add_argument("--generate", action="store_true",
        help="generate a block every so often")
    parser.add_argument("--minify", type=int, default=None,
        help="minify blocks burried deeper than this")
    parser.add_argument("--science",
        help="filename to log statistics to, for doing science")
        
    # Logging options
    parser.add_argument("--loglevel", default="INFO", choices=["DEBUG", "INFO",
        "WARNING", "ERROR", "CRITICAL"],
        help="logging level to use")

        
    return parser.parse_args(args)
    
block_in_progress = None

def generate_block(peer, wallet):
    """
    Keep on generating blocks in the background.
    
    Put the blocks in the given peer's blockchain, and send the proceeds to the
    given wallet.
    
    Don't loop indefinitely, so that the Twisted main thread dying will stop
    us.
   
    
    """
    
    global block_in_progress
    
    if block_in_progress is not None:
        # Keep working on the block we were working on
        success = block_in_progress.do_some_work(peer.blockchain.algorithm)
        
        if success:
            # We found a block!
            logging.info("Generated block!")
            
            # Dump the block
            logging.info("{}".format(block_in_progress))
            for transaction_bytes in pybc.unpack_transactions(
                block_in_progress.payload):
                
                logging.info("{}".format(pybc.coin.Transaction.from_bytes(
                    transaction_bytes)))
            
            peer.send_block(block_in_progress)
            # Start again
            block_in_progress = None
        elif time.time() > block_in_progress.timestamp + 60:
            # This block is too old. Try a new one.
            logging.info("Generating block is getting old! Restart generation!")
            block_in_progress = None
        elif (peer.blockchain.highest_block is not None and 
            peer.blockchain.highest_block.block_hash() != 
            block_in_progress.previous_hash):
            
            # This block is no longer based on the top of the chain
            logging.info("New block from elsewhere! Restart generation!")
            block_in_progress = None
    else:
        # We need to start a new block
        block_in_progress = peer.blockchain.make_block(wallet.get_address())
        
        if block_in_progress is not None:
            logging.info("Starting a block!")
            
            # Might as well dump balance here too
            logging.info("Receiving address: {}".format(pybc.util.bytes2string(
            wallet.get_address())))
            logging.info("Current balance: {}".format(wallet.get_balance()))
    
    # Tell the main thread to make us another thread.
    reactor.callFromThread(reactor.callInThread, generate_block, peer, wallet)
                
def main(args):
    """
    Parses command line arguments, and runs a blockchain peer.
    "args" specifies the program arguments, with args[0] being the executable
    name. The return value should be used as the program's exit code.
    
    """
    
    options = parse_args(args) # This holds the nicely-parsed options object
    
    # Set the log level
    pybc.util.set_loglevel(options.loglevel)
    
    if options.science is not None:
        # Start the science
        pybc.science.log_to(options.science)
    
    logging.info("Starting server on port {}".format(options.port))
    
    pybc.science.log_event("startup")
    
    # Make a CoinBlockchain, using the specified blockchain file
    blockchain = pybc.coin.CoinBlockchain(options.blockstore,
        minification_time=options.minify)
    
    # Make a Wallet that uses the blockchain and our keystore
    wallet = pybc.coin.Wallet(blockchain, options.keystore)
    
    logging.info("Receiving address: {}".format(pybc.util.bytes2string(
        wallet.get_address())))
    logging.info("Current balance: {}".format(wallet.get_balance()))
    
    # Now make a Peer.
    peer = pybc.Peer("PyBC-Coin", 2, blockchain, peer_file=options.peerstore, 
        external_address=options.host, port=options.port)
    
    if options.peer_host is not None and options.peer_port is not None:
        # Point it at a user specified other peer.
        peer.connect(options.peer_host, options.peer_port)
        # Tell it it has a bootstrap peer
        peer.peer_seen(options.peer_host, options.peer_port, None)
    
    if (options.transaction_amount is not None and
        options.transaction_destination is not None):
        
        # Grab the amount to send
        amount = options.transaction_amount
        
        # Grab the destination to send it to
        destination = pybc.string2bytes(options.transaction_destination)
        
        if len(destination) != 32:
            raise Exception("Destination address malformed!")
        
        # We're supposed to send a transaction.
        transaction = wallet.make_simple_transaction(amount, destination)
        
        if transaction is not None:
            logging.info("Sending transaction:")
            logging.info("{}".fromat(transaction))
            # Send the transaction as bytes. It won't get announced right now
            # since we're not connected, but as soon as we connect others should
            # pick it up.
            peer.send_transaction(transaction.to_bytes())
            
        else:
            raise Exception("Could not make requested transaction.")
        
    if options.generate:
        # Schedule a block generation
        reactor.callFromThread(generate_block, peer, wallet)
    
    # Run the peer (and thus the Twisted reactor)
    peer.run()
        
    return 0

if __name__ == "__main__" :
    sys.exit(main(sys.argv))

