# -*- coding: latin-1 -*-
# -----------------------------------------------------------------------------
# Copyright 2009-2015 Stephen Tiedemann <stephen.tiedemann@gmail.com>
#
# Licensed under the EUPL, Version 1.1 or - as soon they 
# will be approved by the European Commission - subsequent
# versions of the EUPL (the "Licence");
# You may not use this work except in compliance with the
# Licence.
# You may obtain a copy of the Licence at:
#
# http://www.osor.eu/eupl
#
# Unless required by applicable law or agreed to in
# writing, software distributed under the Licence is
# distributed on an "AS IS" basis,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied.
# See the Licence for the specific language governing
# permissions and limitations under the Licence.
# -----------------------------------------------------------------------------

import logging
log = logging.getLogger(__name__)

from os import urandom
from time import time
from collections import namedtuple
from binascii import hexlify

import nfc.clf

class DataExchangeProtocol(object):
    def __init__(self, clf):
        self.count = Counters()
        self.clf = clf
        self.gbi = ""
        self.gbt = ""

    @property
    def general_bytes(self):
        """The general bytes received with the ATR exchange"""
        if isinstance(self, Initiator):
            return str(self.gbt)
        if isinstance(self, Target):
            return str(self.gbi)

    @property
    def role(self):
        """Role in DEP communication, either 'Target' or 'Initiator'"""
        if isinstance(self, Initiator):
            return "Initiator"
        if isinstance(self, Target):
            return "Target"

    @property
    def stat(self):
        return "sent/rcvd " \
            "INF {count.inf_sent}/{count.inf_rcvd} " \
            "ATN {count.atn_sent}/{count.atn_rcvd} " \
            "ACK {count.ack_sent}/{count.ack_rcvd} " \
            "NAK {count.nak_sent}/{count.nak_rcvd} " \
            .format(count=self.count)

