from threading import Thread, Lock, Event

from nm_payment.base import Terminal, PaymentSession
from nm_payment.exceptions import SessionCancelledError, SessionCompletedError


class DummyPaymentSession(PaymentSession):
    def __init__(
            self, amount, *, before_commit=None,
            on_print=lambda *args, **kwargs: None,
            on_display=lambda *args, **kwargs: None):
        self._lock = Lock()
        self._cancel = Event()

        self._before_commit = before_commit
        self._on_display = on_display
        self._on_print = on_print

        self._result = None
        self._exception = None

        self._thread = Thread(target=self._sequence)
        self._thread.start()

    def _sleep(self, timeout):
        if not self._event.wait(timeout):
            raise SessionCancelledError()

    def _sequence(self):
        try:
            self._on_display("Insert card")

            self._sleep(1)

            self._on_display("Enter pin")

            self._sleep(5)

            commit = True
            if self._before_commit is not None:
                # TODO result
                commit = self._before_commit(None)

            if commit:
                # TODO
                self._result = True
            else:
                self._exception = SessionCancelledError()
        except BaseException as e:
            with self._lock:
                self._exception = e
            raise

    def cancel(self):
        with self._lock:
            if not self._cancel.is_set():
                self._cancel.set()

        try:
            self.result()
        except SessionCancelledError:
            return
        # TODO do other exceptions count as a cancellation
        else:
            raise SessionCompletedError()

    def result(self, *, timeout=None):
        self._thread.join(timeout=timeout)
        if self._thread.is_alive():
            raise TimeoutError()

        if self._exception is not None:
            raise self._exception
        return self._result


class DummyTerminal(Terminal):
    def __init__(self):
        self._lock = Lock()

    def start_payment(
            self, amount, *, before_commit=None,
            on_print=None, on_display=None):
        with self._lock:
            if self._current_session is not None:
                try:
                    self._current_session.cancel()
                # TODO too narrow?
                except SessionCompletedError:
                    pass
            self._current_session = DummyPaymentSession(
                self._connection, amount, before_commit=before_commit,
                on_print=on_print, on_display=on_display
            )
        return self._current_session

    def shutdown(self):
        pass


def open_dummy(uri, *args, **kwargs):
    return DummyTerminal()

@@ -751,23 +751,20 @@
  if 
-not 
 self._
-event
+cancel
 .wai
@@ -1000,9 +1000,9 @@
 eep(
-5
+3
 )%0A%0A 
@@ -2025,16 +2025,243 @@
 result%0A%0A
+    def add_done_callback(self, fn):%0A        def wait():%0A            try:%0A                self.result()%0A            except Exception:%0A                pass%0A            fn(self)%0A%0A        Thread(target=wait, daemon=True).start()%0A%0A
 %0Aclass D
@@ -2335,16 +2335,53 @@
 = Lock()
+%0A        self._current_session = None
 %0A%0A    de
@@ -2801,16 +2801,16 @@
 ession(%0A
+
         
@@ -2820,26 +2820,8 @@
     
- self._connection,
  amo

fix silly bugs in dummy driver