#! /usr/bin/env python
###############################################################################
#                                                                             #
#   Copyright 2005 University of Cambridge Computer Laboratory.               #
#                                                                             #
#   This file is part of Nprobe.                                              #
#                                                                             #
#   Nprobe 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.                                       #
#                                                                             #
#   Nprobe 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 Nprobe; if not, write to the Free Software                     #
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
#                                                                             #
###############################################################################


##############################################################################
## 
##
## 
## 
##
## 

############################################################################ 

from Tkinter import *

import Numeric
import os
from tempfile import mktemp
import string

from minmax import MIN, MAX
from nprobe import SERVER, CLIENT
from np_TCP_Window import TCP_Window, TCP_canv, TCP_WDW_HT, TCP_WDW_WI
from  np_longutil import Longstring
from np_packet_markers import *
from np_TCP import TCP_Machine, TCPNoModel, TCPModelPkts, \
     TCPModelNoTrans, trig_str
from TCP_Imp import TCP_Imps
from np_atypes import Atype

from np_widgets import PCan, PListB, BarButton, XSCROLL, YSCROLL, XYSCROLL
import np_grabrec
#from np_grabrec import grab

#from print_col import whoops


############################################################################
############################################################################

ROOT_WI = 600
#ROOT_WI = 500
ROOT_HI = 750
#ROOT_HI = 500
ROOT_WDW_TOOLBAR_HT = 25

ROOT_XPOS = 25
ROOT_YPOS = 25
#ROOT_YPOS = 0

#DISPLAY_XPOS = 500
DISPLAY_XPOS = ROOT_XPOS + ROOT_WI +10

# where to place the windows

RootGeom = '%dx%d+%d+%d' % (ROOT_WI, ROOT_HI, ROOT_XPOS,ROOT_YPOS)
DisplayGeom = '%dx%d+%d+%d' % (TCP_WDW_WI, TCP_WDW_HT, 
			       DISPLAY_XPOS, ROOT_YPOS)

#RootGeom = '%dx%d' % (ROOT_WI, ROOT_HI)
#DisplayGeom = '%dx%d' % (TCP_WDW_WI, TCP_WDW_HT)

############################################################################
############################################################################

# display - externally scoped pointer needed for set Imp callbacks

display = None

############################################################################
############################################################################

class TcpDisplay:

    def __init__(self, connlist, standalone='no', path='', logfun=None, 
		 trace=0):

	if not len(connlist):
	    print 'TCP_Display: empty connection list'
	    return

        if standalone == 'yes':
            self.root = Tk()
        else:
            self.root = Toplevel()
            
	self.root.geometry(RootGeom)
	self.root.minsize(200, 200)
	self.display = Tcp_Disp(connlist, standalone=standalone, 
				master=self.root, 
				path=path, logfun=logfun, trace=trace)
        if standalone == 'yes':
            self.root.mainloop()
        else:
            self.root.wait_window(self.root)

############################################################################
############################################################################

