#!/usr/bin/python
#contact_status.py
#
#
#    Copyright DataHaven.NET LTD. of Anguilla, 2006-2013
#    Use of this software constitutes acceptance of the Terms of Use
#      http://datahaven.net/terms_of_use.html
#    All rights reserved.
#

import os
import sys
import time

try:
    from twisted.internet import reactor
except:
    sys.exit('Error initializing twisted.internet.reactor in contact_status.py')
    

import lib.dhnio as dhnio
import lib.nameurl as nameurl
import lib.transport_control as transport_control
import lib.contacts as contacts
import lib.identitycache as identitycache
import lib.automat as automat
import lib.settings as settings
import lib.commands as commands

if transport_control._TransportCSpaceEnable:
    import lib.transport_cspace as transport_cspace
if transport_control._TransportUDPEnable:
    import lib.transport_udp_session as transport_udp_session    

import ratings


_ContactsStatusDict = {}


#------------------------------------------------------------------------------ 


def init():
    dhnio.Dprint(4, 'contact_status.init')
    transport_control.AddInboxCallback(Inbox)
    transport_control.AddOutboxCallback(Outbox)
    transport_control.AddOutboxPacketStatusFunc(OutboxStatus)
    transport_control.AddWorkItemSentCallbackFunc(FileSent)
#    if transport_control._TransportCSpaceEnable:
#        transport_cspace.SetContactStatusNotifyFunc(CSpaceContactStatus)
#    if transport_control._TransportUDPEnable:
#        transport_udp_session.SetStateChangedCallbackFunc(TransportUDPSessionStateChanged)


def shutdown():
    dhnio.Dprint(4, 'contact_status.shutdown')
    global _ContactsStatusDict
    for A in _ContactsStatusDict.values():
        automat.clear_object(A.index)
    _ContactsStatusDict.clear()
    

def isOnline(idurl):
    if idurl in [None, 'None', '']:
        return False
    global _ContactsStatusDict
    if idurl not in _ContactsStatusDict.keys():
        dhnio.Dprint(6, 'contact_status.isOnline %s is not found, ' % idurl)
    return A(idurl).state == 'CONNECTED'


def isOffline(idurl):
    if idurl in [None, 'None', '']:
        return True
    global _ContactsStatusDict
    if idurl not in _ContactsStatusDict.keys():
        dhnio.Dprint(6, 'contact_status.isOffline %s is not found' % idurl)
    return A(idurl).state == 'OFFLINE'


def hasOfflineSuppliers():
    for idurl in contacts.getSupplierIDs():
        if isOffline(idurl):
            return True
    return False


def countOfflineAmong(idurls_list):
    num = 0
    for idurl in idurls_list:
        if isOffline(idurl):
            num += 1
    return num

def countOnlineAmong(idurls_list):
    num = 0
    for idurl in idurls_list:
        if idurl:
            if isOnline(idurl):
                num += 1
    return num

#------------------------------------------------------------------------------ 

#def check_contacts(contacts_list):
#    for idurl in contacts_list:
#        if idurl:
#            A(idurl, 'check') 


def A(idurl, event=None, arg=None):
    global _ContactsStatusDict
    if not _ContactsStatusDict.has_key(idurl):
        _ContactsStatusDict[idurl] = ContactStatus(idurl, 'status_%s' % nameurl.GetName(idurl), 'OFFLINE', 10)
    if event is not None:
        _ContactsStatusDict[idurl].automat(event, arg)
    return _ContactsStatusDict[idurl]
      

