#!/usr/bin/env python
#
# Copyright 2012 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio 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, or (at your option)
# any later version.
#
# GNU Radio 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 GNU Radio; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

import threading
import curses
import os, sys, time
from optparse import OptionParser

import Ice
from gnuradio.ctrlport import GNURadio

ENTER = chr(10)
UP_ARROW = chr(65)
DOWN_ARROW = chr(66)

class modem_monitor(threading.Thread):
    def __init__(self, cb_live, cb_exit, interface):
        threading.Thread.__init__(self)
        self.cb_live = cb_live
        self.cb_exit = cb_exit

        self.running = True

    def __del__(self):
        rx.close()

    def run(self):                
        while self.running:
            time.sleep(0.5)

    def shutdown(self):
        self.running = False
        self.rx.close()

    def cb(self,contents):
        (op, sep, payload) = contents.partition(":")
        if(op == "live"):
            print "live"
            self.cb_live(payload)
        elif(op == "exit"):
            self.cb_exit(payload)
        else:
            print "unknown op arrived! garbage on multicast adx?"

class modem_window(threading.Thread):
    def __init__(self, locator):
        threading.Thread.__init__(self)
        self.locator = locator

        # curses init
        self.win = curses.newwin(30,100,4,4)

        # Ice/GRCP init
        self.comm = Ice.initialize()
        proxy = self.comm.stringToProxy(locator)
        self.radio = GNURadio.ControlPortPrx.checkedCast(proxy)
        self.updateKnobs()

        # GUI init
        self.running = True
        self.ssel = 0
        self.start()
        #self.updateGUI()
        
        # input loop
        while(self.running):
            self.getInput()

        # wait for update thread exit
        self.join()

    def updateKnobs(self):
        self.knobs = self.radio.get([])

    def getInput(self):
        a = self.win.getch()
        if(a <= 256):
            a = chr(a)
            if(a == 'q'):
                self.running = False
            elif(a == UP_ARROW):
                self.ssel = max(self.ssel-1, 0)
                self.updateGUI()
            elif(a == DOWN_ARROW):
                self.ssel = max(min(self.ssel+1, len(self.knobs.keys())-1),0)
                self.updateGUI()
        self.updateGUI()

    def updateGUI(self):
        self.win.clear()
        self.win.border(0)
        self.win.addstr(1, 2, "Modem Statistics :: %s"%(self.locator))
        self.win.addstr(2, 2, "---------------------------------------------------")

        maxnb = 0
        maxk = 0
        for k in self.knobs.keys():
            (nb,k) = k.split("::", 2)
            maxnb = max(maxnb,len(nb))
            maxk = max(maxk,len(k))

        offset = 3
        keys = self.knobs.keys()
        keys.sort()
        for k in keys:
            (nb,sk) = k.split("::", 2)
            v = self.knobs[k].value
            sv = str(v)
            if(len(sv) > 20):
                sv = sv[0:20]
            props = 0
            if(self.ssel == offset-3):
                props = props | curses.A_REVERSE
            self.win.addstr(offset, 2, "%s %s %s" % \
                (nb.rjust(maxnb," "), sk.ljust(maxk," "), sv),props)
            offset = offset + 1
        self.win.refresh()

    def run(self):
        while(self.running):
            self.updateKnobs()
            self.updateGUI()
            time.sleep(1)

class monitor_gui:
    def __init__(self, interfaces, options):

        locator = None

        # Extract options into a locator
        if(options.host and options.port):
            locator = "{0} -t:{1} -h {2} -p {3}".format(
                options.app, options.protocol,
                options.host, options.port)

        # Set up GUI
        self.locators = {}

        self.mode = 0 # modem index screen (modal keyboard input)
        self.lsel = 0 # selected locator
        self.scr = curses.initscr()
        self.updateGUI()

        # Kick off initial monitors
        self.monitors = []
        for i in interfaces:
            self.monitors.append( modem_monitor(self.addModem, self.delModem, i) )
            self.monitors[-1].start()

        if not ((locator == None) or (locator == "")):
            self.addModem(locator)

        # wait for user input
        while(True):
            self.getInput()

    def addModem(self, locator):
        if(not self.locators.has_key(locator)):
            self.locators[locator] = {}
        self.locators[locator]["update_time"] = time.time()
        self.locators[locator]["status"] = "live"

        self.updateGUI();

    def delModem(self, locator):
        #if(self.locators.has_key(locator)):
        if(not self.locators.has_key(locator)):
            self.locators[locator] = {}
        self.locators[locator]["update_time"] = time.time()
        self.locators[locator]["status"] = "exit"

        self.updateGUI()

    def updateGUI(self):
        if(self.mode == 0): #redraw locators
            self.scr.clear()
            self.scr.border(0)
            self.scr.addstr(1, 2, " GRCP-Curses Modem Monitor :: (A)dd (R)efresh, (Q)uit, ...")
            for i in range(len(self.locators.keys())):
                locator = self.locators.keys()[i]
                lhash = self.locators[locator]
                #self.scr.addstr(3+i, 5, locator + str(lhash))
                props = 0
                if(self.lsel == i):
                    props = props | curses.A_REVERSE
                self.scr.addstr(3+i, 5, locator + str(lhash), props)
            self.scr.refresh()

    def connectGUI(self):
        self.scr.clear()
        self.scr.addstr(1, 1, "Connect to radio:")
        locator = self.scr.getstr(200)
        self.addModem(locator)
        self.updateGUI()

    def getInput(self):
        a = self.scr.getch()
        self.scr.addstr(20, 2, "got key (%d)    " % (int(a)))
        if(a <= 256):
            a = chr(a)
            if(a =='r'):
                self.updateGUI()
            elif(a == 'q'):
                self.shutdown()
            elif(a == 'a'):
                self.connectGUI()
            elif(a == UP_ARROW):
                self.lsel = max(self.lsel-1, 0)
                self.updateGUI()
            elif(a == DOWN_ARROW):
                self.lsel = max(min(self.lsel+1, len(self.locators.keys())-1),0)
                self.updateGUI()
            elif(a == ENTER):
                try:
                    locator = self.locators.keys()[self.lsel]
                    self.mode = 1
                    mwin = modem_window(locator)
                    self.mode = 0
                    # pop up a new modem display ...
                    self.updateGUI()
                except:
                    pass
        
    def shutdown(self):
        curses.endwin()
        os._exit(0)

if __name__ == "__main__":
    parser = OptionParser()
    parser.add_option("-H", "--host", type="string",
                      help="Hostname of ControlPort server.")
    parser.add_option("-p", "--port", type="int",
                      help="Port number of host's ControlPort endpoint.")
    parser.add_option("-i", "--interfaces", type="string",
                      action="append", default=["lo"],
                      help="Interfaces to use. [default=%default]")
    parser.add_option("-P", "--protocol", type="string", default="tcp",
                      help="Type of protocol to use (usually tcp). [default=%default]")
    parser.add_option("-a", "--app", type="string", default="gnuradio",
                      help="Name of application [default=%default]")
    (options, args) = parser.parse_args()

    if((options.host == None) ^ (options.port == None)):
        print "Please set both a hostname and a port number.\n"
        parser.print_help()
        sys.exit(1)

    mg = monitor_gui(options.interfaces, options)

