#!/usr/bin/env python
"""Visualization tool for viewing AMS memory details.

Display a GTK window with system, VIO bus, and VIO bus devices memory
statistics.  Devices will be added and removed as they are added/removed
from the system.
"""

__author__ = "Robert Jennings rcj@linux.vnet.ibm.com"
__copyright__ = "Copyright (c) 2008 IBM Corporation"
__license__ = "Common Public License v1.0"

import sys
import re
import gobject
from gtk import gdk
import logging
from optparse import OptionParser
from optparse import OptionGroup

import powerpcAMS.amsnet as amsnet
from powerpcAMS.amswidget import *
import powerpcAMS.amsdata as amsdata

_verbose = False
_hosts = []
_port_default = 50000L
_port = 0
_refresh = 1
_run_locally = True
_net_client = True

_widgets = {}
_table = None

def parse_hosts(hosts, def_port=_port_default):
    """Parse a string of IPv4 hosts of form "host[:port][,host[:port]]*"
    
    The result is a list of 3-tuples.  The Tuple has the full hostname,
    port, and the short name (either the part up to the first '.' or
    the full ip address if an address is passed).
   
    Example:
     parse_hosts("bar.tar.com:40,type.great.org,182.582.3.4:40234,trophy:34")
    would return:
     [['bar.tar.com', 40L, 'bar'], ['type.great.org', _port_default, 'type'], \
      ['182.582.3.4', 40234L, '182.582.3.4'], ['trophy', 34L, 'trophy']]
     
    Returns:
        A list of 3-tuples.  The elements are the hostname/IP, the port, and
        a short representation of the name (or full IP addr).
    """

    ip_pattern = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
    hosts = hosts.split(',')
    for idx in range(len(hosts)):
        hosts[idx] = hosts[idx].split(':')
        if len(hosts[idx]) is 1:
            hosts[idx].append(def_port)
        hosts[idx][1] = long(hosts[idx][1])
        # If the IP addr was specified rather than a hostname, keep the full IP
        if re.search(ip_pattern, hosts[idx][0]) is not None:
            hosts[idx].append(hosts[idx][0])
        else:
            hosts[idx].append(hosts[idx][0].split('.')[0])
            if hosts[idx][2] == "localhost":
                hosts[idx][2] = "localhost:%ld" % hosts[idx][1]

    return hosts

def cmdline_parser():
    global _hosts
    global _port_default
    global _port
    global _refresh
    global _verbose
    global _run_locally
    global _net_client

    usage = "usage: %prog [options]"
    parser = OptionParser(usage)
    parser.add_option("-u", "--update", dest="refresh", default=5, type="int",
                      metavar="SEC",
                      help="Seconds between updates [default: %default]")
    parser.add_option("-v", "--verbose", default=False, action="store_true",
                      help="Enable verbose output")
    parser.add_option("-l", "--local", dest="run_local", action="store_true",
                      help="Run locally (network options are ignored [default]")
    parser.add_option("-r", "--remote", dest="run_locally",
                      action="store_false", help="Run over the network")
    parser.set_defaults(run_locally=True)

    network_group = OptionGroup(parser, "Network options",
                                "Specify these options when running remotely")
    network_group.add_option("-c", "--client", dest="net_client",
                             action="store_true",
                             help="Run as a network client [default]")
    network_group.add_option("-s", "--server", dest="net_server",
                             action="store_true",
                             help="Run as a network server")
    network_group.add_option("--hosts", dest="hosts", metavar="HOSTNAME",
                             help="Hostname(s) with optional port number specified as host[:port][,host[:port]][,...] [default: %default:_port_default].")
    network_group.add_option("-p", "--port", dest="port", default=_port_default,
                          type="long",
                          help="Network port number while running as a server. [default: %default]")
    parser.add_option_group(network_group)
    parser.set_defaults(net_client=False, net_server=False)
    parser.set_defaults(hosts="localhost")

    (options, args) = parser.parse_args()

    if not options.run_locally:
        if options.net_client and options.net_server:
            parser.error("Specify either -c or -s, not both.")
        if options.net_server:
            _net_client = False
        else:
            _net_client = True

    if options.refresh < 1:
        parser.error("Update frequency must be greater than 0.")

    if len(args) != 0:
        parser.error("Incorrect number of arguments.")

    _hosts = parse_hosts(options.hosts, 50000L)
    _port = options.port
    _refresh = options.refresh
    _verbose = options.verbose
    _run_locally = options.run_locally

    if _verbose:
        logging.getLogger().setLevel(logging.DEBUG)
        logging.debug("Verbose output enabled")