class ContactStatus(automat.Automat):
    timers = {
        'timer-20sec': (20.0, ['PING', 'ACK?']),
        }
    
    def __init__(self, idurl, name, state, debug_level):
        self.idurl = idurl
        self.time_connected = None
        automat.Automat.__init__(self, name, state, debug_level)
        dhnio.Dprint(10, 'contact_status.ContactStatus %s %s %s' % (name, state, idurl))
        
    def state_changed(self, oldstate, newstate):
        dhnio.Dprint(6, '%s : [%s]->[%s]' % (nameurl.GetName(self.idurl), oldstate.lower(), newstate.lower()))
        
    def A(self, event, arg):
        #---CONNECTED---
        if self.state is 'CONNECTED':
            if event == 'outbox-packet' and self.isPingPacket(arg) :
                self.state = 'PING'
                self.AckCounter=0
                self.doRepaint(arg)
            elif event == 'sent-failed' and self.isDataPacket(arg) :
                self.state = 'OFFLINE'
                self.doRepaint(arg)
        #---OFFLINE---
        elif self.state is 'OFFLINE':
            if event == 'outbox-packet' and self.isPingPacket(arg) :
                self.state = 'PING'
                self.AckCounter=0
                self.doRepaint(arg)
            elif event == 'inbox-packet' :
                self.state = 'CONNECTED'
                self.doRememberTime(arg)
                self.doRepaint(arg)
        #---PING---
        elif self.state is 'PING':
            if event == 'sent-done' :
                self.state = 'ACK?'
                self.AckCounter=0
            elif event == 'inbox-packet' :
                self.state = 'CONNECTED'
                self.doRememberTime(arg)
                self.doRepaint(arg)
            elif event == 'file-sent' :
                self.AckCounter+=1
            elif event == 'sent-failed' and self.AckCounter>1 :
                self.AckCounter-=1
            elif event == 'timer-20sec' or ( event == 'sent-failed' and self.AckCounter==1 ) :
                self.state = 'OFFLINE'
                self.doRepaint(arg)
        #---ACK?---
        elif self.state is 'ACK?':
            if event == 'inbox-packet' :
                self.state = 'CONNECTED'
                self.doRememberTime(arg)
                self.doRepaint(arg)
            elif event == 'timer-20sec' :
                self.state = 'OFFLINE'
            elif event == 'outbox-packet' and self.isPingPacket(arg) :
                self.state = 'PING'
                self.AckCounter=0
                self.doRepaint(arg)

    def isPingPacket(self, arg):
        return arg.Command == commands.Identity() and arg.wide is True

    def isDataPacket(self, arg):
        return arg[0].command not in [commands.Identity(), commands.Ack()]

    def doRememberTime(self, arg):
        self.time_connected = time.time()
        
    def doRepaint(self, arg):
        if transport_control.GetContactAliveStateNotifierFunc() is not None:
            transport_control.GetContactAliveStateNotifierFunc()(self.idurl)
 
#------------------------------------------------------------------------------ 

def OutboxStatus(workitem, proto, host, status, error, message):
    if status == 'finished':
        A(workitem.remoteid, 'sent-done', (workitem, proto, host))
    else:
        A(workitem.remoteid, 'sent-failed', (workitem, proto, host))


def Inbox(newpacket, proto, host):
    A(newpacket.OwnerID, 'inbox-packet', (newpacket, proto, host))
    ratings.remember_connected_time(newpacket.OwnerID)
    

def Outbox(outpacket):
    A(outpacket.RemoteID, 'outbox-packet', outpacket)


def FileSent(workitem, args):
    A(workitem.remoteid, 'file-sent', (workitem, args))


def PacketSendingTimeout(remoteID, packetID):
    # dhnio.Dprint(6, 'contact_status.PacketSendingTimeout ' + remoteID)
    A(remoteID, 'sent-timeout', packetID)


def TransportUDPSessionStateChanged(automatindex, oldstate, newstate):
    global _ContactsStatusDict
#    if not _ContactsStatusDict.has_key(idurl):
#        return
    sess = automat.objects().get(automatindex, None)
    if sess is None:
        return
    idurl = sess.remote_idurl
    if idurl is None:
        return
    ident = contacts.getContact(idurl)
    if ident is None:
        return
    address = ident.getProtoContact('udp')
    if address is None:
        return True
    try:
        ip, port = address[6:].split(':')
        address = (ip, int(port))
    except:
        dhnio.DprintException()
        return True
    if sess.remote_address == address or sess.remote_address == identitycache.RemapContactAddress(address):
        A(idurl, 'transport_udp_session.state', newstate)

def CSpaceContactStatus(contact, status):
    idurls = identitycache.GetIDURLsByContact('cspace://'+contact)
    idurl = idurls[0] if len(idurls) > 0 else ''
    dhnio.Dprint(10, 'contact_status.CSpaceContactStatus %s:%s is [%s]' % (nameurl.GetName(idurl), contact, status)) 
