package eworks.eRQL.gui;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.prefs.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.MouseInputAdapter;
import javax.swing.filechooser.FileFilter;
import edu.stanford.ejalbert.BrowserLauncher;
import eworks.eRQL.engine.*;
import eworks.RDF.model.Tuple;
import eworks.RDF.model.UriLiteralValue;
import eworks.RQL.engine.*;

/**
 * <p>GUI for {@link eRqlEngine} and {@link RqlEngine} - an <em>eRQL</em> and
 * <em>RQL</em> processor.
 * Details on <em>eRQL</em> can be obtained at 
 * <a href="http://www.dbis.informatik.uni-frankfurt.de/~tolle/RDF/eRQL/" target="_blank">http://www.dbis.informatik.uni-frankfurt.de/~tolle/RDF/eRQL/</a>
 * and
 * <a href="http://www.wleklinski.de/rdf/" target="_blank">http://www.wleklinski.de/rdf/</a>.
 * Details on RDF are available at
 * <a href="http://www.w3.org/RDF/" target="_blank">http://www.w3.org/RDF/</a>.</p>
 * 
 * @author Fabian Wleklinski (<a href="mailto:fabian@wleklinski.de">fabian@wleklinski.de</a>)
 * @version 1.00 (2003-11-03)
 */
public class Gui extends JFrame {
	
	/////////////////////////////////////////////////////////////////////////
	// constants
	/////////////////////////////////////////////////////////////////////////

	private final static int MODE_ERQL = 0; 
	private final static int MODE_RQL = 1; 

	/////////////////////////////////////////////////////////////////////////
	// fields
	/////////////////////////////////////////////////////////////////////////

	// rdf source file
	// private File sourceFile = null; 
	
	private eRqlEngine eRQL = null;
	private RqlEngine RQL = null;
	
	// user specific preferences node of this application
	private Preferences prefs = null;

	// output-stream for VRP's error- and debug-messages
	private java.io.ByteArrayOutputStream out;

	/////////////////////////////////////////////////////////////////////////
	// templates
	/////////////////////////////////////////////////////////////////////////
	
	// border template for JTextField- and JTextArea-instances
	private Border JTextFieldBorder = null; 

	/////////////////////////////////////////////////////////////////////////
	// controls
	/////////////////////////////////////////////////////////////////////////

	// button for closing the form
	private JButton quitButton;
	
	// panel including the upper area, itself including fileAreaPanel + queryTabbedPane
	private JPanel topPanel;
	
	// tabbed pane including the RQL- and eRQL query panels
	private JTabbedPane queryTabbedPane;
	
	// panel including the file area
	private JPanel fileAreaPanel;
	
	// label showing application information
	private JLabel appinfoLabel;
	
	// text field for showing + entering the input file's name
	private JTextField fileNameTextField;
	
	// caption for fileNameTextField
	private JLabel fileNameTextFieldLabel;
	
	// panel including the eRQL query area
	private JPanel erqlQueryPanel;
	
	// panel including the RQL query area
	private JPanel rqlQueryPanel;
	
	// text area for showing + entering the eRQL query string
	private JTextArea erqlQueryTextArea;
	
	// text area for showing + entering the RQL query string
	private JTextArea rqlQueryTextArea;
	
	// JScrollPane for rqlQueryTextArea
	private JScrollPane rqlQueryScrollPane;
	
	// JScrollPane for erqlQueryTextArea
	private JScrollPane erqlQueryScrollPane;
	
	// panel including the result area
	private JPanel resultPanel;
	
	// text area for showing the query result
	private JTextArea resultTextArea;
	
	// JScrollPane for resultTextArea
	private JScrollPane resultScrollPane;
	
	// button for choosing rdf source file
	private JButton chooseSourceFileButton;
	
	// file chooser for choosing rdf source file, if already shown - else null
	private JFileChooser sourceFileChooser;
	
	// panel including the center area (itself including the query result)
	private JPanel centerPanel;
	
	// panel including the lower area (itself including the close-button,...)
	private JPanel bottomPanel;
	
	// button for executing the RQL or eRQL query
	private JButton executeButton;
	
