#!/usr/bin/env python

# ------------------------------------------------------------------------
# ecamonitor: Ecasound monitor client implemented using NetECI
# Copyright (C) 2002-2003,2009 Kai Vehmanen
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
# ------------------------------------------------------------------------

import curses
import re
import socket
import string
import sys
import time

ecamonitor_remote_host = "localhost"
ecamonitor_remote_port = 2868
ecamonitor_version     = "v20090419-7"

# TODO:
#  - nothing at the moment

# References:
#  - http://www.python.org/doc/essays/styleguide.html
#  - http://py-howto.sourceforge.net/curses/curses.html
#  - http://www.python.org/doc/2.2.2/lib/module-curses.html
#  - http://py-howto.sourceforge.net/regex/regex.html
#  - http://www.python.org/doc/2.2.2/lib/module-re.html
#  - http://py-howto.sourceforge.net/sockets/sockets.html
#  - http://www.python.org/doc/2.2.2/lib/module-string.html

def connect_to_server(remote_host, remote_port):
    """Connects to the ecasound server.

    @return Socket object for the connection.
    """

    while 1:
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((remote_host, remote_port))
            s.setblocking(1)
            return(s)

        except Exception, e:
            if e[0] == 111: # 111 = connection refused
                time.sleep(1)
                pass
            else:
                raise

def issue_eiam_command(s, cmd):
    """Sends a command to ecasound and waits for an response.

    @param s socket for an active connection
    @param cmd EIAM command to send

    @return tuple of return value type and value
    """

    tm = ''
    counter = 0
    s.send(cmd + '\r\n')
    while counter < 16:
        count = counter + 1
        newdata = s.recv(4096)
        if len(newdata) == 0:
            return ('e','')

        tm = tm + newdata

        # lets test whether we have received a valid
        # EIAM command
        try:
            m = expand_eiam_response(tm)
            return parse_eiam_response(m, tm)

        except Exception, e:
            pass

    return ('e','')

def expand_eiam_response(str):
    """Checks wheter 'str' is a valid EIAM response.

    @return Regex match object.
    """

    m = re.match('256 ([0-9]{1,5}) (.+)\r\n(.*)\r\n\r\n.*', str, re.MULTILINE | re.S)
    return m

def parse_eiam_response(m, str):
    """Parses a valid EIAM response.

    @param m Valid regex match object.
    @param str The whole EIAM response.

    @return tuple of return value type and value
    """

    if not m:
        m = re.match('256 ([0-9]{1,5}) (.+)\r\n(.*)', str, re.MULTILINE | re.S)
        if not m:
            raise Exception, 'Regexp failed!'

    if m and len(m.groups()) == 0:
        print "(ecamonitor) Matching groups failed: ", m.groups()

    if m and len(m.groups()) == 3:
        #print 'received=', len(m.group(3)), ', expected=', m.group(1)
        if int(m.group(1)) != len(m.group(3)):
            print "(ecamonitor) Response length error."

    if m:
        return (m.group(2), m.group(3))

    return ('e','')

def main():

    s = None

    remote_host = ecamonitor_remote_host
    remote_port = ecamonitor_remote_port

    if not hasattr(sys, 'version_info') or (hasattr(sys, 'version_info') and sys.version_info[1] <2):
        print 'Error! Ecamonitor requires python-2.0 or newer to run!'
        return 1

    if len(sys.argv) > 1:
        destination = sys.argv[1]
        address = string.split(destination, ':')
        remote_host = address[0]
        if len(address) > 1:
            remote_port = int(address[1])

    try:
        stdscr = curses.initscr()
        pad = curses.newpad(255, 80)
        pad.nodelay(1) # to make getch() nonblocking

        while 1:
            try:
                pad.erase()
                pad.addstr(0, 0, "ecamonitor " + ecamonitor_version, curses.A_BOLD)

                if s == None:
                    pad.addstr(2, 0, "No connection. Trying to connect to " + remote_host + ":" + str(remote_port) + ".\n")
                    pad.refresh(0, 0, 0, 0, stdscr.getmaxyx()[0]-1, stdscr.getmaxyx()[1]-1)
                    #time.sleep(3)
                    s = connect_to_server(remote_host, remote_port)
                    pad.addstr(3, 0, "Connection established.\n")
                    pad.refresh(0, 0, 0, 0, stdscr.getmaxyx()[0]-1, stdscr.getmaxyx()[1]-1)
                    pad.addstr(1, 0, "")
                else:
                    pad.addstr("\n")

                pad.addstr("\n------------------------------------------------------------")
                pad.addstr("\nEngine status: ")
                pad.addstr((issue_eiam_command(s, 'engine-status')[1]), curses.A_BOLD)
                pad.addstr("\nConnected chainsetup: ")
                pad.addstr(issue_eiam_command(s, 'cs-connected')[1], curses.A_BOLD)
                pad.addstr("\nSelected chainsetup: ")
                pad.addstr(issue_eiam_command(s, 'cs-selected')[1], curses.A_BOLD)

                #pad.addstr("\nSelected chainsetup status:: ")
                #pad.addstr(issue_eiam_command(s, 'cs-status')[1])

                pad.addstr("\n\nPosition: ")
                pad.addstr(issue_eiam_command(s, 'cs-get-position')[1] + "s", curses.A_BOLD)
                pad.addstr(" / Length: ")
                pad.addstr(issue_eiam_command(s, 'cs-get-length')[1] + "s", curses.A_BOLD)
                pad.addstr("\nChains: ")
                pad.addstr(str(len(string.split(issue_eiam_command(s, 'c-list')[1],','))), curses.A_BOLD)
                pad.addstr(" / Inputs: ")
                pad.addstr(str(len(string.split(issue_eiam_command(s, 'ai-list')[1],','))), curses.A_BOLD)
                pad.addstr(" / Outputs: ")
                pad.addstr(str(len(string.split(issue_eiam_command(s, 'ao-list')[1],','))), curses.A_BOLD)

                pad.addstr("\n\n------------------------------------------------------------\n")
                res = issue_eiam_command(s, 'aio-status')
                pad.addstr(res[1])

                pad.addstr("\n\n------------------------------------------------------------\n")
                res = issue_eiam_command(s, 'cop-status')
                pad.addstr(res[1])

                pad.addstr("\n\n------------------------------------------------------------\n")
                res = issue_eiam_command(s, 'ctrl-status')
                pad.addstr(res[1])

                pad.addstr("\n\n------------------------------------------------------------\n")

                pad.refresh(0, 0, 0, 0, stdscr.getmaxyx()[0]-1, stdscr.getmaxyx()[1]-1)

                time.sleep(1.0)

                ch=pad.getch()
                if ch == ord('q'):
                    break

            except curses.error:
                raise

            except socket.error, e:
                if e[0] == 32 or e[0] == 104 or e[0] == 111:
                    s = None
                    pass
                else:
                    curses.endwin()
                    print "Exception!" , e
                    raise

            except KeyboardInterrupt:
                break


    finally:
        if s != None:
            s.close()
        curses.endwin()

if __name__ == '__main__':
    main()
