/*
 * Created on May 11, 2005
 *
 */
package com.kellegous.mas864.clusterss;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTree;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import Jama.Matrix;

import com.kellegous.mas864.clusterss.nlp.WordExtractor;


/**
 * @author knorton
 * Copyright (C) 2005 MIT Media Lab, All Right Reserved.
 */
public class RSSClusterer {    
    private ClusterFrame		guiFrame = null;
    
    public static void main(String[] args) {
        WordExtractor.DO_STEMMING = false;
        WordExtractor.DO_STOP_CHECKING = false;
        new RSSClusterer().run();
    }
    
    private RSSClusterer() {
        this.guiFrame = new ClusterFrame();
    }
    
    private void run() {
        this.guiFrame.show();
        this.loadFeeds(0);
    }

    private void addArticlesToGenerator(Article[] articles) {
        for (int i=0;i<articles.length;i++)
        		this.guiFrame.addArticle(articles[i]);
    }
    
    private void loadFeeds(int index) {
        this.guiFrame.getKernelGenerator().reset();
        
        URL[] 				urls = Feeds.getFeedURLS(index);
        BOWKernelGenerator 	kg = this.guiFrame.getKernelGenerator();
        ClusteringMachine		cm = this.guiFrame.getClusteringMachine();
        
        for (int i=0;i<urls.length;i++) {
            try {
                this.guiFrame.setStatus("Load: "+urls[i]);
                Feed f = Feed.create(urls[i]);
                this.guiFrame.addFeed(f);
                this.addArticlesToGenerator(f.getArticles());
            }
            catch (Throwable t) {
                //Ignore it if something goes awry.
            }
        }
        
        // Tell JTree to update.
        this.guiFrame.clearStatus();
        this.guiFrame.updateUI();
        this.guiFrame.clusterArticles();
    }

    
    class ClusterFrame extends JFrame implements TreeModel, TreeSelectionListener, ClusteringMachineListener, ChangeListener, ActionListener {
    	    private static final          String    title = "RSS Clusters";
    	    private static final			int		 CLUSTERED_NODE = 0;
    		private static final			int		 UNCLUSTERED_NODE = 1;
    		
        private JEditorPane			guiContent = null;
        private JSlider				guiK = null;
        private JTree					guiArticleTree = null;
        private JLabel				guiStatus = null;
        private KernelViewPanel		guiKernel = null;
        private JComboBox             guiFeeds = null;
        
        private ArrayList				feeds = new ArrayList();
        private String				rootTreeNode = "#root";
        private String[]				forkTreeNode = new String[] {
                "Clustered",
                "Unclustered"
        };
        private Cluster[]				clusters = null;
        private ArrayList				articles = null;
        
        private BOWKernelGenerator	kernelGenerator = null;
        private ClusteringMachine		clusterMachine = null;
        
        public ClusterFrame() {
            this.kernelGenerator = new BOWKernelGenerator();
            this.clusterMachine = new ClusteringMachine(
                    this.kernelGenerator);
            this.clusterMachine.addClusteringMachineListener(this);
            
            this.articles = new ArrayList();
            
            this.guiContent = new JEditorPane();
            this.guiK = new JSlider(0,100,10);
            this.guiArticleTree = new JTree(this);
            this.guiStatus = new JLabel();
            this.guiKernel = new KernelViewPanel();
            this.guiFeeds = new JComboBox();
            
            // Config: K Slider
            this.guiK.setPaintLabels(true);
            this.guiK.setPaintTicks(true);
            this.guiK.setMajorTickSpacing(10);
            this.guiK.setMinorTickSpacing(5);
            //this.guiK.setSnapToTicks(true);
            this.guiK.addChangeListener(this);
            
            // Config: Content
            this.guiContent.setContentType("text/plain");
            this.guiContent.setEditable(false);
            
            // Config: Tree
            this.guiArticleTree.setRootVisible(false);
            this.guiArticleTree.setEditable(false);
            this.guiArticleTree.addTreeSelectionListener(this);
            
            // Config: Status
            this.guiStatus.setBorder(
                    BorderFactory.createLoweredBevelBorder());
            
            //Config: Feeds
            String[] lbls = Feeds.getLabels();
            for (int i=0;i<lbls.length;i++)
                this.guiFeeds.addItem(lbls[i]);
            this.guiFeeds.addActionListener(this);
            
            // Layout
            this.getContentPane().setLayout(new BorderLayout());
            
            JPanel guiGauche = new JPanel();
            guiGauche.setPreferredSize(new Dimension(300,500));            
            guiGauche.setLayout(new BorderLayout());
            guiGauche.add(this.guiK,BorderLayout.NORTH);
            guiGauche.add(
                    new JScrollPane(this.guiArticleTree),
                    BorderLayout.CENTER);
            this.getContentPane().add(guiGauche,BorderLayout.WEST);
            
            JPanel guiDroite = new JPanel();
            guiDroite.setPreferredSize(new Dimension(400,500));            
            guiDroite.setLayout(new BorderLayout());
            guiDroite.add(
                    new  JScrollPane(this.guiContent),
                    BorderLayout.CENTER);
            JScrollPane guiKernelScroll = new JScrollPane(
                    this.guiKernel);
            guiKernelScroll.setPreferredSize(new Dimension(400,200));
            guiDroite.add(
                    guiKernelScroll,
                    BorderLayout.NORTH);
            this.getContentPane().add(guiDroite,BorderLayout.CENTER);
          
            this.getContentPane().add(this.guiStatus,BorderLayout.SOUTH);
            this.getContentPane().add(this.guiFeeds,BorderLayout.NORTH);
            this.pack();
        }
        