class Tcp_Disp(Frame):

    def __init__(self, connlist, standalone, master = None, path='.', 
		 logfun=None, trace=0, **cnf):
	apply(Frame.__init__, (self, master), cnf)
	self.configure(width=ROOT_WI, height=ROOT_HI)
	self.pack(expand=1, fill=BOTH)

	self.master = master
	self.trace = trace

	# Set up the TCP implementations
	self.TCP_imps = TCP_Imps()
	#print self.TCP_imps

	# create a toolbar
	toolbar = self.toolbar = Frame(self)
	toolbar.configure(bg='grey')
	toolbar.pack(side=TOP, fill=X)

	if len(connlist) > 1:

	    # prev button
	    toolbar.prevb = Button(toolbar, text="Prev", width=6, 
				   command=self.draw_prev_conn)
	    toolbar.prevb.configure(bg='grey', fg='black')
	    toolbar.prevb.pack(side=LEFT, padx=2)
	    toolbar.prevb.config(state=DISABLED)

	    # next button
	    toolbar.nextb = Button(toolbar, text="Next", width=6, 
				   command=self.draw_next_conn)
	    toolbar.nextb.configure(bg='grey', fg='black')
	    toolbar.nextb.pack(side=LEFT, padx=2)

	# TCP implementations menu
	self.make_impmenu(toolbar)

        # Show pkts/transactions
        self.show_what = StringVar()
        
        toolbar.show_whatb = swb = BarButton(self.toolbar, 
				text='Show', bg='gray', 
				relief=RAISED, padx=2).menu
        swb.add_radiobutton(label='Pkts', 
				  command=self.show_what_fun, 
				  variable=self.show_what,
				  value='Pkts', indicatoron=0, 
				  selectcolor='blue')
        swb.add_radiobutton(label='Trans', 
				  command=self.show_what_fun, 
				  variable=self.show_what,
				  value='Trans', indicatoron=0, 
				  selectcolor='blue')
        swb.add_radiobutton(label='Both', 
				  command=self.show_what_fun, 
				  variable=self.show_what,
				  value='Both', indicatoron=0, 
				  selectcolor='blue')
        self.show_what.set('Trans')
        
	
	# quit button
	if standalone == 'yes':
	    quitfun = self.finish
	else:
	    quitfun = self._destroy
	toolbar.quitb = Button(toolbar, text="Quit", width=6, 
			       command=quitfun)
	toolbar.quitb.configure(bg='grey', fg='red')
	toolbar.quitb.pack(side=RIGHT, padx=2)

	# print button
	toolbar.printb = Button(toolbar, text="Print", width=6, 
			       command=self.print_conn)
	toolbar.printb.configure(bg='grey', fg='black')
	toolbar.printb.pack(side=RIGHT, padx=25)

        # grab button
	toolbar.grabb = Button(self.toolbar, text="grab", width=5,
		   command=self.grab)
	toolbar.grabb.configure(bg='grey')
	toolbar.grabb.pack(side=LEFT, padx=2)

	# Pkt info display
        self.canv = ConnPktInfo(self, ROOT_HI-ROOT_WDW_TOOLBAR_HT)

	# Conn/pkt info display
        self.lb = ConnTransInfo(self)

        self.lb.pack(side=TOP, fill=BOTH, expand=1)
        
        self.transdata = None

	self.connlist = connlist
	self.conn_indx = 0
	self.path = path # where the rep file is
	self.logfun = logfun # to write any logged stuff

	self.display = None
	self.draw_next_conn() # Draw the first conn

############################################################################

    def finish(self):
	#print 'Finish called'
	sys.exit(0)

############################################################################

    def _destroy(self):

	if self.display:
	    self.display.destroy()
	    del(self.display)
	self.master.destroy()

############################################################################

    def grab(self):

        #print self.clist[self.indx].FileRec
        #np_grabrec.grab(self.curr_conn.FileRec)
        np_grabrec.grab(self.curr_conn)

############################################################################

    def show_what_fun(self):

        show_what = self.show_what.get()
        show_pkts = (show_what == 'Pkts') or (show_what == 'Both')
        show_trans = (show_what == 'Trans') or (show_what == 'Both')

        self.canv.pack_forget()
        self.lb.pack_forget()
            
        if show_pkts:
            self.canv.pack(side=TOP, fill=BOTH, expand=1)
            
        if show_trans:
            self.lb.pack(side=TOP, fill=BOTH, expand=1)
        elif not show_pkts:
            self.canv.pack(side=TOP, fill=BOTH, expand=1)
            self.show_pkts.set(1)
            
	

############################################################################ 

    def message(self, str):

	canv = self.canv
	canv.delete(ALL)
	canv.create_text(5, 5, fill='white', anchor=NW, text=str,  font=("helvetica", 8))
	
############################################################################

    def set_titlebar(self, conn):

	tstr = 'Packet list for connection #%d' % (conn.id)
	self.winfo_toplevel().title(tstr)
	

