#!/usr/bin/python
# coding: iso-8859-15

global asyncore
import asyncore
global socket
import socket

## @brief Klassen zum Erzeugen von TCP-Verbindungen
## @deprecated Dieses Modul sollte nicht mehr eingesetzt werden. In den Beispielen wird gezeigt, wie es durch Standardmodule von Python ersetzt werden kann.
## Klasse ist deprecated
class hsl20_4_tcp:

    ## @cond NO_DOC
    _BUFFER_SIZE = 8192
    ## @endcond

    ## @brief Alle Methoden des TCP-Clients.
	## @deprecated Diese Klasse sollte nicht mehr eingesetzt werden.
    ## @details
    ## Dient zur Kommunikation mit einer Gegenstelle per TCP/IP.
    ## @cond NO_DOC
    ## @code
    ## class mein_demo_modul(hsl20_4.BaseModule):
    ##
    ##     def __init__(self, homeserver_context):
    ##         ...
    ##         #####################################################
    ##
    ##     def on_init(self):
    ##         self.is_connected = False
    ##         self.tcpclient = self.FRAMEWORK.create_tcp_client()
    ##         self.tcpclient.set_address("10.0.0.1", 12345)
    ##         self.tcpclient.set_on_connect(self.on_socket_connected)
    ##         self.tcpclient.set_on_close(self.on_socket_closed)
    ##         self.tcpclient.set_on_error(self.on_socket_error)
    ##         self.tcpclient.set_on_data(self.on_socket_data)
    ##
    ##     def on_input_value(self, index, value):
    ##         if index == self.PIN_I_START:
    ##             self.tcpclient.connect()
    ##         elif index == self.PIN_I_DATEN:
    ##             if self.is_connected:
    ##                 self.tcpclient.send(str(value))
    ##
    ##     def on_socket_connected(self):
    ##         self.is_connected = True
    ##
    ##     def on_socket_error(self):
    ##         self._set_output_value(self.PIN_O_FEHLER, 1.0)
    ##
    ##     def on_socket_closed(self):
    ##         self.is_connected = False
    ##         self.tcpclient.connect()
    ##
    ##     def on_socket_data(self, data):
    ##         self._set_output_value(self.PIN_O_DATEN, str(data))
    ## @endcode
    ## Das Beispiel versucht, beim Eintreffen eines Telegramms auf Eingang 1 (PIN_I_START) eine stehende TCP/IP-Verbindung zur Gegenstelle herzustellen. @n
    ## Wenn diese Verbindung erfolgreich hergestellt wurde, werden alle Telegramme, die auf Eingang 2 (PIN_I_DATEN) eintreffen, auf den Socket geschrieben. @n
    ## Alle Telegramme die über den Socket empfangen werden, werden auf Ausgang 1 (PIN_O_DATEN) geschrieben.
    ## Tritt bei der Kommunikation ein Fehler auf, wird auf Ausgang 2 (PIN_O_FEHLER) eine "1" gesendet.
    ## @endcond
    ##
    ## @note Die Klasse arbeitet asynchron. Keine der angebotenen Methoden blockiert.
    ## @if OLD_CHANGELOG
    ## @chlg03 Beispiel angepasst: 2. Parameter bei on_socket_error entfernt
    ## @chlg15 Beispiel korrigiert, Beschreibungstext des Beispiels korrigiert
    ## @endif
    ## Klasse ist deprecated
    class Client:
                
        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__(self, framework, context_map):
            ## @cond NO_DOC
            self._dispatcher = hsl20_4_tcp._Dispatcher(self, context_map)
            self._ip=None
            self._port=None
            self._on_connect_callback=None
            self._on_data_callback=None
            self._on_error_callback=None
            self._on_close_callback=None
            self._context_map = context_map
            self._framework = framework
            ## @endcond


        ## Setzt die Zieladresse.
        ## @param ip @e string @n IP-Adresse oder Hostname
        ## @param port @e int @n IP-Port
        ## @throws RuntimeError @n Wird diese Methode aufgerufen während der Client bereits verbunden wurde, wird eine RuntimeError-Exception ausgelöst.
        ## @if OLD_CHANGELOG
        ## @chlg03 Exception hinzugefügt
        ## @endif
        def set_address(self, ip, port):
            ## @cond NO_DOC
            if self._dispatcher.connected:
                raise RuntimeError("TCP-Client is already connected!")
            self._ip=ip
            self._port=port
            ## @endcond

        ## Setzt eine Callback-Methode. Die Methode wird aufgerufen, sobald eine Verbindung zur Gegenstelle hergestellt wurde.
        ##
        ## Dem Callback werden keine Parameter übergeben.
        ##
        ## @param callback @e function @n Callback-Methode
        ## @if OLD_CHANGELOG
        ## @chlg03 Erläuterung Callback hinzugefügt
        ## @endif
        def set_on_connect(self, callback):
            ## @cond NO_DOC
            self._on_connect_callback=callback
            ## @endcond


        ## Setzt eine Callback-Methode. Die Methode wird aufgerufen, sobald ein TCP/IP-Telegramm empfangen wurde.
        ##
        ## @param callback @e function @n Callback-Methode
        ## - @b data @e string @n Daten
        ## @if OLD_CHANGELOG
        ## @chlg03 Erläuterung Callback hinzugefügt
        ## @endif
        def set_on_data(self, callback):
            ## @cond NO_DOC
            self._on_data_callback=callback
            ## @endcond


        ## Setzt die Callback-Methode. Die Methode wird aufgerufen, wenn in der Kommunikation ein Fehler aufgetreten ist.
        ##
        ## Parameter für Callback:
        ## - @b exception @e Exception @n Das Exception-Object
        ##
        ## @param callback @e function Callback-Methode
        ## @if OLD_CHANGELOG
        ## @chlg03 Erläuterung Callback hinzugefügt
        ## @chlg04 Parameter @e exception beim Callback hinzugefügt
        ## @endif
        def set_on_error(self, callback):
            ## @cond NO_DOC
            self._on_error_callback=callback
            ## @endcond


        ## Setzt die Callback-Methode. Die Methode wird aufgerufen, sobald die Verbindung (von der Gegenstelle) geschlossen wurde.
        ##
        ## Dem Callback werden keine Parameter übergeben.
        ##
        ## @param callback @e function @n Callback-Methode
        ## @if OLD_CHANGELOG
        ## @chlg03 Beschreibung überarbeitet
        ## @endif
        def set_on_close(self, callback):
            ## @cond NO_DOC
            self._on_close_callback=callback
            ## @endcond


        ## Versucht, eine Verbindung zur eingerichteten Gegenstelle herzustellen.
        ## @throws AttributeError @n Wird ausgelöst, wenn noch keine IP-Adresse oder kein Port definiert wurde.
        ## @throws RuntimeError @n Wird diese Methode aufgerufen während der Client bereits verbunden wurde, wird eine RuntimeError-Exception ausgelöst.
        ## @if OLD_CHANGELOG
        ## @chlg03 Exceptions hinzugefügt
        ## @endif
        def connect(self):
            ## @cond NO_DOC
            if self._dispatcher.connected:
                raise RuntimeError("TCP-Client is already connected!")
            if self._ip == None:
                raise AttributeError("no ip")
            if self._port == None:
                raise AttributeError("no port")
            ip = self._framework.resolve_dns(self._ip)
            if(ip==None):
                raise RuntimeError("Could not resolve Host '%s'." % self._ip)
            self._dispatcher.closed=False
            self._dispatcher.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self._dispatcher.connect((ip, self._port))
            self._framework._signal_asyncore_select_interrupt()
            ## @endcond

        ## Schließt die aktuelle Verbindung.
        def close(self):
            ## @cond NO_DOC
            self._dispatcher.close() 
            ## @endcond

        ## Sendet Daten zur Gegenstelle.
        ## @param data @e string @n Zu versendende Daten
        def send(self, data):
            ## @cond NO_DOC
            self._dispatcher.sendbuffer+=data
            self._framework._signal_asyncore_select_interrupt()
            ## @endcond

    ## @cond NO_DOC
    ## Hilfsklasse für den Client, da dieser nicht direkt asyncore.dispatcher ableiten kann (manche Methoden des Dispatchers sind schon in der Client-Klasse vorhanden und würden die vom Dispatcher überschreiben).
    class _Dispatcher(asyncore.dispatcher):
    
        def __init__(self, client_instance, context_map):
            asyncore.dispatcher.__init__(self, map=context_map)
            self._client_instance=client_instance
            self.sendbuffer=""
            self.closed=False
            
        def readable(self):
            return not self.closed
            
        def writable(self):
            return (not self.closed) and ((not self.connected) or (len(self.sendbuffer)>0))
            
        #Wird ignoriert
        def handle_expt(self):
            pass
            
        def handle_close(self):
            self.closed=True
            # Socket muss noch geschlossen werden
            self.close()
            self._client_instance._framework._run_in_context_thread(self.__handle_close)
            
        def handle_connect_event(self):  
            err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)  
            if err != 0:                                                   
                raise socket.error(err, asyncore._strerror(err))                   
            self.handle_connect()                                    
            self.connected = True                            
            self.connecting = False 
        
        def __handle_close(self):
            if(self._client_instance._on_close_callback!=None):
                self._client_instance._on_close_callback()
                
        def handle_connect(self):
            self._client_instance._framework._run_in_context_thread(self.__handle_connect) 
        
        def __handle_connect(self):
            if(self._client_instance._on_connect_callback!=None):
                self._client_instance._on_connect_callback()
         
        def handle_error(self):
            if(self._client_instance._on_error_callback!=None):
                self._client_instance._framework._run_in_context_thread(self._client_instance._on_error_callback, (sys.exc_info()[1],))
            else:
                # Wenn der Fehler nicht durch den Usercode gehandelt wird, wird die Exception auf der Debug-Seite angezeigt.
                hsl20_4.Framework._get_global_debug_section().add_exception()
            self.handle_close()
        
        def handle_read(self):
            recv_data = self.recv(hsl20_4_tcp._BUFFER_SIZE)
            if(self._client_instance._on_data_callback!=None and len(recv_data)>0):
                self._client_instance._framework._run_in_context_thread(self._client_instance._on_data_callback,(recv_data,))
        
        def handle_write(self):
            if(len(self.sendbuffer)>0):
                sent = self.send(self.sendbuffer)
                self.sendbuffer = self.sendbuffer[sent:]
        
    ## @endcond
        
        
