"""generic watcher object that keeps track of connection status"""
# This should be deprecated in favor of daemon mode + management
# interface. But we can leave it here for debug purposes.


class EIPConnectionStatus(object):
    """
    Keep track of client (gui) and openvpn
    states.

    These are the OpenVPN states:
    CONNECTING    -- OpenVPN's initial state.
    WAIT          -- (Client only) Waiting for initial response
                     from server.
    AUTH          -- (Client only) Authenticating with server.
    GET_CONFIG    -- (Client only) Downloading configuration options
                     from server.
    ASSIGN_IP     -- Assigning IP address to virtual network
                     interface.
    ADD_ROUTES    -- Adding routes to system.
    CONNECTED     -- Initialization Sequence Completed.
    RECONNECTING  -- A restart has occurred.
    EXITING       -- A graceful exit is in progress.

    We add some extra states:

    DISCONNECTED  -- GUI initial state.
    UNRECOVERABLE -- An unrecoverable error has been raised
                     while invoking openvpn service.
    """
    CONNECTING = 1
    WAIT = 2
    AUTH = 3
    GET_CONFIG = 4
    ASSIGN_IP = 5
    ADD_ROUTES = 6
    CONNECTED = 7
    RECONNECTING = 8
    EXITING = 9

    # gui specific states:
    UNRECOVERABLE = 11
    DISCONNECTED = 0

    def __init__(self, callbacks=None):
        """
        EIPConnectionStatus is initialized with a tuple
        of signals to be triggered.
        :param callbacks: a tuple of (callable) observers
        :type callbacks: tuple
        """
        # (callbacks to connect to signals in Qt-land)
        self.current = self.DISCONNECTED
        self.previous = None
        self.callbacks = callbacks

    def get_readable_status(self):
        # XXX DRY status / labels a little bit.
        # think we'll want to i18n this.
        human_status = {
            0: 'disconnected',
            1: 'connecting',
            2: 'waiting',
            3: 'authenticating',
            4: 'getting config',
            5: 'assigning ip',
            6: 'adding routes',
            7: 'connected',
            8: 'reconnecting',
            9: 'exiting',
            11: 'unrecoverable error',
        }
        return human_status[self.current]

    def get_state_icon(self):
        """
        returns the high level icon
        for each fine-grain openvpn state
        """
        connecting = (self.CONNECTING,
                      self.WAIT,
                      self.AUTH,
                      self.GET_CONFIG,
                      self.ASSIGN_IP,
                      self.ADD_ROUTES)
        connected = (self.CONNECTED,)
        disconnected = (self.DISCONNECTED,
                        self.UNRECOVERABLE)

        # this can be made smarter,
        # but it's like it'll change,
        # so +readability.

        if self.current in connecting:
            return "connecting"
        if self.current in connected:
            return "connected"
        if self.current in disconnected:
            return "disconnected"

    def set_vpn_state(self, status):
        """
        accepts a state string from the management
        interface, and sets the internal state.
        :param status: openvpn STATE (uppercase).
        :type status: str
        """
        if hasattr(self, status):
            self.change_to(getattr(self, status))

    def set_current(self, to):
        """
        setter for the 'current' property
        :param to: destination state
        :type to: int
        """
        self.current = to

    def change_to(self, to):
        """
        :param to: destination state
        :type to: int
        """
        if to == self.current:
            return
        changed = False
        from_ = self.current
        self.current = to

        # We can add transition restrictions
        # here to ensure no transitions are
        # allowed outside the fsm.

        self.set_current(to)
        changed = True

        #trigger signals (as callbacks)
        #print('current state: %s' % self.current)
        if changed:
            self.previous = from_
            if self.callbacks:
                for cb in self.callbacks:
                    if callable(cb):
                        cb(self)


def status_watcher(cs, line):
    """
    a wrapper that calls to ConnectionStatus object
    :param cs: a EIPConnectionStatus instance
    :type cs: EIPConnectionStatus object
    :param line: a single line of the watched output
    :type line: str
    """
    #print('status watcher watching')

    # from the mullvad code, should watch for
    # things like:
    # "Initialization Sequence Completed"
    # "With Errors"
    # "Tap-Win32"

    if "Completed" in line:
        cs.change_to(cs.CONNECTED)
        return

    if "Initial packet from" in line:
        cs.change_to(cs.CONNECTING)
        return