import re
import time
from lxml import etree
import select
import socket
import logging
import sys

from lxml.builder import E
from lxml.etree import XMLSyntaxError
from datetime import datetime, timedelta
from ncclient.operations.rpc import RPCReply, RPCError
from ncclient.xml_ import to_ele
import six
from ncclient.transport.session import HelloHandler


class PY6:
    NEW_LINE = six.b("\n")
    EMPTY_STR = six.b("")
    NETCONF_EOM = six.b("]]>]]>")
    STARTS_WITH = six.b("<!--")


__all__ = ["xmlmode_netconf"]

_NETCONF_EOM = six.b("]]>]]>")
_xmlns = re.compile(six.b("xmlns=[^>]+"))
_xmlns_strip = lambda text: _xmlns.sub(PY6.EMPTY_STR, text)
_junosns = re.compile(six.b("junos:"))
_junosns_strip = lambda text: _junosns.sub(PY6.EMPTY_STR, text)

logger = logging.getLogger("jnpr.junos.tty_netconf")

# =========================================================================
# xmlmode_netconf
# =========================================================================


class tty_netconf(object):

    """
    Basic Junos XML API for bootstraping through the TTY
    """

    def __init__(self, tty):
        self._tty = tty
        self.hello = None
        self._session_id = -1

    # -------------------------------------------------------------------------
    # NETCONF session open and close
    # -------------------------------------------------------------------------

    def open(self, at_shell):
        """ start the XML API process and receive the 'hello' message """
        nc_cmd = ("junoscript", "xml-mode")[at_shell]
        self._tty.write(nc_cmd + " netconf need-trailer")
        mark_start = datetime.now()
        mark_end = mark_start + timedelta(seconds=15)

        while datetime.now() < mark_end:
            time.sleep(0.1)
            line = self._tty.read()
            if line.startswith(PY6.STARTS_WITH):
                break
        else:
            # exceeded the while loop timeout
            raise RuntimeError("Error: netconf not responding")

        self.hello = self._receive()
        self._session_id, _ = HelloHandler.parse(self.hello.decode("utf-8"))

    def close(self, device_handler, force=False):
        """ issue the XML API to close the session """

        # if we do not have an open connection, then return now.
        if force is False:
            if self.hello is None:
                return

        self.rpc("close-session", device_handler)
        # removed flush

    # -------------------------------------------------------------------------
    # MISC device commands
    # -------------------------------------------------------------------------

    def zeroize(self):
        """ issue a reboot to the device """
        cmd = E.command("request system zeroize")
        try:
            encode = None if sys.version < "3" else "unicode"
            self.rpc(etree.tostring(cmd, encoding=encode))
        except:
            return False
        return True

    # -------------------------------------------------------------------------
    # XML RPC command execution
    # -------------------------------------------------------------------------

    def rpc(self, cmd, device_handler):
        """
        Write the XML cmd and return the response as XML object.

        :cmd:
          <str> of the XML command.  if the :cmd: is not XML, then
          this routine will perform the brackets; i.e. if given
          'get-software-information', this routine will turn
          it into '<get-software-information/>'

        NOTES:
          The return XML object is the first child element after
          the <rpc-reply>.  There is also no error-checking
          performing by this routine.
        """
        if not cmd.startswith("<"):
            cmd = "<{}/>".format(cmd)
        rpc = six.b("<rpc>{}</rpc>".format(cmd))
        logger.info("Calling rpc: %s" % rpc)
        self._tty.rawwrite(rpc)

        rsp = self._receive()
        rsp = rsp.decode("utf-8") if isinstance(rsp, bytes) else rsp
        reply = RPCReply(rsp, device_handler, huge_tree=self._tty._huge_tree)
        errors = reply.errors
        if len(errors) > 1:
            raise RPCError(to_ele(reply._raw), errs=errors)
        elif len(errors) == 1:
            raise reply.error
        return reply

    # -------------------------------------------------------------------------
    # LOW-LEVEL I/O for reading back XML response
    # -------------------------------------------------------------------------

    def _receive(self):
        # On windows select.select throws io.UnsupportedOperation: fileno
        # so use read function for windows serial COM ports
        if hasattr(self._tty, "port") and str(self._tty.port).startswith("COM"):
            return self._receive_serial_win()
        else:
            return self._receive_serial()

    def _receive_serial(self):
        """ process the XML response into an XML object """
        rxbuf = PY6.EMPTY_STR
        line = PY6.EMPTY_STR
        while True:
            try:
                rd, wt, err = select.select([self._tty._rx], [], [], 0.1)
            except select.error as err:
                raise err
            except socket.error as err:
                raise err
            if rd:
                line, lastline = rd[0].read_until(PY6.NETCONF_EOM, 0.1), line
                if not line:
                    continue
                if _NETCONF_EOM in line or _NETCONF_EOM in lastline + line:
                    rxbuf = rxbuf + line
                    break
                else:
                    rxbuf = rxbuf + line
                    if _NETCONF_EOM in rxbuf:
                        break
        return self._parse_buffer(rxbuf)

    # -------------------------------------------------------------------------
    # Read message from windows COM ports
    # -------------------------------------------------------------------------

    def _receive_serial_win(self):
        """ process incoming data from windows port"""
        rxbuf = PY6.EMPTY_STR
        line = PY6.EMPTY_STR
        while True:
            line, lastline = self._tty.read().strip(), line
            if not line:
                continue
            if _NETCONF_EOM in line or _NETCONF_EOM in lastline + line:
                rxbuf = rxbuf + line
                break
            else:
                rxbuf = rxbuf + line
                if _NETCONF_EOM in rxbuf:
                    break
        return self._parse_buffer(rxbuf)

    def _parse_buffer(self, rxbuf):
        rxbuf = rxbuf.splitlines()
        if _NETCONF_EOM in rxbuf[-1]:
            if rxbuf[-1] == _NETCONF_EOM:
                rxbuf.pop()
            else:
                rxbuf[-1] = rxbuf[-1].split(_NETCONF_EOM)[0]
        try:
            rxbuf = [i.strip() for i in rxbuf if i.strip() != PY6.EMPTY_STR]
            rcvd_data = PY6.NEW_LINE.join(rxbuf)
            logger.debug("Received: \n%s" % rcvd_data)
            parser = etree.XMLParser(
                remove_blank_text=True, huge_tree=self._tty._huge_tree
            )
            try:
                etree.XML(rcvd_data, parser)
            except XMLSyntaxError:
                if _NETCONF_EOM in rcvd_data:
                    rcvd_data = rcvd_data[: rcvd_data.index(_NETCONF_EOM)]
                    etree.XML(rcvd_data)  # just to recheck
                else:
                    parser = etree.XMLParser(recover=True)
                    rcvd_data = etree.tostring(etree.XML(rcvd_data, parser=parser))
            return rcvd_data
        except:
            if "</xnm:error>" in rxbuf:
                for x in rxbuf:
                    if "<message>" in x:
                        return etree.XML(
                            "<error-in-receive>" + x + "</error-in-receive>"
                        )
            else:
                return etree.XML("<error-in-receive/>")