		public void stateChanged(ChangeEvent e) {
            if (!this.guiK.getValueIsAdjusting())
                new Thread(new Runnable() {
                	    public void run() {
                	    	    clusterArticles();
                	    }
                }).start();
		}
        
        public void clusterArticles() {
            this.guiK.setEnabled(false);
            this.setTitle(title+" (sensitivity="+this.guiK.getValue()+")");
            try {
	            this.kernelGenerator.setTolerance(
	                    this.guiK.getValue());
	        		this.clusters = this.clusterMachine.cluster();
	        		for (int i=0;i<this.clusters.length;i++)
	        			this.clusters[i].setName("Cluster #"+(i+1)+" ["+this.clusters[i].size()+"]");
	        		this.guiKernel.setClusters(this.clusters);
	        		this.guiArticleTree.expandPath(
	                        new TreePath(
	                                new Object[] {
	                                        this.rootTreeNode,
	                                        this.forkTreeNode[CLUSTERED_NODE]}));
	        		this.guiArticleTree.updateUI();
            }
            finally {
                this.guiK.setEnabled(true);
            }
        }
        
        public ClusteringMachine getClusteringMachine() {
            return this.clusterMachine;
        }
        
        public BOWKernelGenerator getKernelGenerator() {
            return this.kernelGenerator;
        }
        
        public void setStatus(String msg) {
            this.guiStatus.setText(msg);
        }
        
        public void clearStatus() {
            this.guiStatus.setText(" ");
        }
        
        public void addArticle(Article a) {
        		this.articles.add(a);
        		this.kernelGenerator.addDataVector(a.getVector());
        }
        
        public void addFeed(Feed feed) {
            this.feeds.add(feed);
        }
        
        public void updateUI() {
            this.guiArticleTree.updateUI();
        }

        /*** ActionListener ***/
        public void actionPerformed(ActionEvent e) {
            this.clusters = null;
            this.feeds.clear();
            this.articles.clear();
            this.guiKernel.reset();
            new Thread(new Runnable() {
                public void run() {
                	    loadFeeds(guiFeeds.getSelectedIndex());
                }
            }).start();
        }
        
        /*** ClusteringMachineListener **/
		public void clusterProcessCompleted(ClusteringMachineEvent e) {
			this.clearStatus();
		}
		
		public void clusterProcessStarted(ClusteringMachineEvent e) {
		}

		public void clusterStatusChanged(ClusteringMachineEvent e) {
			this.setStatus(e.getMessage());
			this.guiStatus.repaint();
		}
        
		public void clusterKernelGenerated(ClusteringMachineEvent e) {
		    final Matrix k = (Matrix)e.getData();
		    new Thread(new Runnable() {
		        public void run() {
		            guiKernel.setKernelMatrix(k);
		        }
		    }).start();
		}
		
