###############################################################################
#
# Copyright 2011-2012 Pants Developers (see AUTHORS.txt)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
###############################################################################

###############################################################################
# Imports
###############################################################################

import re
import struct

from pants import Stream, Server

try:
    from netstruct import NetStruct as _NetStruct
except ImportError:
    # Create the fake class because isinstance expects a class.
    class _NetStruct(object):
        def __init__(self, *a, **kw):
            raise NotImplementedError


###############################################################################
# Logging
###############################################################################

import logging
log = logging.getLogger(__name__)


###############################################################################
# Constants
###############################################################################

RegexType = type(re.compile(""))
Struct = struct.Struct

# Telnet commands
IAC  = chr(255)  # Interpret As Command
DONT = chr(254)  # Don't Perform
DO   = chr(253)  # Do Perform
WONT = chr(252)  # Won't Perform
WILL = chr(251)  # Will Perform
SB   = chr(250)  # Subnegotiation Begin
SE   = chr(240)  # Subnegotiation End

###############################################################################
# TelnetConnection Class
###############################################################################

class TelnetConnection(Stream):
    """
    A basic implementation of a Telnet connection.

    A TelnetConnection object is capable of identifying and extracting
    Telnet command sequences from incoming data. Upon identifying a
    Telnet command, option or subnegotiation, the connection will call a
    relevant placeholder method. This class should be subclassed to
    provide functionality for individual commands and options.
    """
    def __init__(self, **kwargs):
        Stream.__init__(self, **kwargs)

        # Initialize Stuff
        self._telnet_data = ""

    ##### Public Event Handlers ###############################################

    def on_command(self, command):
        """
        Placeholder. Called when the connection receives a telnet command,
        such as AYT (Are You There).

        =========  ============
        Argument   Description
        =========  ============
        command    The byte representing the telnet command.
        =========  ============
        """
        pass

    def on_option(self, command, option):
        """
        Placeholder. Called when the connection receives a telnet option
        negotiation sequence, such as IAC WILL ECHO.

        =========  ============
        Argument   Description
        =========  ============
        command    The byte representing the telnet command.
        option     The byte representing the telnet option being negotiated.
        =========  ============
        """
        pass

    def on_subnegotiation(self, option, data):
        """
        Placeholder. Called when the connection receives a subnegotiation
        sequence.

        =========  ============
        Argument   Description
        =========  ============
        option     The byte representing the telnet option for which subnegotiation data has been received.
        data       The received data.
        =========  ============
        """
        pass

    ##### Internal Telnet State Processing ####################################

    def _on_telnet_data(self, data):
        self._telnet_data += data

        while self._telnet_data:
            delimiter = self.read_delimiter

            if delimiter is None:
                data = self._telnet_data
                self._telnet_data = ''
                self._safely_call(self.on_read, data)

            elif isinstance(delimiter, (int, long)):
                if len(self._telnet_data) < delimiter:
                    break
                data = self._telnet_data[:delimiter]
                self._telnet_data = self._telnet_data[delimiter:]
                self._safely_call(self.on_read, data)

            elif isinstance(delimiter, basestring):
                mark = self._telnet_data.find(delimiter)
                if mark == -1:
                    break
                data = self._telnet_data[:mark]
                self._telnet_data = self._telnet_data[mark + len(delimiter):]
                self._safely_call(self.on_read, data)

            elif isinstance(delimiter, Struct):
                # Weird. Why are you using Struct in telnet? Silly person.
                if len(self._telnet_data) < delimiter.size:
                    break
                data = self._telnet_data[:delimiter.size]
                self._telnet_data = self._telnet_data[delimiter.size:]

                try:
                    data = delimiter.unpack(data)