#! /usr/bin/env python
from twisted.internet import protocol, reactor, task
from twisted.internet.error import (ConnectionLost, ConnectionAborted,
                                    ConnectionClosed, ConnectionDone)
from twisted.protocols.amp import UnknownRemoteError
from twisted.protocols import amp
from twisted.trial import unittest

from jmbase.commands import *

import json

test_completed = False

class JMProtocolError(Exception):
    pass

class JMBaseProtocol(amp.AMP):
    def checkClientResponse(self, response):
        """A generic check of client acceptance; any failure
        is considered criticial.
        """
        if 'accepted' not in response or not response['accepted']:
            raise Exception(response)

    def defaultErrback(self, failure):
        failure.trap(ConnectionAborted, ConnectionClosed, ConnectionDone,
                     ConnectionLost, UnknownRemoteError)
        raise Exception(failure)

    def defaultCallbacks(self, d):
        d.addCallback(self.checkClientResponse)
        d.addErrback(self.defaultErrback)

def show_receipt(name, *args):
    print("Received msgtype: " + name + ", args: " + ",".join([str(x) for x in args]))

def end_test():
    global test_completed
    test_completed = True


class JMTestServerProtocol(JMBaseProtocol):

    @JMInit.responder
    def on_JM_INIT(self, bcsource, network, chan_configs, minmakers,
                   maker_timeout_sec, dust_threshold, blacklist_location):
        show_receipt("JMINIT", bcsource, network, chan_configs, minmakers,
                     maker_timeout_sec, dust_threshold, blacklist_location)
        d = self.callRemote(JMInitProto,
                            nick_hash_length=1,
                            nick_max_encoded=2,
                            joinmarket_nick_header="J",
                            joinmarket_version=5)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMStartMC.responder
    def on_JM_START_MC(self, nick):
        show_receipt("STARTMC", nick)
        d = self.callRemote(JMUp)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMSetup.responder
    def on_JM_SETUP(self, role, initdata, use_fidelity_bond):
        show_receipt("JMSETUP", role, initdata, use_fidelity_bond)
        d = self.callRemote(JMSetupDone)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMRequestOffers.responder
    def on_JM_REQUEST_OFFERS(self):
        show_receipt("JMREQUESTOFFERS")
        #build a huge orderbook to test BigString Argument
        orderbook = ["aaaa" for _ in range(2**15)]
        d = self.callRemote(JMOffers,
                        orderbook=json.dumps(orderbook),
                        fidelitybonds="dummyfidelitybonds")
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMFill.responder
    def on_JM_FILL(self, amount, commitment, revelation, filled_offers):
        show_receipt("JMFILL", amount, commitment, revelation, filled_offers)
        d = self.callRemote(JMFillResponse,
                            success=True,
                            ioauth_data=['dummy', 'list'])
        return {'accepted': True}

    @JMMakeTx.responder
    def on_JM_MAKE_TX(self, nick_list, tx):
        show_receipt("JMMAKETX", nick_list, tx)
        d = self.callRemote(JMSigReceived,
                            nick="dummynick",
                            sig="xxxsig")
        self.defaultCallbacks(d)
        #add dummy calls to check message sign and message verify
        d2 = self.callRemote(JMRequestMsgSig,
                             nick="dummynickforsign",
                             cmd="command1",
                             msg="msgforsign",
                             msg_to_be_signed="fullmsgforsign",
                             hostid="hostid1")
        self.defaultCallbacks(d2)
        d3 = self.callRemote(JMRequestMsgSigVerify,
                             msg="msgforverify",
                             fullmsg="fullmsgforverify",
                             sig="xxxsigforverify",
                             pubkey="pubkey1",
                             nick="dummynickforverify",
                             hashlen=4,
                             max_encoded=5,
                             hostid="hostid2")
        self.defaultCallbacks(d3)
        d4 = self.callRemote(JMTXBroadcast, tx=b"deadbeef")
        self.defaultCallbacks(d4)
        return {'accepted': True}
            

    @JMMsgSignature.responder
    def on_JM_MSGSIGNATURE(self, nick, cmd, msg_to_return, hostid):
        show_receipt("JMMSGSIGNATURE", nick, cmd, msg_to_return, hostid)
        return {'accepted': True}

    @JMMsgSignatureVerify.responder
    def on_JM_MSGSIGNATURE_VERIFY(self, verif_result, nick, fullmsg, hostid):
        show_receipt("JMMSGSIGVERIFY", verif_result, nick, fullmsg, hostid)
        return {'accepted': True}