############################################################################

    def draw_next_conn(self):
	

	if len(self.connlist) > 1 and self.conn_indx >= len(self.connlist):
	    self.message('All connections drawn')
	    self.toolbar.nextb.config(state=DISABLED)
	    return

	#print len(self.connlist)

	#print self.connlist
	self.curr_conn = conn = self.connlist[self.conn_indx]
	self.set_titlebar(conn)
	if self.display != None: #not the first
	    self.display.clear_canvasses()
	    self.toolbar.prevb.config(state=NORMAL)
	#conn.make_clusters()
	if self.trace or 1:
	    self.canv.list_pkts(conn, 1)
            self.lb.transdata = None
            self.lb.list_trans(conn)
	self.TCP_imps.set_start()
        conn.logfun = self.logfun
        conn.model(self.TCP_imps, None)

        if self.trace:
            if conn.modelled:
                extend = 'true'
            else:
                extend = 'false'
            self.canv.list_pkts(conn, 1, extend=extend)
        if self.display != None: #not the first
            self.display.draw_conn(conn)
        else:
            self.display = TCP_Window(conn, self, self)
            self.display.geometry(DisplayGeom)
                    
	self.conn_indx = self.conn_indx + 1



############################################################################

    def draw_prev_conn(self):
	
	if self.conn_indx < 2:
	    self.toolbar.prevb.config(state=DISABLED)
	    #self.message('First connection shown')
	    return

	self.conn_indx = self.conn_indx - 2

	self.curr_conn = conn = self.connlist[self.conn_indx]

	self.set_titlebar(conn)
	self.display.clear_canvasses()
	if conn.modelled:
	    self.display.draw_conn(conn)
	self.set_titlebar(conn)
	self.canv.list_pkts(conn, conn.modelled, extend=conn.modelled)
        self.lb.transdata = None
	self.lb.list_trans(conn)
	self.toolbar.nextb.config(state=NORMAL)
	self.conn_indx = self.conn_indx + 1

############################################################################

    def re_draw(self):

	self.clear()
	conn = self.curr_conn
	t = TCP_Machine(conn, self.TCP_imps, self.logfun, trace=self.trace)
	try:
	    simp, cimp = t.do_pkts()
	    conn.modelled = 1
	    self.add_imps(simp, cimp)
	    self.display.draw_conn(conn)	
	except TCPNoModel:
	    pass
	

############################################################################

    def print_conn(self):

	cm = [0.0, 0.0, 0.0, 'green']

## 	canv = self.display.canvasses[self.display.canvas_curr].canv

## 	canv.postscript(file='./foo')

	conn = self.curr_conn

	if not conn.modelled:
	    return

	indx1 = self.display.canvasses[self.display.canvas_curr].indx1
	indx2 = self.display.canvasses[self.display.canvas_curr].indx2

	connstr = '#%d' % (conn.id)
	if indx1 != 0 or indx2 != len(conn.pktlist)-1:
	    indxstr = '.%d-%d' % (indx1, indx2)
	else:
	    indxstr = ''
	suffstr = '.ps'

	path = self.path + connstr + indxstr + suffstr


	d = TCP_canv(self.display, conn, conn.pktlist, 
			indx1, indx2, 550, 800, 0, 0, 
			bgcol='white', fgcol='black')
	d.canv.postscript(file=path, width=600, height=900, 
			  colormode='color', colormap=cm, 
			  pageheight='250m')

	d.canv.place_forget()
	del(d.canv)
	del(d)

	print 'printing %s' % (path)
	

############################################################################

    def clear(self):
	self.display.clear_canvasses()
	print 'cleared'
	

############################################################################

    def change_simp(self):
	print 'change_simp val %s' % (self.simp_curr.get())
	self.TCP_imps.simp_curr = self.TCP_imps.imps_dict[self.simp_curr.get()]

	self.re_draw()
	
	

############################################################################

    def change_cimp(self):
	print 'change_cimp val %s' % (self.cimp_curr.get())
	self.TCP_imps.cimp_curr = self.TCP_imps.imps_dict[self.cimp_curr.get()]

	self.re_draw()
	

