from time import sleep

import os

import curses 
from curses import panel
import random
import collections
import operator
import datetime

g_curses_active = False

# simple percetange show
def percentage (a, total):
    x = int ((float(a) / total) * 100)
    return str(x) + "%"

# simple float to human readable
def float_to_human_readable (size, suffix = "bps"):
    for unit in ['','K','M','G']:
        if abs(size) < 1024.0:
            return "%3.1f %s%s" % (size, unit, suffix)
        size /= 1024.0
    return "NaN"

# panel object
class TrexStatusPanel(object):
    def __init__ (self, h, l, y, x, headline):
        self.h = h
        self.l = l
        self.y = y
        self.x = x
        self.headline = headline

        self.win = curses.newwin(h, l, y, x)
        self.win.erase()
        self.win.box()

        self.win.addstr(1, 2, headline, curses.A_UNDERLINE)
        self.win.refresh()

        panel.new_panel(self.win)
        self.panel = panel.new_panel(self.win)
        self.panel.top()

    def clear (self):
        self.win.erase()
        self.win.box()
        self.win.addstr(1, 2, self.headline, curses.A_UNDERLINE)

    def getwin (self):
        return self.win


# total stats (ports + global)
class Stats():
    def __init__ (self, rpc_client, port_list, interval = 100):

        self.rpc_client = rpc_client

        self.port_list = port_list
        self.port_stats = {}

        self.interval = interval
        self.delay_count = 0

    def get_port_stats (self, port_id):
        if self.port_stats.get(port_id):
            return self.port_stats[port_id]
        else:
            return None

    def query_sync (self):
        self.delay_count += 1
        if self.delay_count < self.interval:
            return

        self.delay_count = 0

        # query global stats

        # query port stats

        rc, resp_list = self.rpc_client.get_port_stats(self.port_list)
        if not rc:
            return

        for i, rc in enumerate(resp_list):
            if rc[0]:
                self.port_stats[self.port_list[i]] = rc[1]


# various kinds of panels

# Server Info Panel
class ServerInfoPanel(TrexStatusPanel):
    def __init__ (self, h, l, y, x, status_obj):

        super(ServerInfoPanel, self).__init__(h, l, y ,x ,"Server Info:")

        self.status_obj = status_obj

    def draw (self):

        if self.status_obj.server_version == None:
            return

        self.clear()

        connection_details = self.status_obj.rpc_client.get_connection_details()

        self.getwin().addstr(3, 2, "{:<30} {:30}".format("Server:",self.status_obj.server_sys_info["hostname"] + ":" + str(connection_details['port'])))
        self.getwin().addstr(4, 2, "{:<30} {:30}".format("Version:", self.status_obj.server_version["version"]))
        self.getwin().addstr(5, 2, "{:<30} {:30}".format("Build:", 
                                                                    self.status_obj.server_version["build_date"] + " @ " + 
                                                                    self.status_obj.server_version["build_time"] + " by " + 
                                                                    self.status_obj.server_version["built_by"]))

        self.getwin().addstr(6, 2, "{:<30} {:30}".format("Server Uptime:", self.status_obj.server_sys_info["uptime"]))
        self.getwin().addstr(7, 2, "{:<30} {:<3} / {:<30}".format("DP Cores:", str(self.status_obj.server_sys_info["dp_core_count"]) + 
                                                                  " cores", self.status_obj.server_sys_info["core_type"]))

        self.getwin().addstr(9, 2, "{:<30} {:<30}".format("Ports Count:", self.status_obj.server_sys_info["port_count"]))

        ports_owned = " ".join(str(x) for x in self.status_obj.rpc_client.get_owned_ports())

        if not ports_owned:
            ports_owned = "None"

        self.getwin().addstr(10, 2, "{:<30} {:<30}".format("Ports Owned:", ports_owned))

# general info panel
class GeneralInfoPanel(TrexStatusPanel):
    def __init__ (self, h, l, y, x, status_obj):

        super(GeneralInfoPanel, self).__init__(h, l, y ,x ,"General Info:")

        self.status_obj = status_obj

    def draw (self):
        pass