	/**
	 * Application entry point. No parameters supported yet.
	 */
	public static void main() {
		try {
			// try to set the platform-Look And Feel
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch( Exception e ) {
			// this doesn't really matter => default Look And Feel is used
		}
		
		new Gui().setVisible(true);
	}
	
	/**
	 * Constructor, creates and show the main application window.
	 */
	private Gui() {
		// call base class' constructor + set window title
		super("eRqlEngine");
		
		// resize window
		this.setSize(640,480);
		
		// center window
		this.setLocationRelativeTo(null);

		// create output-stream for printing error- and debug-messages
		out = new java.io.ByteArrayOutputStream();
		
		// create eRQL-Engine
		this.eRQL=new eRqlEngine(out);
		this.RQL=new RqlEngine();
		
		// add listener for the window's closing-button
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				exit();
			}
		});
		
		// create + add top area panel
		this.getContentPane().add(this.topPanel=createPanel(),BorderLayout.NORTH);
		
		// create + add file area panel
		this.fileAreaPanel = this.createPanel( "RDF Data Source" );
		this.topPanel.add(this.fileAreaPanel,BorderLayout.NORTH);
		
		// create + add the text field for showing + entering the input
		// file's name and it's label
		this.fileAreaPanel.add(this.fileNameTextField=new JTextField(),
			BorderLayout.CENTER );
		this.fileNameTextField.setToolTipText("Enter the full path and filename of a local RDF source file here");
		this.fileNameTextField.setBorder( this.getTextFieldBorder() );	
		this.fileAreaPanel.add(this.fileNameTextFieldLabel=new JLabel(
			"Source File"), BorderLayout.WEST );
			
		// create + add button for choosing rdf source file
		fileAreaPanel.add(this.chooseSourceFileButton=new JButton(
			"..."),BorderLayout.EAST);
		this.chooseSourceFileButton.setToolTipText("Click here to browse for a RDF source file");
		chooseSourceFileButton.addActionListener(
			new SourceFileChooseButtonHandler(this) );

		// create + add eRQL query panel
		this.erqlQueryPanel = this.createPanel();
		
		// create + add tabbed pane for containing the eRQL query panel
		this.queryTabbedPane = new JTabbedPane();
		this.topPanel.add(queryTabbedPane,BorderLayout.SOUTH);
		queryTabbedPane.addTab("eRQL", erqlQueryPanel);

		// create + add RQL query panel
		this.rqlQueryPanel = this.createPanel();
		
		// create + add tabbed pane for containing the RQL query panel
		queryTabbedPane.addTab("RQL", rqlQueryPanel);
		
		this.queryTabbedPane.setMnemonicAt(Gui.MODE_ERQL, KeyEvent.VK_E);
		this.queryTabbedPane.setMnemonicAt(Gui.MODE_RQL, KeyEvent.VK_R);

		// create + add the text area for showing + entering the eRQL query string
		this.erqlQueryPanel.add( this.rqlQueryScrollPane=createScrollPane(
			this.erqlQueryTextArea=this.createTextArea()
		), BorderLayout.CENTER );
		this.erqlQueryTextArea.setToolTipText("Enter an eRQL-query here, e. g. PABLO PICASSO");
		
		KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_MASK);
		Action a = new ExecuteQueryButtonHandler(this);
		this.erqlQueryTextArea.getKeymap().addActionForKeyStroke(enter, a);
			
		this.erqlQueryPanel.setPreferredSize(new Dimension(640,150));
		
		// create + add the text area for showing + entering the RQL query string
		this.rqlQueryPanel.add( this.rqlQueryScrollPane=createScrollPane(
			this.rqlQueryTextArea=this.createTextArea()
		), BorderLayout.CENTER );
		this.rqlQueryTextArea.setToolTipText("Enter a RQL-query here, e. g. SELECT S,@P,O FROM {S}@P{O}");
		this.rqlQueryPanel.setPreferredSize(new Dimension(640,150));
		
		// create + add center area panel
		this.getContentPane().add(this.centerPanel=createPanel(),BorderLayout.CENTER);
		
		// create + add result panel
		this.centerPanel.add(this.resultPanel = this.createPanel("Query Result"),BorderLayout.CENTER);
		
		// create + add the buttong for executing an eRQL query
		this.centerPanel.add(this.executeButton=new JButton("Execute! (ALT-ENTER)"),BorderLayout.NORTH);
		this.executeButton.setToolTipText("Click here to execute the above query");
		this.executeButton.addActionListener(new ExecuteQueryButtonHandler(this));
		this.getRootPane().setDefaultButton(this.executeButton);
		enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_MASK);
		a = new ExecuteQueryButtonHandler(this);
		this.rqlQueryTextArea.getKeymap().addActionForKeyStroke(enter, a);

		// create + add the text area for showing the query result
		this.resultPanel.add( this.resultScrollPane=createScrollPane(
			this.resultTextArea=this.createTextArea()
		), BorderLayout.CENTER );
		this.resultTextArea.setToolTipText("The result of the query above");
		this.resultTextArea.setEditable(false);
	
		// create + add bottom area panel
		this.getContentPane().add(this.bottomPanel=this.createPanel(),BorderLayout.SOUTH);
		
		// create + add quit-button
		this.bottomPanel.add(quitButton=new JButton("Close"),BorderLayout.EAST);
		this.quitButton.setToolTipText("Click here to close the application");
		quitButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e){
				exit();
			}
		});
		
		// create application information label
		this.bottomPanel.add(this.appinfoLabel=new JLabel("eRqlEngine - http://www.wleklinski.de/rdf/"),BorderLayout.WEST);
		this.appinfoLabel.setHorizontalAlignment(SwingConstants.CENTER);
		this.appinfoLabel.setToolTipText("http://www.wleklinski.de/rdf/");
		Font urlLabelFont = new Font(this.appinfoLabel.getFont().getName(),
			Font.PLAIN, this.appinfoLabel.getFont().getSize() );
		this.appinfoLabel.setFont(urlLabelFont);
		this.appinfoLabel.addMouseListener(new UrlLabelMouseListener());
		
		// access user-specific preferences node
		this.prefs = Preferences.userNodeForPackage(Gui.class);
		
		// get last source file
		this.setSourceFile(this.prefs.get("sourceFile",null));

		// get last eRQL and RQL query string
		this.erqlQueryTextArea.setText(this.prefs.get("query",null));
		this.rqlQueryTextArea.setText(this.prefs.get("query.rql",null));

		this.erqlQueryTextArea.requestFocusInWindow();

		// restore window position out of preferences registry
		this.setLocation(
			this.prefs.getInt("windowPositionX",this.getLocation().x),
			this.prefs.getInt("windowPositionY",this.getLocation().y)
		);

		// restore window size out of preferences registry
		this.setSize(
			this.prefs.getInt("windowSizeX",this.getSize().width),
			this.prefs.getInt("windowSizeY",this.getSize().height)
		);

		// restore window state out of preferences registry
		this.setExtendedState(prefs.getInt("windowState",this.getExtendedState()));
		
		this.queryTabbedPane.setSelectedIndex(this.prefs.getInt("mode",0));

		// reduce the window's size to the minimum size
		// this.pack();
	}
	
	private int getMode() {
		return this.queryTabbedPane.getSelectedIndex();
	}

	/**
	 * Exits this application.
	 */
	private void exit() {
		// store source file name into preferences repository
		this.prefs.put("sourceFile", this.fileNameTextField.getText());
		
		// store eRQL and RQL query string into preferences repository
		this.prefs.put("query", this.erqlQueryTextArea.getText());
		this.prefs.put("query.rql", this.rqlQueryTextArea.getText());

		// store window state
		this.prefs.putInt("windowState",this.getExtendedState());

		// ensure that the window is not maximized		
		this.setExtendedState(Frame.NORMAL);
		
		// store position + size only if window isn't maximized
		if ((this.getExtendedState() & Frame.MAXIMIZED_BOTH) != Frame.MAXIMIZED_BOTH) {
			// store window position into preferences registry
			this.prefs.putInt("windowPositionX",this.getLocation().x);
			this.prefs.putInt("windowPositionY",this.getLocation().y);

			// store window size into preferences registry
			this.prefs.putInt("windowSizeX",this.getSize().width);
			this.prefs.putInt("windowSizeY",this.getSize().height);
		}
		
		this.prefs.putInt("mode", this.queryTabbedPane.getSelectedIndex() );
		
		// call dispose of the surrounding class
		setVisible(false);
		dispose();
		System.exit(0);
	}
	
	private JScrollPane createScrollPane(Component view) {
		JScrollPane sp=new JScrollPane(view);
		sp.setBorder(this.getTextFieldBorder());
		return sp;
	}
	
	private JTextArea createTextArea() {
		JTextArea ta=new JTextArea();
		
		ta.setLineWrap(true);
		ta.setWrapStyleWord(true);
		ta.setMargin(new Insets(5,5,5,5));
		
		return ta;
	}
	
	private JPanel createPanel() {
		return createPanel(null);
	}
	private JPanel createPanel(String title) {
		JPanel p = new JPanel();
		if (title!=null) {
			p.setLayout(new BorderLayout(10,10));
			p.setBorder(BorderFactory.createCompoundBorder(
				BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),title),
				BorderFactory.createEmptyBorder(2, 5, 5, 5)
			));
		} else {
			p.setLayout(new BorderLayout(0,5));
			p.setBorder(BorderFactory.createEmptyBorder(2, 5, 5, 5));
		}
		return p;
	}
	
	private Border getTextFieldBorder() {
		return (JTextFieldBorder!=null) ? JTextFieldBorder :
			(JTextFieldBorder = UIManager.getBorder("TextField.border"));
	}	
		
	/**
	 * Sets the source file.
	 */
	private void setSourceFile( File sourceFile ) {
		// this.sourceFile = sourceFile;
		this.fileNameTextField.setText(sourceFile.getAbsolutePath());
	}
	
	/**
	 * Sets the source file.
	 */
	private void setSourceFile( String sourceFileName ) {
		this.fileNameTextField.setText(sourceFileName);
	}
	
	private void erqlQuery() {
		eworks.RDF.model.Tuples result = null;
		try {
			result=this.eRQL.query(new java.io.File(this.fileNameTextField.getText()),
				this.erqlQueryTextArea.getText());
		} catch(Exception e) {
			System.out.println(e);
			e.printStackTrace();
		}
		this.resultTextArea.setText(dumpTupels(result));		
	}
	
	private void rqlQuery() {
		eworks.RDF.model.Tuples result = null;
		this.resultTextArea.setText("");
		try {
			this.RQL.setDataSource(new java.io.File(this.fileNameTextField.getText()));
			result=this.RQL.query(this.rqlQueryTextArea.getText());
		} catch(Exception e) {
			System.out.println(e);
			e.printStackTrace();
		}
		this.resultTextArea.setText(dumpTupels(result));		
	}
	
	private String dumpTupels( eworks.RDF.model.Tuples tupels ) {
		StringBuffer resultText = new StringBuffer();
		ArrayList namespaces = new ArrayList();
		if (tupels == null) {
			resultText.append("0 Rows found.");
			resultText.append('\n');
			resultText.append('\n');
		} else {
			resultText.append(tupels.size());
			resultText.append(" Rows found.");
			resultText.append('\n');
			resultText.append('\n');
			
			for (int i=tupels.size()-1; i>=0; i--) {
				Tuple tuple = tupels.getTuple(i);
				
				for (int j=tuple.getSize()-1;j>=0;j--) {
					
					eworks.RDF.model.Value value = tuple.get(j);
					
					if (value instanceof UriLiteralValue) {
						java.net.URI namespace = ((UriLiteralValue) value).getNamespace();

						if (namespace != null) {
							long namespaceIndex = namespaces.indexOf(namespace);
							if (namespaceIndex < 0) {
								namespaceIndex = namespaces.size();
								namespaces.add(namespace);
							}
							resultText.append( '[' );
							resultText.append( namespaceIndex+1 );
							resultText.append( ']' );
						}
						
						resultText.append( ((UriLiteralValue) value).getLocalName() );
					} else {
						String str = value.toString();
						
						if (str.indexOf(' ') > 0) {
							resultText.append( '"' );
							resultText.append( str );
							resultText.append( '"' );
						} else {
							resultText.append( str );
						}
					}
					resultText.append( ' ' );
					
				}
				resultText.append( '\n' );
			}
			resultText.append( '\n' );

			for (int i=0; i<namespaces.size(); i++) {
				java.net.URI namespace = (java.net.URI) namespaces.get(i);
				
				resultText.append( "[" );
				resultText.append( i+1 );
				resultText.append( "] " );
				resultText.append( namespace.toString() );
				resultText.append( '\n' );				
			}
			resultText.append( '\n' );
		}
		return resultText.toString();
	}
	
	/**
	 * A class that handles clicks onto the "eRQL execute query"-button.
	 */
	private class ExecuteQueryButtonHandler extends AbstractAction {
		// the calling instance
		private Gui gui;
		
		/**
		 * Constructs with an explicitly given Gui-instance. 
		 */
		private ExecuteQueryButtonHandler( Gui gui ) {
			this.gui=gui;
		}
		
		/**
		 * Event handler for clicks onto the button 
		 */
		public void actionPerformed(ActionEvent e) {
			if (this.gui.getMode()==Gui.MODE_ERQL) {
				// focus query result area
				this.gui.erqlQueryTextArea.requestFocusInWindow();
				
				this.gui.erqlQuery();
			} else {
				// focus query result area
				this.gui.rqlQueryTextArea.requestFocusInWindow();
				
				this.gui.rqlQuery();
			}
		}
	}
	
	/**
	 * A class that handles clicks onto the "choose source file"-button.
	 * It shows a dialog for choosing the source file.
	 *  
	 * @author wleklinski
	 */
	private class SourceFileChooseButtonHandler implements ActionListener {
		// the frame to be the dialog's parent
		private Component dialogParent;
		
		/**
		 * Constructs without an explicit dialog parent. 
		 */
		private SourceFileChooseButtonHandler() {
			this( null );
		}

		/**
		 * Constructs with the given an explicit dialog parent. 
		 */
		private SourceFileChooseButtonHandler( Component dialogParent ) {
			this.dialogParent=dialogParent;
		}
		
		/**
		 * initializes the file chooser but doesn't show it.
		 */
		private void initSourceFileChooser() {
			if (sourceFileChooser == null) {
				// create file chooser
				sourceFileChooser = new JFileChooser();
				
				// set dialog's title
				sourceFileChooser.setDialogTitle(
					"Choose RDF Source File" );
					
				// create default file filter (*.RDF)
				FileFilter rdfFileFilter = new RdfFileFilter();
				
				// add chooseable file filters
				sourceFileChooser.addChoosableFileFilter(new KnownFileFilter());
				sourceFileChooser.addChoosableFileFilter(rdfFileFilter);
				sourceFileChooser.addChoosableFileFilter(new XmlFileFilter());
	
				// define default file filter
				sourceFileChooser.setFileFilter(rdfFileFilter);
			}
			
			sourceFileChooser.setSelectedFile( new File(
				fileNameTextField.getText()));
		}
		
		/**
		 * Event handler for clicks onto the button- 
		 */
		public void actionPerformed(ActionEvent e) {
			// initialize the file chooser
			this.initSourceFileChooser();
			
			// show file chooser
			int result=sourceFileChooser.showOpenDialog(dialogParent);
			
			// check result (okay vs. abort)
			if (result==JFileChooser.APPROVE_OPTION) {
				if (sourceFileChooser.getSelectedFile().exists()) {
					// file does exist => use as source file
					setSourceFile( sourceFileChooser.getSelectedFile() );
				} else {
					// file does not exist => error message
					JOptionPane.showMessageDialog(sourceFileChooser,
						"The choosen file *) does not exist. Please " +
						"choose another file.\n\n*) " +
						sourceFileChooser.getSelectedFile().getAbsolutePath(),
						"File not found", JOptionPane.WARNING_MESSAGE );
				}
			}
			
			// focus query text area
			erqlQueryTextArea.requestFocusInWindow();
		}
	}
	
	private class KnownFileFilter extends FileFilter {
		public boolean accept(java.io.File f) {
			if (! f.isFile()) return true;
			
			String fileExtension = Utils.getExtension(f);
			return (fileExtension != null) &&
				(fileExtension.equalsIgnoreCase("rdf") ||
				fileExtension.equalsIgnoreCase("xml"));
		}
		public String getDescription() {
			return "All known filetypes (*.RDF, *.XML)";
		}
	}
	private class RdfFileFilter extends FileFilter {
		public boolean accept(java.io.File f) {
			if (! f.isFile()) return true;

			return ((! f.isFile()) || "rdf".equalsIgnoreCase(Utils.getExtension(f)));  
		}
		public String getDescription() {
			return "RDF-Files (*.RDF)";
		}
	}
	private class XmlFileFilter extends FileFilter {
		public boolean accept(java.io.File f) {
			return ((! f.isFile()) || "xml".equalsIgnoreCase(Utils.getExtension(f)));  
		}
		public String getDescription() {
			return "XML-Files (*.XML)";
		}
	}
	
	private class UrlLabelMouseListener extends MouseInputAdapter {
		private Color oldColor = Color.BLACK;
		
		public void mouseEntered(MouseEvent e) {
			this.oldColor = ((JLabel) e.getSource()).getForeground();
			((JLabel) e.getSource()).setForeground(Color.BLUE);
		}
 
		public void mouseExited(MouseEvent e) {
			((JLabel) e.getSource()).setForeground(this.oldColor);
		}

		public void mouseClicked(MouseEvent e) {
			try {
				BrowserLauncher.openURL( ((JLabel) e.getSource()).getToolTipText() );
			} catch (IOException ex) {
				System.out.println(ex);
			}
		}
	}
}