def process_devices(devices):
    global _widgets
    global _table
    changed = False

    if sorted(_widgets["devices"].keys()) != sorted(devices.keys()):
        changed = True

    if changed:
        # Remove all device widgets from the table
        for widget in _widgets["devices"].values():
            logging.debug("Removing widget from table for " + str(widget))
            _table.remove(widget)

        # Remove reference of widgets for devices no longer in the system
        for key in (k for k in _widgets["devices"].keys() if k not in devices):
            logging.debug("Removing widget for " + key)
            _widgets["devices"].pop(key)

        # Create widgets for new devices
        for key in (k for k in devices.keys() if k not in _widgets["devices"]):
            logging.debug("Creating widget for new device " + key)
            _widgets["devices"][key] = device_data_widget(data=devices[key])
            devices.pop(key)


    # Update values for devices
    for key in devices:
        logging.debug("Updating values for device " + key)
        _widgets["devices"][key].update_values(devices[key])

    if changed:
        # Resize table
        logging.debug("Resize table for %d devices" % (len(_widgets["devices"])))
        _table.resize(1, (3 + len(_widgets["devices"])))

        # Add all devices to table
        column = 3
        for widget in sorted(_widgets["devices"].values()):
            logging.debug("Adding widget to table for " + str(widget))
            _table.attach(widget, column, (column + 1), 0, 1)
            widget.show()
            column = column + 1

def update_single_system_widgets(host="localhost", port=_port_default):
    global _verbose
    global _widgets
    global _run_locally

    if _run_locally:
        data = amsdata.gather_all_data()
        if data is not None:
            response = {"result":"success", "data":data}
        else:
            response = {"result":"error", "data":""}
    else:
        try:
            response = amsnet.net_get_data(host, port, amsnet.cmd_GET_ALL_DATA)
        except:
            gtk.main_quit()
            raise

    if response["result"] != "success":
        logging.error("Data query was unsuccessful.")
        logging.error(response["data"])
        gtk.main_quit()
        return False

    (sys_data, bus_data, devices_data) = response["data"]

    # Need to change the system widget to be host specific
    _widgets["system"].update_values(sys_data)
    _widgets["iobus"].update_values(bus_data)
    process_devices(devices_data)

    return True

def update_multi_system_widgets():
    global _hosts
    global _verbose
    global _widgets
    global _run_locally

    for host in _hosts:
        try:
            response = amsnet.net_get_data(host[0], host[1],
                                           amsnet.cmd_GET_SYS_DATA)
        except:
            gtk.main_quit()
            raise

        if response["result"] != "success":
            logging.error("Data query of %s was unsuccessful." % host[0])
            logging.error(response["data"])
            gtk.main_quit()
            return False

        host[4].update_values(response["data"])

    return True