class JMTestClientProtocol(JMBaseProtocol):

    def connectionMade(self):
        self.clientStart()

    def clientStart(self):
        d = self.callRemote(JMInit,
                            bcsource="dummyblockchain",
                            network="dummynetwork",
                            chan_configs=['dummy', 'irc', 'config'],
                            minmakers=7,
                            maker_timeout_sec=8,
                            dust_threshold=1500,
                            blacklist_location=".")
        self.defaultCallbacks(d)

    @JMInitProto.responder
    def on_JM_INIT_PROTO(self, nick_hash_length, nick_max_encoded,
                         joinmarket_nick_header, joinmarket_version):
        show_receipt("JMINITPROTO", nick_hash_length, nick_max_encoded,
                     joinmarket_nick_header, joinmarket_version)
        d = self.callRemote(JMStartMC,
                            nick="dummynick")
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMUp.responder
    def on_JM_UP(self):
        show_receipt("JMUP")
        d = self.callRemote(JMSetup,
                            role="TAKER",
                            initdata=None,
                            use_fidelity_bond=False)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMSetupDone.responder
    def on_JM_SETUP_DONE(self):
        show_receipt("JMSETUPDONE")
        d = self.callRemote(JMRequestOffers)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMFillResponse.responder
    def on_JM_FILL_RESPONSE(self, success, ioauth_data):
        show_receipt("JMFILLRESPONSE", success, ioauth_data)
        d = self.callRemote(JMMakeTx,
                            nick_list=['nick1', 'nick2', 'nick3'],
                            tx=b"deadbeef")
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMOffers.responder
    def on_JM_OFFERS(self, orderbook, fidelitybonds):
        show_receipt("JMOFFERS", orderbook, fidelitybonds)
        d = self.callRemote(JMFill,
                            amount=100,
                            commitment="dummycommitment",
                            revelation="dummyrevelation",
                            filled_offers=['list', 'of', 'filled', 'offers'])
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMSigReceived.responder
    def on_JM_SIG_RECEIVED(self, nick, sig):
        show_receipt("JMSIGRECEIVED", nick, sig)
        #end of test
        reactor.callLater(1, end_test)
        return {'accepted': True}

    @JMRequestMsgSig.responder
    def on_JM_REQUEST_MSGSIG(self, nick, cmd, msg, msg_to_be_signed, hostid):
        show_receipt("JMREQUESTMSGSIG", nick, cmd, msg, msg_to_be_signed, hostid)
        d = self.callRemote(JMMsgSignature,
                            nick=nick,
                            cmd=cmd,
                            msg_to_return="xxxcreatedsigxx",
                            hostid=hostid)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMRequestMsgSigVerify.responder
    def on_JM_REQUEST_MSGSIG_VERIFY(self, msg, fullmsg, sig, pubkey, nick,
                                    hashlen, max_encoded, hostid):
        show_receipt("JMREQUESTMSGSIGVERIFY", msg, fullmsg, sig, pubkey,
                     nick, hashlen, max_encoded, hostid)
        d = self.callRemote(JMMsgSignatureVerify,
                            verif_result=True,
                            nick=nick,
                            fullmsg=fullmsg,
                            hostid=hostid)
        self.defaultCallbacks(d)
        return {'accepted': True}

    @JMTXBroadcast.responder
    def on_JM_TX_BROADCAST(self, tx):
        show_receipt("JMTXBROADCAST", tx)
        return {"accepted": True}

class JMTestClientProtocolFactory(protocol.ClientFactory):
    protocol = JMTestClientProtocol

class JMTestServerProtocolFactory(protocol.ServerFactory):
    protocol = JMTestServerProtocol

class TrialTestJMProto(unittest.TestCase):

    def setUp(self):
        print("setUp()")
        self.port = reactor.listenTCP(28184, JMTestServerProtocolFactory())
        self.addCleanup(self.port.stopListening)
        def cb(client):
            self.client = client
            self.addCleanup(self.client.transport.loseConnection)
        creator = protocol.ClientCreator(reactor, JMTestClientProtocol)
        creator.connectTCP("localhost", 28184).addCallback(cb)

    def test_waiter(self):
        print("test_main()")
        return task.deferLater(reactor, 3, self._called_by_deffered)

    def _called_by_deffered(self):
        pass