        /*** TreeSelectionListener ***/
        public void valueChanged(TreeSelectionEvent e) {
            Object o = this.guiArticleTree.getLastSelectedPathComponent();
            if (o instanceof Article) {
                Article aa = (Article)o;
                this.guiContent.setText(aa.getContents());
            }
            else if (o instanceof Cluster) {
                Cluster c = (Cluster)o;
                ArrayList s = new ArrayList();
                ArrayList r = new ArrayList();
                for (Iterator it=c.get(0,this.articles).getVector().iterator();it.hasNext();)
                    s.add(it.next());
                for (int i=1;i<c.size();i++) {
                    for (Iterator it=s.iterator();it.hasNext();) {
                        String wd = (String)it.next();
                        if (!c.get(i,this.articles).getVector().contains(wd))
                            r.add(wd);
                    }
                    
                    for (Iterator it=r.iterator();it.hasNext();) {
                    	    s.remove(it.next());
                    }
                }
                
                StringBuffer sb = new StringBuffer();
                int i=0;
                for (Iterator it=s.iterator();it.hasNext();) {
                    if ((i++)!=0)
                        sb.append(", ");
                    sb.append(it.next());
                }
                this.guiContent.setText(sb.toString());
            }
        }
        
        /*** TreeModel ***/
        public void addTreeModelListener(TreeModelListener l) {
        }

        public Object getChild(Object parent, int index) {
            if (parent == null)
                return null;
            
            if (parent.equals(this.rootTreeNode))
                return this.forkTreeNode[index];
                
            if (parent.equals(forkTreeNode[CLUSTERED_NODE])) {
            		if (this.clusters == null)
            			return null;
            		return this.clusters[index];
            }
            
            if (parent.equals(forkTreeNode[UNCLUSTERED_NODE]))
                return this.feeds.get(index);
            
            if (parent instanceof Cluster)
            		return ((Cluster)parent).get(index,this.articles);
            
            if (parent instanceof Feed)
                	return ((Feed)parent).getArticles()[index];
            
            return null;
        }
        
        public int getChildCount(Object parent) {
            if (parent == this.rootTreeNode)
                return 2;
            
            if (parent == this.forkTreeNode[CLUSTERED_NODE])
                return (this.clusters==null)?0:this.clusters.length;
            
            if (parent == this.forkTreeNode[UNCLUSTERED_NODE])
                return this.feeds.size();
            
            if (parent instanceof Cluster)
            		return ((Cluster)parent).size();
            
            if (parent instanceof Feed)
                return ((Feed)parent).getArticles().length;
            
            return 0;
        }
        
        public int getIndexOfChild(Object parent, Object child) {
            if (parent ==null || child == null)
                return -1;
            
            if (parent == this.rootTreeNode) {
                if (child==this.forkTreeNode[CLUSTERED_NODE])
                    return 0;
                if (child==this.forkTreeNode[UNCLUSTERED_NODE])
                    return 1;
                return -1;
            }
            
            if (parent == this.forkTreeNode[CLUSTERED_NODE]) {
                if (this.clusters == null)
                    return -1;
                
                for (int i=0;i<this.clusters.length;i++)
                    if (child.equals(this.clusters[i]))
                        return i;
                return -1;
            }
            
            if (parent == this.forkTreeNode[UNCLUSTERED_NODE]) {
                for (int i=0;i<this.feeds.size();i++)
                    if (child.equals(this.feeds.get(i)))
                        return i;
                return -1;
            }
            
            if (parent instanceof Cluster) {
                Cluster c = (Cluster)parent;
                for (int i=0;i<c.size();i++) {
                    if (child.equals(c.get(i,this.articles)))
                        return i;
                }
                return -1;
            }
            
            if (parent instanceof Feed) {
                Article[] a = ((Feed)parent).getArticles();
                for (int i=0;i<a.length;i++)
                    if (child.equals(a[i]))
                        return i;
                return -1;
            }
            
            return -1;
        }
        
        public Object getRoot() {
            return this.rootTreeNode;
        }
        
        public boolean isLeaf(Object node) {
            //TODO: This is not really correct.
            return (node instanceof Article);
        }
        
        public void removeTreeModelListener(TreeModelListener l) {
        }
        
        public void valueForPathChanged(TreePath path, Object obj) {
        }
    }    
}