def start_single_system_gui():
    global _verbose
    global _refresh
    global _run_locally
    global _net_client
    global _widgets
    global _table


    if _run_locally:
        data = amsdata.gather_all_data()
        if data is not None:
            response = {"result":"success", "data":data}
        else:
            response = {"result":"error", "data":""}
    else:
        host = _hosts[0][0]
        port = _hosts[0][1]
        # Attempt a connection to the server prior to building GUI
        response = amsnet.net_get_data(host, port, amsnet.cmd_GET_ALL_DATA)

    if response["result"] != "success":
        logging.error("Data query was unsuccessful.")
        logging.error(response["data"])
        sys.exit(1)

    (sys_data, bus_data, devices_data) = response["data"]

    window = gtk.Window()
    if _run_locally or host == "localhost":
        window.set_title("AMSVis")
    else:
        window.set_title("AMSVis (@" + host + ")")

    icon = window.render_icon(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON)
    window.set_icon(icon)

    scroll = gtk.ScrolledWindow()
    scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
    scroll.set_shadow_type(gtk.SHADOW_NONE)
    scroll.set_placement(gtk.CORNER_TOP_LEFT)

    viewport = gtk.Viewport()
    viewport.set_shadow_type(gtk.SHADOW_NONE)

    _table = gtk.Table(rows = 1, columns = (3 + len(devices_data)))
    _table.set_row_spacings(0)
    _table.set_col_spacings(0)

    _widgets["system"] = system_memory_widget(data=sys_data)
    _widgets["iobus"] = iobus_memory_widget(data=bus_data)
    _widgets["device_labels"] = device_label_widget()
    _widgets["devices"] = {}

    _table.attach(_widgets["system"], 0, 1, 0, 1)
    _table.attach(_widgets["iobus"], 1, 2, 0, 1)
    _table.attach(_widgets["device_labels"], 2, 3, 0, 1,
                  xoptions=gtk.FILL)

    process_devices(devices_data)

    # Getting the size request of the table yeilds (-1, -1) so
    # we get the sizes of each of the components to calculate the
    # size of the window
    (width, height) = _widgets["system"].get_size_request()
    (w, h) = _widgets["iobus"].get_size_request()
    width += w
    height = max(height, h)
    (w, h) = _widgets["device_labels"].get_size_request()
    width += w
    height = max(height, h)
    for k in _widgets["devices"]:
        (w,h) = _widgets["devices"][k].get_size_request()
        width += w
        height = max(height, h)

    # Set default size based size request of the widgets
    window.set_default_size(width, height)

    window.add(scroll)
    scroll.add(viewport)
    viewport.add(_table)

    window.connect("destroy", gtk.main_quit)
    window.show_all()

    # schedule an update for once every _refresh seconds
    if _run_locally:
        gobject.timeout_add(int(_refresh * 1000), update_single_system_widgets)
    else:
        gobject.timeout_add(int(_refresh * 1000), update_single_system_widgets,
                            host, port)

    # Pass control to gtk
    gtk.main()

def start_multi_system_gui():
    global _verbose
    global _hosts
    global _refresh
    global _net_client
    global _widgets
    global _table


    # Attempt a connection to the server prior to building GUI
    for host in _hosts:
        response = amsnet.net_get_data(host[0], host[1],
                                       amsnet.cmd_GET_SYS_DATA)
        if response["result"] != "success":
            logging.error("Data query of %s was unsuccessful." % host[0])
            logging.error(response["data"])
            sys.exit(1)
        host.append(system_name_widget(hostname=host[2]))
        host.append(system_memory_widget(data=response["data"]))

    window = gtk.Window()
    window.set_title("AMSVis Multi-System Overview")

    icon = window.render_icon(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON)
    window.set_icon(icon)

    scroll = gtk.ScrolledWindow()
    scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
    scroll.set_shadow_type(gtk.SHADOW_NONE)
    scroll.set_placement(gtk.CORNER_TOP_LEFT)

    viewport = gtk.Viewport()
    viewport.set_shadow_type(gtk.SHADOW_NONE)

    _table = gtk.Table(rows = 1, columns = len(_hosts))
    _table.set_row_spacings(0)
    _table.set_col_spacings(0)

    col = 0
    for host in _hosts:
        _table.attach(host[3], col, (col + 1), 0, 1, yoptions=gtk.FILL)
        _table.attach(host[4], col, (col + 1), 1, 2)
        col += 1

    # Getting the size request of the table yeilds (-1, -1) so
    # we get the sizes of each of the components to calculate the
    # size of the window
    width, height = (0, 0)
    for host in _hosts:
        (w1, h1) = host[3].get_size_request()
        (w2, h2) = host[4].get_size_request()
        width += max(w1, w2)
        height = max(height, (h1 + h2))

    # Set default size based size request of the widgets
    window.set_default_size(width, height)

    window.add(scroll)
    scroll.add(viewport)
    viewport.add(_table)

    window.connect("destroy", gtk.main_quit)
    window.show_all()

    # schedule an update for once every _refresh seconds
    gobject.timeout_add(int(_refresh * 1000), update_multi_system_widgets)

    # Pass control to gtk
    gtk.main()

def main():
    global _verbose
    global _hosts
    global _port
    global _run_locally
    global _net_client

    cmdline_parser()

    if not _run_locally and not _net_client:
        ret = amsnet.send_data_loop(_port)
        sys.exit(ret)
    elif len(_hosts) == 1:
        start_single_system_gui()
    else:
        start_multi_system_gui()

if __name__ == "__main__":
    main()
