class CitationStream(Tkinter.Tk):
    '''
    This class is responsible for sending citation edge streams (loaded by using DataLoader)
    to Gephi's Master server by using GephiJsonClient.
    '''
    
    def __init__(self, url = 'http://localhost:8080/workspace0', filename='Citation_Stream', on_gui = True):
        self.url = url;
        self.filename = filename;
        self.loader = DataLoader();     # create an instance of DataLoader
        self.loader.loadData(self.filename);    # load data from file
        self.g = GephiJsonClient(url=self.url);     # create an instance of GephiJsonClient
        self.g.cleanAll();
        self.degree_dict = {};
        self.cited_dict = {};
        
        # elemts for GUI
        self.is_run = True;
        self.is_gui = on_gui;
        if self.is_gui == True:
            self.initializeUI();
    
    
    def run(self):
        self.loader.flush();
        self.g.cleanAll();
        self.degree_dict.clear();
        self.cited_dict.clear();
        self.streamIn(IN_THRESHOLD , OUT_THRESHOLD, BATCH_SPEED);
        
    
    def runForever(self):
        i = 1;
        while self.is_run:
            print "ROUND #", i;
            self.run();
            print "Waiting 10 seconds for the next round...";
            if self.is_gui == True:
                self.date_txt.set("Waiting 10 seconds for the next round...");
                
            time.sleep(10);  
            i += 1;
  
              
    def streamIn(self, in_threshold = 10, out_threshold = 30, timeout=1):
        '''
        Feed fetch batch into Gephi's pool.
        The maximum number of nodes displayed in the pool is set to be 1000.
        The nodes with in-degree >= in_threshold or out-degree >= out_threshold 
        will be highlighted using different colors and sizes. 
        timeout will be the time for sleeping between two batches.
        '''
        displayed_nodes = deque(maxlen=1000);    # create a queue and set its size 1000. There are 1000 nodes will be displayed in Gephi's pool
        displayed_dict = {};    # store nodes which are currently existing in Gephi's pool
        
        while True:
            tm,edgeset = self.loader.sendData();
            if tm == -1 or edgeset == None:
                break;
            
            print "Batch: ", tm;
            if self.is_gui == True:
                self.date_txt.set(tm);
                
            for fromnode,tonode in edgeset:
                # update the cited list of the tonode
                self.cited_dict[tonode] = self.cited_dict.get(tonode,[]);
                self.cited_dict[tonode].append(fromnode);
                
                # update degrees
                if self.degree_dict.get(fromnode) == None:
                    self.degree_dict[fromnode] = [0,1, tm];  # [in-degree, out-degree, date]
                else:
                    self.degree_dict[fromnode][1] += 1;
                if self.degree_dict.get(tonode) == None:
                    self.degree_dict[tonode] = [1,0, tm];    # [in-degree, out-degree, date]
                else:
                    self.degree_dict[tonode][0] += 1;
                
                # add the fromnode to Gephi's pool
                node_attributes = NODE_ATTRIBUTE.copy();
                if displayed_dict.get(fromnode) == None:
                    displayed_dict[fromnode] = fromnode;
                    # check the size
                    if len(displayed_nodes) >= displayed_nodes.maxlen:
                        deletenode = displayed_nodes.popleft();
                        del displayed_dict[deletenode];
                        self.g.deleteNode(deletenode);  # delete the node from Gephi's pool
                    self.g.addNode(fromnode, **node_attributes);
                    displayed_nodes.append(fromnode);
                    
                # check fromnode's in-degree, and update it in Gephis' pool
                if self.degree_dict[fromnode][1] >= out_threshold:
                    sz = setSize(self.degree_dict[fromnode][1]);
                    node_attributes['size'] = sz;
                    node_attributes['r'] = 0.0/255;
                    node_attributes['g'] = 200.0/255;
                    node_attributes['b'] = 0.0/255;
                    self.g.changeNode(fromnode, **node_attributes);
                    
                # check tonode's out-degree, and update it in Gephi's pool
                node_attributes = NODE_ATTRIBUTE.copy();
                if displayed_dict.get(tonode) == None:
                    if str(int(self.degree_dict[tonode][2][0:4])+5)+self.degree_dict[tonode][2][4:7] >= tm[0:7]:
                        displayed_dict[tonode] = tonode;
                        # check the size
                        if len(displayed_nodes) >= displayed_nodes.maxlen:
                            deletenode = displayed_nodes.popleft();
                            del displayed_dict[deletenode];
                            self.g.deleteNode(deletenode);
                        if self.degree_dict[tonode][0] >= in_threshold and self.degree_dict[tonode][1] >= out_threshold:
                            sz = setSize(self.degree_dict[tonode][0]);
                            node_attributes['size'] = sz;
                            node_attributes['r'] = 0.0/255;
                            node_attributes['g'] = 0.0/255;
                            node_attributes['b'] = 100.0/255;
                        elif self.degree_dict[tonode][0] >= in_threshold:
                            sz = setSize(self.degree_dict[tonode][0]);
                            node_attributes['size'] = sz;
                            node_attributes['r'] = 200.0/255;
                            node_attributes['g'] = 0.0/255;
                            node_attributes['b'] = 0.0/255;
                        self.g.addNode(tonode, **node_attributes);
                        displayed_nodes.append(tonode);
                        # connect the tonode to those nodes that cite it and already in Gephi's pool
                        for eachcit in self.cited_dict[tonode]:
                            if displayed_dict.get(eachcit) != None:
                                self.g.addEdge(str(eachcit+"->"+tonode), eachcit, tonode, directed=True);
                else:
                    if self.degree_dict[tonode][0] >= in_threshold and self.degree_dict[tonode][1] >= out_threshold:
                        sz = setSize(self.degree_dict[tonode][0]);
                        node_attributes['size'] = sz;
                        node_attributes['r'] = 0.0/255;
                        node_attributes['g'] = 0.0/255;
                        node_attributes['b'] = 100.0/255;
                    elif self.degree_dict[tonode][0] >= in_threshold:
                        sz = setSize(self.degree_dict[tonode][0]);
                        node_attributes['size'] = sz;
                        node_attributes['r'] = 200.0/255;
                        node_attributes['g'] = 0.0/255;
                        node_attributes['b'] = 0.0/255;
                    self.g.changeNode(tonode, **node_attributes);         
                self.g.addEdge(str(fromnode+"->"+tonode), fromnode, tonode, directed=True);
            
            # sleep for seconds if one timestamp is done
            time.sleep(timeout);  
        
        # clear
        displayed_nodes.clear();
        displayed_dict.clear();
         
                       
    def clearData(self):
        '''
        Clear up.
        '''
        self.loader.clearData();
        self.degree_dict.clear();
        self.cited_dict.clear();
        
    
    #----------------------
    # Functions for the UI
    #----------------------
    def initializeUI(self):
        '''
        Initialize the components needed in the UI.
        '''
        Tkinter.Tk.__init__(self);  
        self.title("Dynamic Citation Network");                
        self.date_txt = Tkinter.StringVar();
            
        # part 1
        self.intro_lf = Tkinter.LabelFrame(self, text="INTRODUCTION", height=200, width=150);
        self.intro_lf.pack(fill=Tkinter.BOTH, expand=1);
        intro_lbl = Tkinter.Label(self.intro_lf, text=INTRO_INFO, wraplength=400, justify=Tkinter.LEFT, padx=10, pady=10);
        intro_lbl.pack(side=Tkinter.LEFT, expand=1);
        space_lbl_1 = Tkinter.Label(self, text="");
        space_lbl_1.pack(fill=Tkinter.BOTH, expand=1);
        #part 2
        self.legnd_lf = Tkinter.LabelFrame(self, text="LEGEND", height=200, width=150);
        self.legnd_lf.pack(fill=Tkinter.BOTH, expand=1);
        legnd_cvs = Tkinter.Canvas(self.legnd_lf, height=200, width=150);
        legnd_cvs.pack(fill=Tkinter.BOTH, expand=1);
        legnd_cvs.create_text(10,2,anchor=Tkinter.NW, text="Node Color:");
        legnd_cvs.create_oval(15,22,30,37,fill="gray"); legnd_cvs.create_text(40,22,text="an ordinary paper",anchor=Tkinter.NW);
        legnd_cvs.create_oval(15,42,30,57,fill="green"); legnd_cvs.create_text(40,42,text="a paper cites >= "+str(OUT_THRESHOLD)+" papers",anchor=Tkinter.NW);
        legnd_cvs.create_oval(15,62,30,77,fill="blue"); legnd_cvs.create_text(40,62,text="a paper cites >= "+str(OUT_THRESHOLD)+" papers and cited by >= "+str(IN_THRESHOLD)+" papers",anchor=Tkinter.NW);
        legnd_cvs.create_oval(15,82,30,97,fill="red"); legnd_cvs.create_text(40,82,text="a paper cited by >= "+str(IN_THRESHOLD)+" papers",anchor=Tkinter.NW);
        legnd_cvs.create_text(10,112,anchor=Tkinter.NW, text="Node Size:");
        legnd_cvs.create_text(15, 132, text="large:", anchor=Tkinter.NW); legnd_cvs.create_text(75,132,text="in-degree(out-degree) >= 50", anchor=Tkinter.NW);
        legnd_cvs.create_text(15, 152, text="medium:", anchor=Tkinter.NW); legnd_cvs.create_text(75,152,text="in-degree(out-degree) >= 30", anchor=Tkinter.NW);
        legnd_cvs.create_text(15, 172, text="small:", anchor=Tkinter.NW); legnd_cvs.create_text(75,172,text="in-degree(out-degree) >= 10", anchor=Tkinter.NW);
        space_lbl_1 = Tkinter.Label(self, text="");
        space_lbl_1.pack(fill=Tkinter.BOTH, expand=1);
        
        #part 3
        self.date_lf = Tkinter.LabelFrame(self, text="CURRENT DATE", height=200, width=150);
        self.date_lf.pack(fill=Tkinter.BOTH, expand=1);
        self.date_txt = Tkinter.StringVar();
        date_lbl = Tkinter.Label(self.date_lf, textvariable=self.date_txt, padx=10);
        date_lbl.pack(side=Tkinter.LEFT);
        space_lbl_1 = Tkinter.Label(self, text="");
        space_lbl_1.pack(fill=Tkinter.BOTH, expand=1);
        #part 4
        self.btn_lf = Tkinter.LabelFrame(self, text="", height=100, width=150);
        self.btn_lf.pack(fill=Tkinter.BOTH);
        self.is_run = True;
        self.start_btn = Tkinter.Button(self.btn_lf, text="START", command = self.pressStart);
        self.start_btn.pack(side=Tkinter.LEFT);
        self.quit_btn = Tkinter.Button(self.btn_lf, text="QUIT", command = self.pressQuit);
        self.quit_btn.pack(side=Tkinter.RIGHT);
        
        
    def pressStart(self):
        '''
        Function triggered by clicking 'START' button.
        It will start a new thread to simulate the streaming fashion.
        '''
        thread.start_new(self.runForever, ());
        self.start_btn['state'] = Tkinter.DISABLED;
    
    
    def pressQuit(self):
        '''
        Function triggered by clicking 'QUIT' button.
        It will stop running application and quit it.
        '''
        self.is_run = False;
        self.quit();