class Initiator(DataExchangeProtocol):
    def __init__(self, clf):
        DataExchangeProtocol.__init__(self, clf)
        self.target = None
        self.miu = None # maximum information unit size
        self.did = None # dep device identifier
        self.nad = None # dep node address
        self.gbt = None # general bytes from target
        self.pni = None # dep packet number information
        self.rwt = None # target response waiting time
        self.acm = None # active communication mode flag

    def __str__(self):
        msg = "NFC-DEP Initiator {brty} {mode} mode MIU={miu} RWT={rwt:.6f}"
        return msg.format(brty=self.target.brty, miu=self.miu, rwt=self.rwt,
                          mode=("passive", "active")[self.acm])

    def activate(self, target=None, **options):
        """Activate DEP communication with a target."""

        log.debug("initiator options: {0}".format(options))

        timeout = 4096 * 2**12 / 13.56E6
        self.did = options.get('did', None)
        self.nad = options.get('nad', None)
        self.gbi = options.get('gbi', '')[0:48]
        self.brs = min(max(0, options.get('brs', 2)), 2)
        self.lri = min(max(0, options.get('lri', 3)), 3)
  
      if self.acm is None or 'acm' in options:
            self.acm = bool(options.get('acm', True))

        assert self.did is None or (self.did >=0 a
nd self.did <= 255)
        assert self.nad is None or (self.nad >=0 and self.nad <= 255)

        ppi = (self.lri << 4) | (bool(self.gbi) << 1) | int(bool(self.nad))
        did = 0 if self.did is None else self.did
        atr_req = ATR_REQ(urandom(10), did, 0, 0, ppi, self.gbi)
        psl_req = PSL_REQ(did, (0, 9, 18)[self.brs], self.lri)
        atr_res = psl_res = None
        self.target = target
        
        if self.target is None and self.acm is True:
            log.debug("searching active communication mode target at 106A")
            target = nfc.clf.RemoteTarget("106A", atr_req=atr_req.encode())
            try:
                target = self.clf.sense(target,iterations=2,interval=0.1)
                if target: atr_res = ATR_RES.decode(target.atr_res)
            except nfc.clf.UnsupportedTargetError:
                self.acm = False
            except nfc.clf.CommunicationError:
                pass
            else:
                self.target = target

        if self.target is None:
            log.debug("searching passive communication mode target at 106A")
            target = nfc.clf.RemoteTarget("106A")
            target = self.clf.sense(target, iterations=2, interval=0.1)
            if target:
                if not (target.sel_res and bool(target.sel_res[0] & 0x40)):
                    log.debug("Target does not support NFC-DEP")
                    return None
                self.target = target

        if self.target and self.target.atr_res is None:
            try: atr_res = self.send_req_recv_res(atr_req, 1.0)
            except nfc.clf.CommunicationError: pass
            if atr_res is None:
                log.debug("NFC-DEP Attribute Request failed")
                return None

        if self.target and atr_res:
            if self.brs > 0:
                try: psl_res = self.send_req_recv_res(psl_req, 0.1)
                except nfc.clf.CommunicationError: pass
                if psl_res is None:
                    log.debug("NFC-DEP Parameter Selection failed")
                    return None
                self.target.brty = ('212F','424F')[self.brs-1]

            self.rwt = 4096/13.56E6 * 2**(atr_res.wt if atr_res.wt<15 else 14)
            self.miu = atr_res.lr-3 - int(self.did!=None) - int(self.nad!=None)
            self.gbt = atr_res.gb
            self.pni = 0

            log.info("running as " + str(self))
            return self.gbt

    def deactivate(self, release=True):
        log.info("stop {0}, packets {1}".format(self, self.stat))
        REQ, RES = (RLS_REQ, RLS_RES) if release else (DSL_REQ, DSL_RES)
        try: res = self.send_req_recv_res(REQ(self.did), 0.1)
        except nfc.clf.CommunicationError: return
        if type(res) != RES:
            log.error("received unexpected response for " + req.NAME)
        if res.did != req.did:
            log.error("target returned wrong DID in " + res.NAME)

    def exchange(self, send_data, timeout):
        def INF(pni, data, more, did, nad):
            pdu_type = (DEP_REQ.LastInformation, DEP_REQ.MoreInformation)[more]
            pfb = DEP_REQ.PFB(pdu_type, nad is not None, did is not None, pni)
            return DEP_REQ(pfb, did, nad, data)
            
        def ACK(pni, did, nad):
            pdu_type = DEP_REQ.PositiveAck
            pfb = DEP_REQ.PFB(pdu_type, nad is not None, did is not None, pni)
            return DEP_REQ(pfb, did, nad, data=None)
        
        def RTOX(rtox, did, nad):
            if rtox < 1 or rtox > 59:
                error = "NFC-DEP RTOX must be in range 1 to 59"
                raise nfc.clf.ProtocolError(error)
            pdu_type = DEP_REQ.TimeoutExtension
            pfb = DEP_REQ.PFB(pdu_type, nad is not None, did is not None, 0)
            return DEP_REQ(pfb, did, nad, data=bytearray([rtox]))

        #log.debug("dep raw >> " + str(send_data).encode("hex"))
        send_data = bytearray(send_data)
        
        while send_data:
            data = send_data[0:self.miu]; del send_data[0:self.miu]
            req = INF(self.pni, data, bool(send_data), self.did, self.nad)
            res = self.send_dep_req_recv_dep_res(req, timeout)
            self.count.inf_sent += 1
            if res.pfb.type == DEP_RES.TimeoutExtension:
                req = RTOX(res.data[0], self.did, self.nad)
                rwt = res.data[0] * self.rwt
                log.warning("target requested %.3f sec more time" % rwt)
                res = self.send_dep_req_recv_dep_res(req, min(timeout, rwt))
            if res.pfb.type == DEP_RES.TimeoutExtension:
                log.error("target repeated timeout extension request")
                raise nfc.clf.TimeoutError("repeated timeout extension")
            if res.pfb.type == DEP_RES.PositiveAck:
        