# -*- coding: utf-8 -*-
# Functions used for communication of LILT system
# Copyright (C) 2011 Matěj Grégr, Michal Kajan, Libor Polčák, Vladimír Veselý
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import select
import socket
import sys
import time

from .li_socket import LISocket, LIServerSocket, LIHeartbeatSocket, TCPSocketWrapper
from ..shared.liexceptions import LIException
from ..tools import log as log
from subprocess import call

SOCKET_PATHS = {
    "hi1": "/tmp/hi1",
    "hi2": "/tmp/hi2",
    "hi3": "/tmp/hi3",
    "ini1a": "/tmp/ini1a",
    "ini1b": "/tmp/ini1b",
    "ini1c": "/tmp/ini1c",
    "ini2": "/tmp/ini2",
    "ini3": "/tmp/ini3",
    "ccci": "/tmp/ccci",
    # Sockets for internal communication inside function blocks
    "afwiq": "/tmp/afwiq",
    "afaiq": "/tmp/afaiq",
    "iricorewq" : "/tmp/iricorewq",
    "liptest": "/tmp/liptest",
    "dhcp4": "/tmp/dhcp4",
    "ipv6ns": "/tmp/ipv6ns",
    "iricol": "/tmp/iricol"
    }

def ptptAsServer(liName, liSocketClass = LISocket):
    """ Creates Unix socket for point-to-point communication as server

    liName Name of the LI interface
    liSocketClass Class of socket (LISocket or derrived) to be created

    Returns LI socket
    """
    s = socketAsServer(liName)
    conn = acceptConnection(s, liName, liSocketClass)
    s.close()
    return conn

def socketAsServer(liName, queue = 5):
    """ Creates server Unix socket for communication establishment

    liName Name of the LI interface

    Returns socket.socket
    """
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    name = SOCKET_PATHS[liName]
    try:
        os.remove(name)
    except OSError:
        # Socket was not present
        pass
    s.bind(name)
    s.listen(queue)
    call(["sudo", "chmod", "a+w", name])
    return LIServerSocket(s, liName)

def tcpSocketAsServer(liName, ifc_addr, port, wrappers = ()):
    """ Creates server TCP socket

    liName
      Name of the LI interface
    ifc_addr
      IP address where to listen
    port
      Number of TCP port where the server is going to accept connections
    """
    s = None
    for res in socket.getaddrinfo(ifc_addr, port, socket.AF_UNSPEC, \
            socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
        af, socktype, proto, canonname, sa = res
        try:
            s = socket.socket(af, socktype, proto)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        except socket.error as msg:
            s = None
            continue
        try:
            s.bind(sa)
            s.listen(5)
        except socket.error as msg:
            s.close()
            s = None
            continue
        break
    if s is None:
        raise LIException("Could not open server socket")
    s = LIServerSocket(s, liName)
    for wrapper in wrappers:
        s = wrapper(s)
    return s

def acceptTCPLIConnection(s, sm, ifcName, liSocketClass = LIHeartbeatSocket):
    """ Handles new connection from a probe

    s
      Server socket
    sm
      Socket manager
    ifcName
      Name of the LI interface in lower case

    Returns created socket
    """
    conn = acceptTCPLIConnectionSimple(s, ifcName, liSocketClass)
    log.info("Connected %s through %s" % (conn.getpeername(), ifcName.upper()))
    sm.addPtPtSocket(conn, ifcName + str(time.localtime()))
    return conn

def acceptTCPLIConnectionSimple(s, ifcName = "Anonymous", liSocketClass = LISocket):
    """ Accepts TCP connection but does not add it to Socket Manager """
    return TCPSocketWrapper(acceptConnection(s, ifcName, liSocketClass))

def acceptConnection(s, liName = "Anonymous", liSocketClass = LISocket):
    """ Accepts new connection for server

    s socket
    liName Name of the LI interface
    liSocketClass Class of socket (LISocket or derrived) to be created

    Returns LI socket
    """
    conn, addr = s.accept()
    return liSocketClass(conn, liName)

def ptptAsClient(liName, liSocketClass = LISocket):
    """ Creates Unix socket for point-to-point communication as client

    liName Name of the LI interface
    liSocketClass Class of socket (LISocket or derrived) to be created

    Returns LI socket
    """
    OK = False
    while not OK:
        try:
            s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            s.connect(SOCKET_PATHS[liName])
            s = liSocketClass(s, liName)
            OK = True
        except socket.error:
            # Server not ready
            pass
    return s

def tcpSocketAsClient(liName, server, port, liSocketClass = LISocket, liSocketParams = ()):
    """ Creates client TCP socket

    liName
      Name of the LI interface
    server
      IP address or DNS name of the server
    port
      TCP port number of the server
    liSocketClass
      Class of socket (LISocket or derrived) to be created
    liSocketParams
      Additional parameters to be sent to liSocketClass constructor
    """
    s = None
    for res in socket.getaddrinfo(server, port, socket.AF_UNSPEC, \
            socket.SOCK_STREAM):
        af, socktype, proto, canonname, sa = res
        log.debug("tcpSocketAsClient trying ", res)
        try:
            s = socket.socket(af, socktype, proto)
        except socket.error as msg:
            log.debug("tcpSocketAsClient: Failed to create socket for ", res)
            s = None
            continue
        try:
            s.connect(sa)
        except socket.error as msg:
            log.debug("tcpSocketAsClient: Failed to connect ", res)
            s.close()
            s = None
            continue
        break
    if s is None:
        raise LIException("Could not connect to %s port %d" % (server, port))
    return liSocketClass(TCPSocketWrapper(s), liName, *liSocketParams)