[((537, 552), 'six.b', 'six.b', (['"""]]>]]>"""'], {}), "(']]>]]>')\n", (542, 552), False, 'import six\n'), ((768, 811), 'logging.getLogger', 'logging.getLogger', (['"""jnpr.junos.tty_netconf"""'], {}), "('jnpr.junos.tty_netconf')\n", (785, 811), False, 'import logging\n'), ((385, 396), 'six.b', 'six.b', (['"""\n"""'], {}), "('\\n')\n", (390, 396), False, 'import six\n'), ((413, 422), 'six.b', 'six.b', (['""""""'], {}), "('')\n", (418, 422), False, 'import six\n'), ((441, 456), 'six.b', 'six.b', (['"""]]>]]>"""'], {}), "(']]>]]>')\n", (446, 456), False, 'import six\n'), ((475, 488), 'six.b', 'six.b', (['"""<!--"""'], {}), "('<!--')\n", (480, 488), False, 'import six\n'), ((573, 593), 'six.b', 'six.b', (['"""xmlns=[^>]+"""'], {}), "('xmlns=[^>]+')\n", (578, 593), False, 'import six\n'), ((677, 692), 'six.b', 'six.b', (['"""junos:"""'], {}), "('junos:')\n", (682, 692), False, 'import six\n'), ((1632, 1646), 'datetime.datetime.now', 'datetime.now', ([], {}), '()\n', (1644, 1646), False, 'from datetime import datetime, timedelta\n'), ((2720, 2755), 'lxml.builder.E.command', 'E.command', (['"""request system zeroize"""'], {}), "('request system zeroize')\n", (2729, 2755), False, 'from lxml.builder import E\n'), ((4024, 4085), 'ncclient.operations.rpc.RPCReply', 'RPCReply', (['rsp', 'device_handler'], {'huge_tree': 'self._tty._huge_tree'}), '(rsp, device_handler, huge_tree=self._tty._huge_tree)\n', (4032, 4085), False, 'from ncclient.operations.rpc import RPCReply, RPCError\n'), ((1679, 1700), 'datetime.timedelta', 'timedelta', ([], {'seconds': '(15)'}), '(seconds=15)\n', (1688, 1700), False, 'from datetime import datetime, timedelta\n'), ((1716, 1730), 'datetime.datetime.now', 'datetime.now', ([], {}), '()\n', (1728, 1730), False, 'from datetime import datetime, timedelta\n'), ((1755, 1770), 'time.sleep', 'time.sleep', (['(0.1)'], {}), '(0.1)\n', (1765, 1770), False, 'import time\n'), ((6963, 7034), 'lxml.etree.XMLParser', 'etree.XMLParser', ([], {'remove_blank_text': '(True)', 'huge_tree': 'self._tty._huge_tree'}), '(remove_blank_text=True, huge_tree=self._tty._huge_tree)\n', (6978, 7034), False, 'from lxml import etree\n'), ((2852, 2888), 'lxml.etree.tostring', 'etree.tostring', (['cmd'], {'encoding': 'encode'}), '(cmd, encoding=encode)\n', (2866, 2888), False, 'from lxml import etree\n'), ((4171, 4189), 'ncclient.xml_.to_ele', 'to_ele', (['reply._raw'], {}), '(reply._raw)\n', (4177, 4189), False, 'from ncclient.xml_ import to_ele\n'), ((5057, 5100), 'select.select', 'select.select', (['[self._tty._rx]', '[]', '[]', '(0.1)'], {}), '([self._tty._rx], [], [], 0.1)\n', (5070, 5100), False, 'import select\n'), ((7098, 7126), 'lxml.etree.XML', 'etree.XML', (['rcvd_data', 'parser'], {}), '(rcvd_data, parser)\n', (7107, 7126), False, 'from lxml import etree\n'), ((7852, 7884), 'lxml.etree.XML', 'etree.XML', (['"""<error-in-receive/>"""'], {}), "('<error-in-receive/>')\n", (7861, 7884), False, 'from lxml import etree\n'), ((7303, 7323), 'lxml.etree.XML', 'etree.XML', (['rcvd_data'], {}), '(rcvd_data)\n', (7312, 7323), False, 'from lxml import etree\n'), ((7394, 7423), 'lxml.etree.XMLParser', 'etree.XMLParser', ([], {'recover': '(True)'}), '(recover=True)\n', (7409, 7423), False, 'from lxml import etree\n'), ((7471, 7506), 'lxml.etree.XML', 'etree.XML', (['rcvd_data'], {'parser': 'parser'}), '(rcvd_data, parser=parser)\n', (7480, 7506), False, 'from lxml import etree\n'), ((7697, 7756), 'lxml.etree.XML', 'etree.XML', (["('<error-in-receive>' + x + '</error-in-receive>')"], {}), "('<error-in-receive>' + x + '</error-in-receive>')\n", (7706, 7756), False, 'from lxml import etree\n')]