#!/usr/bin/env python3
""" simple script to control TFT backlights by invoking a script via telnet

    Now has a thread for each address

    SVN $WCREV$:$WCRANGE$ $WCMODS?M:$ Date:$WCDATE$
"""

import os
import sys
import subprocess
import socket
import telnetlib
import threading

# expected TFT prompt.
TFT_PROMPT = b"root@iptft:#"
# who we log in as (!)
TFT_USER = "root"

# pathnames of scripts used on the TFT to actually
TFT_BACKLIGHT_ON_SCRIPT = "/usr/bin/ignition_on.sh"
TFT_BACKLIGHT_OFF_SCRIPT = "/usr/bin/ignition_off.sh"

def usage():
    """ print program usage
        we now allow addresses to be specified in comma-separated form,
        could be useful later
    """
    progname = sys.argv[0]
    print("usage: %s [--on|--off] ipaddr1[,ipaddr2,ipaddr3] ipaddrA[,ipAddrB,ipaddrC] ..." % progname)
    print("    control backlight (~powersave) on IP-TFTs")

def isValidIpv4Addr(ipAddr):
    """ test that a passed string represents a valid IPv4 host address
        http://stackoverflow.com/questions/319279
    """
    try:
        socket.inet_pton(socket.AF_INET, ipAddr)
    except AttributeError:  # no inet_pton here, sorry
        try:
            socket.inet_aton(ipAddr)
        except socket.error:
            return False
        return ipAddr.count('.') == 3
    except socket.error:  # not a valid address
        return False
    return True

def pingHost(ipAddr):
    """ simple check that a host is present, by pinging its IP address.
        returns True or False

        ping parameters:
                -c1         perform one ICMP ping only
                -W2         two second timeout in case of no response
    """
    fdNull = open(os.devnull, 'wb')
    try:
        status = subprocess.call(["ping", ipAddr, "-c1", "-W2"], stdout=fdNull, close_fds=True)
    finally:
        fdNull.close()
    return bool(not status)

def setBacklight(ipAddr, state=True):
    """ attempt to control the backlight state of a TFT
        state is the new backlight state: True -> on, False -> Off

        assumption is that the TFT at (ipAddr) is known to be present
        NB: python < 2.6 has no timeout value on eg. telnetlib; this is dangerous...
    """
    print("Connecting to:", ipAddr)
    try:
        tnConn = telnetlib.Telnet(ipAddr)
        tnConn.read_until(b"login: ")
        tnConn.write((TFT_USER + '\n').encode('ascii'))
        tnConn.read_until(TFT_PROMPT)

        if state:
            print("Switch backlight on")
            tnConn.write((TFT_BACKLIGHT_ON_SCRIPT + '\n').encode('ascii'))
        else:
            print("Switch backlight off")
            tnConn.write((TFT_BACKLIGHT_OFF_SCRIPT + '\n').encode('ascii'))
        # wait for the process to return
        tnConn.read_until(TFT_PROMPT)
        tnConn.close()
    except (EOFError, OSError, socket.error) as ex:
        print("Error: Telnet session unexpected error: %s" % ex)
        return False
    return True

def backlightThreadFn(ipAddr, newState):
    """ thread function to turn a backlight on or off
    """
    if pingHost(ipAddr):
        setBacklight(ipAddr, newState)
    else:
        print("host %s not present" % (ipAddr))
    #
    return


def runActionList(actionList):
    """ create and run a series of threads to enable/disable backlights
    """
    threadList = []

    for action in actionList:
        ipAddr, newState = action[0], action[1]

        newThread = threading.Thread(target=backlightThreadFn, args=(ipAddr, newState))
        threadList.append(newThread)

    # run all threads and wait for completion
    for threadId in threadList:
        threadId.start()
    for threadId in threadList:
        threadId.join()

def processArgs():
    """ create a list of (ipAddr, newState) tuples, for
        processing as a set of action threads
    """
    bState = True
    actionList = []
    for parm in sys.argv[1:]:
        if parm == '--on':
            bState = True
        elif parm == '--off':
            bState = False
        else:
            # we now allow a 'parameter' to be a comma-separated sequence of IPv4 addresses
            for candidateAddr in parm.split(","):
                if isValidIpv4Addr(candidateAddr):
                    actionList.append((candidateAddr, bState))
                else:
                    print("ignoring invalid address parameter '%s'" % candidateAddr)
    #
    return actionList

def main():
    """ main executive """
    if len(sys.argv) < 2 or len(sys.argv) > 8:
        usage()
        sys.exit(0)
    if sys.argv[1] == '-h' or sys.argv[1] == '-H' or sys.argv[1] == '-?':
        usage()
        sys.exit(0)

    actList = processArgs()
    if actList:
        runActionList(actList)


if __name__ == "__main__":
    main()