# all ports stats
class PortsStatsPanel(TrexStatusPanel):
    def __init__ (self, h, l, y, x, status_obj):

        super(PortsStatsPanel, self).__init__(h, l, y ,x ,"Trex Ports:")

        self.status_obj = status_obj

    def draw (self):

        self.clear()

        owned_ports = self.status_obj.rpc_client.get_owned_ports()
        if not owned_ports:
            self.getwin().addstr(3, 2, "No Owned Ports - Please Acquire One Or More Ports")
            return

        # table header 
        self.getwin().addstr(3, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
            "Port ID", "Tx [pps]", "Tx [bps]", "Tx [bytes]", "Rx [pps]", "Rx [bps]", "Rx [bytes]"))

        # port loop
        self.status_obj.stats.query_sync()

        for i, port_index in enumerate(owned_ports):

            port_stats = self.status_obj.stats.get_port_stats(port_index)

            if port_stats:
                self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^15,} {:^15,} {:^15,} {:^15,} {:^15,} {:^15,}".format(
                    "{0} ({1})".format(str(port_index), self.status_obj.server_sys_info["ports"][port_index]["speed"]),
                    port_stats["tx_pps"],
                    port_stats["tx_bps"],
                    port_stats["total_tx_bytes"],
                    port_stats["rx_pps"],
                    port_stats["rx_bps"],
                    port_stats["total_rx_bytes"]))

            else:
                self.getwin().addstr(5 + (i * 4), 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
                    "{0} ({1})".format(str(port_index), self.status_obj.server_sys_info["ports"][port_index]["speed"]),
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A",
                    "N/A"))

# control panel
class ControlPanel(TrexStatusPanel):
    def __init__ (self, h, l, y, x, status_obj):

        super(ControlPanel, self).__init__(h, l, y, x, "")

        self.status_obj = status_obj

    def draw (self):
        self.clear()

        self.getwin().addstr(1, 2, "'g' - general, '0-{0}' - specific port, 'f' - freeze, 'c' - clear stats, 'p' - ping server, 'q' - quit"
                             .format(self.status_obj.rpc_client.get_port_count() - 1))

        index = 3

        cut = len(self.status_obj.log) - 4
        if cut < 0:
            cut = 0

        for l in self.status_obj.log[cut:]:
            self.getwin().addstr(index, 2, l)
            index += 1

# specific ports panels
class SinglePortPanel(TrexStatusPanel):
    def __init__ (self, h, l, y, x, status_obj, port_id):

        super(SinglePortPanel, self).__init__(h, l, y, x, "Port {0}".format(port_id))

        self.status_obj = status_obj
        self.port_id = port_id

    def draw (self):
        y = 3

        self.clear()

        if not self.port_id in self.status_obj.rpc_client.get_owned_ports():
             self.getwin().addstr(y, 2, "Port {0} is not owned by you, please acquire the port for more info".format(self.port_id))
             return

        # streams
        self.getwin().addstr(y, 2, "Streams:", curses.A_UNDERLINE)
        y += 2

        # stream table header 
        self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
            "Stream ID", "Enabled", "Type", "Self Start", "ISG", "Next Stream", "VM"))
        y += 2

        # streams
        if 'streams' in self.status_obj.snapshot[self.port_id]:
            for stream_id, stream in self.status_obj.snapshot[self.port_id]['streams'].iteritems():
                self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
                    stream_id,
                    ("True" if stream['stream']['enabled'] else "False"),
                    stream['stream']['mode']['type'],
                    ("True" if stream['stream']['self_start'] else "False"),
                    stream['stream']['isg'],
                    (stream['stream']['next_stream_id'] if stream['stream']['next_stream_id'] != -1 else "None"),
                    ("{0} instr.".format(len(stream['stream']['vm'])) if stream['stream']['vm'] else "None")))

                y += 1

        # new section - traffic
        y += 2

        self.getwin().addstr(y, 2, "Traffic:", curses.A_UNDERLINE)
        y += 2

        self.status_obj.stats.query_sync()
        port_stats = self.status_obj.stats.get_port_stats(self.port_id)


        # table header 
        self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
            "Port ID", "Tx [pps]", "Tx [bps]", "Tx [bytes]", "Rx [pps]", "Rx [bps]", "Rx [bytes]"))

        y += 2

        if port_stats:
                self.getwin().addstr(y, 2, "{:^15} {:^15,} {:^15,} {:^15,} {:^15,} {:^15,} {:^15,}".format(
                    "{0} ({1})".format(str(self.port_id), self.status_obj.server_sys_info["ports"][self.port_id]["speed"]),
                    port_stats["tx_pps"],
                    port_stats["tx_bps"],
                    port_stats["total_tx_bytes"],
                    port_stats["rx_pps"],
                    port_stats["rx_bps"],
                    port_stats["total_rx_bytes"]))

        else:
            self.getwin().addstr(y, 2, "{:^15} {:^15} {:^15} {:^15} {:^15} {:^15} {:^15}".format(
                "{0} ({1})".format(str(self.port_id), self.status_obj.server_sys_info["ports"][self.port_id]["speed"]),
                "N/A",
                "N/A",
                "N/A",
                "N/A",
                "N/A",
                "N/A"))

        y += 2