############################################################################

    def make_impmenu(self, master):

	self.toolbar.ibar = Frame(self.toolbar, name='bar', relief=FLAT,
			 borderwidth=0, bg='gray')
	self.toolbar.ibar.pack(side=LEFT)
	self.simpbm = BarButton(self.toolbar.ibar, 
				text='Server-Imp', bg='gray', 
				relief=RAISED, padx=2).menu
	self.cimpbm = BarButton(self.toolbar.ibar, 
				text='Client-Imp', bg='gray', 
				relief=RAISED, padx=2).menu
	smenu = self.simpbm
	cmenu = self.cimpbm

 	self.simp_curr = StringVar(self)
 	self.simp_curr.set(self.TCP_imps.simp_curr)
 	self.cimp_curr = StringVar(self)
 	self.cimp_curr.set(self.TCP_imps.cimp_curr)

	ilist = self.TCP_imps.imps_dict.values()
	for i in ilist:
	    name = i.sig
	    smenu.add_radiobutton(label=name, 
				  command=self.change_simp, 
				  variable=self.simp_curr,
				  value=name, indicatoron=1, 
				  selectcolor='blue')
	    
	    cmenu.add_radiobutton(label=name, 
				 command=self.change_cimp, 
				  variable=self.cimp_curr,
				 value=name, indicatoron=1,
				 selectcolor='blue')

	    if name == 'Auto':
		smenu.add_separator()
		cmenu.add_separator()

############################################################################

    def imp_menus_add(self, imp):
	    
	self.simpbm.add_radiobutton(label=imp.sig, 
				    command=self.change_simp, 
				    variable=self.simp_curr,
				    value=imp.sig, indicatoron=1, 
				    selectcolor='blue')
	    
	self.cimpbm.add_radiobutton(label=imp.sig, 
				    command=self.change_cimp, 
				    variable=self.cimp_curr,
				    value=imp.sig, indicatoron=1, 
				    selectcolor='blue')
	

############################################################################

    def add_imps(self, simp, cimp):

	for i in [simp, cimp]:
	    if i != None and not self.TCP_imps.imp_stored(i):
		self.TCP_imps.store(i)
		self.imp_menus_add(i)

############################################################################
############################################################################

class ConnTransInfo(PListB):
    """
    A listbox subclass displaying connection/transaction info.
    """
    def __init__(self, master):
        """
        __init__(self, master)
        Initialize a new instance.

        """

        PListB.__init__(self, master, XYSCROLL)
        
	self.config(bg='white')
	#self.ht = canv_ht
        
        self.expand_what = {}
        self.transdata = None
        self.bind("<Double-Button-1>", self.expand)
        
        return
	

############################################################################

    def list_trans(self, conn):
        """
        list_trans(self, conn)
        """
        
	self.delete(0, END)


        if self.transdata == None:
            # first time - get the data
            self.get_trans_data(conn)

        for s in self.transdata:
            #print s
            self.insert(END, s[0])
            try:
                if self.expand_what[s[0]] == 1:
                    for t in s[1]:
                        self.insert(END, t[0])
                        try:
                           if self.expand_what[t[0]] == 1:
                               for l in t[1]:
                                   self.insert(END, l)
                        except KeyError:
                            pass
            except KeyError:
                pass

        self.insert(END, '')
        self.insert(END, '  --- End ---')
        self.insert(END, '')
        
        return
	

############################################################################

    def get_trans_data(self, conn):
        """
        get_trans_data(self, conn)
        """

        tempdir = '/tmp'
        fnm = mktemp('.%d.trans_data' % (conn.id))
        #print fnm
        conn.printself_with_trans(fnm=fnm)

        try:
            f = open(fnm, 'r')
            #print 'opened', fnm
        except IOError, s:
            print 'Couldn\'t open file %s for read: %s' % (fnm, s)
            sys.exit(1)

        self.transdata = td = []
        self.expand_what = {}
        line = 0
        in_trans = 0
        in_links = 0
	while 1:
	    s = f.readline()
	    line = line +1
	    if not len(s):
		break
	    if s[0] =='\n':
		continue
            s = s[:-1]
            if not in_trans:
                p = string.find(s, ' transactions ')
                if p != -1 and string.find(s, ' dummy)') != -1:
                    in_trans += 1
                    td.append(('', []))
                td.append((s, []))
            else:
               if string.find(s, '  Transaction #') == 0:
                   tt = []
                   td.append(('', []))
                   td.append((s, tt))
                   self.expand_what[s] = 0
                   in_links = 0
               else:
                   if not in_links:
                       if string.find(s, '  References') == 0:
                           lt = []
                           tt.append(('', []))
                           tt.append((s, lt))
                           self.expand_what[s] = 0
                           in_links = 1
                       else:
                           tt.append((s, []))
                   else:
                       lt.append(s)

        os.remove(fnm)
        return
	

