package org.khelekore.jsizer;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;

/** Main gui of JSizer */
public class JSizer {
    private JFrame f;
    private JTextArea logArea;
    private Set<File> files = new HashSet<File> ();
    private List<ClassSize> classSizes = new ArrayList<ClassSize> ();
    private SizePanel sizePanel = new SizePanel (classSizes);

    public void buildGui () {
	f = new JFrame ("JSizer");
	f.setDefaultCloseOperation (WindowConstants.EXIT_ON_CLOSE);
	JMenuBar menubar = new JMenuBar ();
	f.setJMenuBar (menubar);
	JMenu fileMenu = new JMenu ("File");
	menubar.add (fileMenu);
	
	Action addJarAction = new AddJarAction (this, f);
	addJarAction.putValue (Action.ACCELERATOR_KEY, 
			       KeyStroke.getKeyStroke(KeyEvent.VK_O, 
						      ActionEvent.CTRL_MASK));	
	fileMenu.add (addJarAction);
	
	Action runAction = new RunAction (this);
	runAction.putValue (Action.ACCELERATOR_KEY, 
			    KeyStroke.getKeyStroke(KeyEvent.VK_R, 
						   ActionEvent.CTRL_MASK));
	fileMenu.add (runAction);
	fileMenu.addSeparator ();
	Action pngAction = new PNGAction (sizePanel, f);
	fileMenu.add (pngAction);
	Action pdfAction = new PDFAction (sizePanel, f);
	fileMenu.add (pdfAction);
	Action svgAction = new SVGAction (sizePanel, f);
	fileMenu.add (svgAction);
	fileMenu.addSeparator ();

	Action quitAction = new CloseAction (f);
	quitAction.putValue (Action.ACCELERATOR_KEY, 
			     KeyStroke.getKeyStroke(KeyEvent.VK_Q, 
						    ActionEvent.CTRL_MASK));
	fileMenu.add (quitAction);

	JMenu editMenu = new JMenu ("Edit");
	menubar.add (editMenu);
	
	Action setFilterAction = new SetFilterAction (f, sizePanel);
	setFilterAction.putValue (Action.ACCELERATOR_KEY, 
				  KeyStroke.getKeyStroke(KeyEvent.VK_F, 
							 ActionEvent.CTRL_MASK));
	editMenu.add (setFilterAction);
	Action setClassNameFilter = new SetClassNameFilter (f, sizePanel);
	editMenu.add (setClassNameFilter);
	
	JMenu toolsMenu = new JMenu ("Tools");
	menubar.add (toolsMenu);
	
	Action colorAction = new ColorAction (f, sizePanel);
	toolsMenu.add (colorAction);

	f.add (sizePanel, BorderLayout.CENTER);
	logArea = new JTextArea (5, 80);
	logArea.setEnabled (false);
	logArea.append ("JSizer started");
	JScrollPane sp = new JScrollPane (logArea);
	f.add (sp, BorderLayout.SOUTH);
	f.validate ();
	f.pack ();
	f.setLocationRelativeTo (null);
    }

    public void show () {
	f.setVisible (true);
    }

    public void addJarFile (File f, boolean runSizer) {
	if (files.add (f)) {
	    log ("jar file: " + f.getName () + " added");
	    if (runSizer)
		runSizer (Collections.singleton (f), false);
	}
    }

    public void runSizer () {
	runSizer (files, true);
    }

    public void runSizer (final Set<File> files, 
			  final boolean replaceCurrentSet) {
	if (EventQueue.isDispatchThread ())
	    new Thread (new Runnable () {
		    public void run () {
			runSizerOffEDT (files, replaceCurrentSet);
		    }
		}).start ();
	else 
	    runSizerOffEDT (files, replaceCurrentSet);
    }

    public void setCursor (final Integer type) {
	EventQueue.invokeLater (new Runnable () {
		public void run () {
		    if (type != null)
			f.setCursor (Cursor.getPredefinedCursor (type));
		    else 
			f.setCursor (null);
		};
	    });
    }

    private void runSizerOffEDT (Set<File> files, boolean replaceCurrentSet) {
	setCursor (Cursor.WAIT_CURSOR);
	if (replaceCurrentSet)
	    clearStats ();
	long start = System.currentTimeMillis ();
	ScanProgressDialog spd = new ScanProgressDialog (f, files.size ());
	try {
	    for (File f : files)
		runSizer (f, spd);
	} catch (IOException e) {
	    e.printStackTrace ();
	} finally {
	    spd.dispose ();
	}
	long end = System.currentTimeMillis ();
	double diff = (end - start) / 1000.0;
	log (String.format ("Class inspection took: %.3f", diff));	
	showResult ();
	setCursor (null);
    }

    private void runSizer (File f, ScanProgressDialog spd) 
	throws IOException {
	if (!f.exists ()) {
	    log ("File '" + f + "' does not exist");
	    return;
	}
	URL[] urls = new URL[] {f.toURI ().toURL ()};
	URLClassLoader cl = new URLClassLoader (urls);

	ClassSizer cs = new AsmSizer ();
	cs.setup (cl);

	JarFile jf = new JarFile (f);
	spd.nextFile (f.getName (), jf.size ());
	Enumeration<JarEntry> e = jf.entries ();
	while (e.hasMoreElements ()) {
	    spd.nextEntry ();
	    JarEntry je = e.nextElement ();
	    String name = je.getName ();
	    if (name.toLowerCase ().endsWith (".class")) {
		String cname = name.substring (0, name.length () - 6);
		cname = cname.replace ('/', '.');
		log ("checking class: " + cname);
		ClassSize s = cs.size (jf, je, cname, this);
		if (s != null)
		    classSizes.add (s);		    
	    }
	}
    }

    private void clearStats () {
	classSizes.clear ();
    }

    private void showResult () {
	sizePanel.setClassSizes (classSizes);
    }

    public void log (final String s) {
	if (EventQueue.isDispatchThread ())
	    logSafe (s);
	else 
	    EventQueue.invokeLater (new Runnable () {
		    public void run () {
			logSafe (s);
		    }
		});
    }

    private void logSafe (String s) {
	logArea.append ("\n");
	logArea.append (s);
	int length = logArea.getDocument ().getLength ();
	logArea.setCaretPosition (length);
    }
}