# status object
class TrexStatus():
    def __init__ (self, stdscr, rpc_client):
        self.stdscr = stdscr
        self.log = []
        self.rpc_client = rpc_client

        self.snapshot = self.rpc_client.snapshot()

        # fetch server info
        self.get_server_info()

        # create stats objects
        self.stats = Stats(rpc_client, self.rpc_client.get_owned_ports())

        # register actions
        self.actions = {}
        self.actions[ord('q')] = self.action_quit
        self.actions[ord('p')] = self.action_ping
        self.actions[ord('f')] = self.action_freeze 

        self.actions[ord('g')] = self.action_show_ports_stats

        for port_id in xrange(0, self.rpc_client.get_port_count()):
            self.actions[ord('0') + port_id] = self.action_show_port_generator(port_id)

        
    # all ports stats
    def action_show_ports_stats (self):
        self.add_log_event("Switching to all ports view")
        self.stats_panel = self.ports_stats_panel
        
        return True

    # function generator for different ports requests
    def action_show_port_generator (self, port_id):
        def action_show_port():
            self.add_log_event("Switching panel to port {0}".format(port_id))
            self.stats_panel = self.ports_panels[port_id]

            return True

        return action_show_port

    def action_freeze (self):
        self.update_active = not self.update_active
        self.add_log_event("Update continued" if self.update_active else "Update stopped")

        return True

    def action_quit(self):
        return False

    def action_ping (self):
        self.add_log_event("Pinging RPC server")

        rc, msg = self.rpc_client.ping_rpc_server()
        if rc:
            self.add_log_event("Server replied: '{0}'".format(msg))
        else:
            self.add_log_event("Failed to get reply")

        return True

    def get_server_info (self):

        self.server_version = self.rpc_client.get_rpc_server_version()
        self.server_sys_info = self.rpc_client.get_system_info()


    def add_log_event (self, msg):
        self.log.append("[{0}] {1}".format(str(datetime.datetime.now().time()), msg))

    # control panel
    def update_control (self):
        self.control_panel.clear()

        self.control_panel.getwin().addstr(1, 2, "'g' - general, '0-{0}' - specific port, 'f' - freeze, 'c' - clear stats, 'p' - ping server, 'q' - quit"
                                           .format(self.rpc_client.get_port_count() - 1))

        index = 3

        cut = len(self.log) - 4
        if cut < 0:
            cut = 0

        for l in self.log[cut:]:
            self.control_panel.getwin().addstr(index, 2, l)
            index += 1

    def generate_layout (self):
        self.max_y = self.stdscr.getmaxyx()[0]
        self.max_x = self.stdscr.getmaxyx()[1]

        self.server_info_panel    = ServerInfoPanel(int(self.max_y * 0.3), self.max_x / 2, int(self.max_y * 0.5), self.max_x /2, self)
        self.general_info_panel   = GeneralInfoPanel(int(self.max_y * 0.5), self.max_x / 2, 0, self.max_x /2, self)
        self.control_panel        = ControlPanel(int(self.max_y * 0.2), self.max_x , int(self.max_y * 0.8), 0, self)

        # those can be switched on the same place 
        self.ports_stats_panel    = PortsStatsPanel(int(self.max_y * 0.8), self.max_x / 2, 0, 0, self)

        self.ports_panels = {}
        for i in xrange(0, self.rpc_client.get_port_count()):
            self.ports_panels[i] = SinglePortPanel(int(self.max_y * 0.8), self.max_x / 2, 0, 0, self, i)

        # at start time we point to the main one 
        self.stats_panel = self.ports_stats_panel
        self.stats_panel.panel.top()

        panel.update_panels(); self.stdscr.refresh()
        return 


    def wait_for_key_input (self):
        ch = self.stdscr.getch()

        # no key , continue
        if ch == curses.ERR:
            return True
        
        # check for registered function        
        if ch in self.actions:
            return self.actions[ch]()
        else:
            self.add_log_event("Unknown key pressed, please see legend")

        return True

    # main run entry point
    def run (self):
        try:
            curses.curs_set(0)
        except:
            pass

        curses.use_default_colors()        
        self.stdscr.nodelay(1)
        curses.nonl()
        curses.noecho()
     
        self.generate_layout()

        self.update_active = True
        while (True):

            rc = self.wait_for_key_input()
            if not rc:
                break

            self.server_info_panel.draw()
            self.general_info_panel.draw()
            self.control_panel.draw()

            # can be different kinds of panels
            self.stats_panel.panel.top()
            self.stats_panel.draw()

            panel.update_panels(); 
            self.stdscr.refresh()
            sleep(0.01)


def show_trex_status_internal (stdscr, rpc_client):
    trex_status = TrexStatus(stdscr, rpc_client)
    trex_status.run()

def show_trex_status (rpc_client):

    try:
        curses.wrapper(show_trex_status_internal, rpc_client)
    except KeyboardInterrupt:
        curses.endwin()

def cleanup ():
    try:
        curses.endwin()
    except:
        pass