############################################################################

    def expand(self, ev):
        """
        expand(self, ev)
        """

        items = self.curselection()
        try:
            items = map(string.atoi, items)
        except ValueError: pass

        key = self.get(items[0])
        try:
            self.expand_what[key] = not self.expand_what[key]
            self.list_trans(None)
        except KeyError:
            pass
        
        return

############################################################################
############################################################################

class ConnPktInfo(PCan):
    """
    Display connection packet info

    """
    def __init__(self, master, ht):
        """
        __init__(self)
        Initialize a new instance.
        """

        PCan.__init__(self, master, XYSCROLL)
        self.ht = ht
        #self.pack(side=TOP, fill=BOTH, expand=1)
        self.config(bg='white')

        return
	

############################################################################

    def pkt_txt(self, p, y, extend = 'false', fold = 'false', scol=None, 
            ccol=None, canv=None):
        """
        pkt_txt(self)
        """

        if canv == None:
            canv = self

        TEXT = canv.create_text
        way = p.dir
        flags = p.flags
        if flags & TH_ACK:
            ackstr = 'ack%s' % (Longstring(p.ack, width=10))
        else:
            ackstr = ''
        if way == SERVER:
            dir = '<'
            if scol == None:
                col = 'darkblue'
            else:
                col = scol
        else:
            dir = '>'
            col = 'darkgreen'

        flagstr = ''
        if flags & TH_SYN:
            flagstr = flagstr+'S'
        if flags & TH_ACK:
            flagstr = flagstr+'A'
        if flags & TH_FIN:
            flagstr = flagstr+'F'
        if flags & TH_RST:
            flagstr = flagstr+'R'
        if flags & TH_PUSH:
            flagstr = flagstr+'P'
        if flags & TH_URG:
            flagstr = flagstr+'U'

        tstr = string.rjust('%.3f' % (p.tm/1000.0), 9)

        str = '%3d %s %s %s +%4d %s w:%5d %-5s' % (p.indx, tstr, dir, 
                                      Longstring(p.seq, width=10),
                                      p.len, ackstr,
                                      p.window, flagstr)

        if fold == 'true':
            istr = ''
        else:
            istr = '%d ' % (p.indx)

        if extend == 'true':
            extstr = '%s' % (trig_str(p))
            if p.prtt or p.trtt or p.delay:
                if (p.trig & TRIG_RTT):
                    obkt = ''
                    cbkt = ''
                else:
                    obkt = '('
                    cbkt = ')'
                extstr = extstr + ' %s' % (istr)
            if p.prtt:
                extstr = extstr + ' %sprtt=%.3f%s' % (obkt, p.prtt/1000.0, 
                                                      cbkt)

            if p.trtt:
                extstr = extstr + ' trtt=%.3f ' % (p.trtt/1000.0)

            if p.delay:
                extstr = extstr + ' delay=%.3f ' % (p.delay/1000.0)

            if fold == 'true' and len(extstr):
                extstr = '\n\t' + extstr

            str = str + extstr

        #t=TEXT(0, y, fill=col, anchor=NW, text=str,  font=("helvetica", 10))
        t=TEXT(0, y, fill=col, anchor=NW, text=str,  font=("-*-helvetica-bold-r-*-*-12-*-*-*-*-*-*-*"))

        return canv.bbox(t)
	

############################################################################

    def list_pkts(self, conn, ok, extend = 'false'):
        """
        list_pkts(self)
        """
        
	self.delete(ALL)

	plist = conn.pktlist
	
	x = ROOT_WI
	y2 = 0

	for p in plist:
	    x1, y1, x2, y2 = self.pkt_txt(p, y2, extend)

	    x = MAX(x, x2+15)
	    y = MAX(self.ht, y2+25)

	if not ok:
	    t = self.create_text(0, y2+25, fill='red', anchor=NW, 
				 font=("helvetica", 8),
		    text='Can\'t model this connection')
	    bb = self.bbox(t)
	    x = MAX(x, bb[2]+15)
	    y = MAX(self.ht, bb[3]+25)
	
	self.config(scrollregion=(0,0,x,y))
        
        return













