package lu.tudor.santec.gecamed.formeditor.gui.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.JTextComponent;

import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.widgets.GECAMedBaseDialog;
import lu.tudor.santec.gecamed.core.gui.widgets.GECAMedBaseDialogImpl;
import lu.tudor.santec.gecamed.formeditor.gui.FormEditorModule;
import lu.tudor.santec.gecamed.formeditor.gui.FormWidgets;
import lu.tudor.santec.gecamed.formeditor.gui.component.EditableComponent;
import lu.tudor.santec.gecamed.formeditor.gui.component.ReloadListener;
import lu.tudor.santec.gecamed.formeditor.gui.component.ScriptHelper;
import lu.tudor.santec.gecamed.formeditor.gui.model.FormModel;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rtextarea.RTextScrollPane;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.toedter.calendar.JDateChooser;

public class FormController implements MouseListener, MouseMotionListener, MouseWheelListener, PropertyChangeListener,
		// AncestorListener, ComponentListener, ContainerListener, 
		FocusListener, KeyListener, ActionListener, CaretListener, ItemListener, ChangeListener, ReloadListener {
	/**
	 * @author Jens Ferring
	 * 
	 * This method is added to every JCommponent in the final form as listener. The code 
	 * handed over with the setters into the String-array, is the code executed in the 
	 * method, implemented from the listener.
	 */
	
	public static final int INIT_METHOD 			= 0;
	public static final int MOUSE_CLICKED 			= 1;
	public static final int MOUSE_ENTERED 			= 2;
	public static final int MOUSE_EXITED 			= 3;
	public static final int MOUSE_PRESSED 			= 4;
	public static final int MOUSE_RELEASED 			= 5;
	public static final int MOUSE_DRAGGED 			= 6;
	public static final int MOUSE_MOVED 			= 7;
	public static final int MOUSE_WHEEL_MOVED 		= 8;
	public static final int FOCUS_GAINED  			= 9;
	public static final int FOCUS_LOST 				= 10;
	public static final int KEY_PRESSED 			= 11;
	public static final int KEY_RELEASED 			= 12;
	public static final int KEY_TYPED 				= 13;
	public static final int ACTION_PERFORMED 		= 14;
	public static final int CARET_UPDATED 			= 15;	
	public static final int STATE_CHANGED 			= 16;
	public static final int ITEM_STATE_CHANGED 		= 17;
	public static final int CONTENT_RELOADED 		= 18;
	public static final int PROPERTY_CHANGE 		= 19;
	public static final int NO_OF_LISTENER_METHODS 	= 20;
	
	public 	static final String FIX_KEY_EVENT 			= "event";
	public 	static final String FIX_KEY_FORM_CONTEXT 	= "formContext";
	public 	static final String FIX_KEY_COMPONENTS 		= "components";
	public 	static final String FIX_KEY_MAIN_PANEL 		= "mainPanel";
	public 	static final String FIX_KEY_PATIENT 		= "patient";
	public 	static final String FIX_KEY_PHYSICIAN 		= "physician";
	private static final String FIX_KEY_CELLCONSTRAINTS = "cc";
	public 	static final String FIX_KEY_COMPONENT_LIST 	= "componentList";
	public 	static final String FIX_KEY_DBREF 			= "dbRef";
	public 	static final String FIX_KEY_GLOBAL_CONTEXT 	= "globalContext";
	public 	static final String FIX_KEY_SCRIPT_HELPER 	= "scriptHelper";
	
	public static final String[][] FIX_KEYS 	= { {FIX_KEY_FORM_CONTEXT, 	"HashMap&lt;Object, Object&gt;"}, 
													{FIX_KEY_GLOBAL_CONTEXT,"HashMap&lt;Object, Object&gt;"}, 
													{FIX_KEY_COMPONENTS, 	"HashMap&lt;String, JComponent&gt;"},
													{FIX_KEY_SCRIPT_HELPER, ScriptHelper.class.getSimpleName()},
													{FIX_KEY_EVENT, 		EventObject.class.getSimpleName()}, 
													{FIX_KEY_MAIN_PANEL, 	JPanel.class.getSimpleName()}, 
													{FIX_KEY_PATIENT, 		Patient.class.getSimpleName()}, 
													{FIX_KEY_PHYSICIAN, 	Physician.class.getSimpleName()} };
	
	public static final String[] FORBIDDEN_KEYS = { FIX_KEY_COMPONENT_LIST, 
													FIX_KEY_DBREF, 
													FIX_KEY_CELLCONSTRAINTS, 
													"context",
													"if", "else", "true", "false",  
													"do", "while", "for", "break", "continue", "in", 
													"throws", "throw", "try", "catch", "finally", 
													"goto", "switch", "case", "default", 
													"instanceof", "typeof", "this", 
													"byte", "short", "int", "long", "float", "double", 
													"boolean", "char", "var", "const", "final",   
													"abstract", "as", "debugger", "in", "enum", "export", "import", 
													"extends", "implements", "interface", "class", "is", "namespace",
													"native", "new", "null", "package", "private", "protected", "public",
													"return", "static", "super", "synchronized", "transient", 
													"use", "void", "volatile", "with"};
	
	private static final long IGNROE_TIME_MILLIS = 5000;
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(FormController.class.getName());
	
	private static ScriptHelper scriptHelper = new ScriptHelper();
	
	private FormModel 			model;
	private String[] 			code;
	private ScriptEngine 		engine;
	
	// ERROR DIALOG	
	private static GECAMedBaseDialogImpl errorDialog;
	private static RSyntaxTextArea 	errorScriptTextComponent;
	private static JLabel 			errorMessageLabel;
	private static long 			ignoreErrorsStart;
	
	
	
	/* ======================================== */
	// 			CONSTRUCTOR
	/* ======================================== */
	
	public FormController (FormModel model) {
		this.model 		= model;
		code 			= new String[FormController.NO_OF_LISTENER_METHODS];
	}
	
	
	/* ======================================== */
	// 			GETTER & SETTER
	/* ======================================== */
	
	public FormModel getModel () {
		return model;
	}
	public void setCodeAt (int index, String newCode) {
		code[index] = newCode;
	}
	public String[] getCode () {
		return code;
	}
	
	private static void showErrorDialog (ScriptException e, String script) 
	{
		if (System.currentTimeMillis() - ignoreErrorsStart <= IGNROE_TIME_MILLIS)
		{
			return;
		}
		
		if (errorDialog == null)
		{
			errorMessageLabel = new JLabel();
			
			errorScriptTextComponent = new RSyntaxTextArea();
			errorScriptTextComponent.setEditable(false);
			errorScriptTextComponent.getCaret().setSelectionVisible(false);
			errorScriptTextComponent.setLineWrap(false);
			errorScriptTextComponent.setSyntaxEditingStyle(RSyntaxTextArea.SYNTAX_STYLE_JAVASCRIPT);
			
			RTextScrollPane scrollPane = new RTextScrollPane(errorScriptTextComponent);
			scrollPane.setLineNumbersEnabled(true);
			scrollPane.setOpaque(false);
			
			JPanel mainPanel = new JPanel(new FormLayout("15px, f:1px:g, 15px", "15px, f:p, 15px, f:1px:g, 15px"));
			CellConstraints cc = new CellConstraints();
			mainPanel.add(errorMessageLabel, 	cc.xy(2, 2));
			mainPanel.add(scrollPane, 			cc.xy(2, 4));
			
			JButton ignoreFollowingErrorsButton = new JButton(FormEditorModule.getSmallIcon("ignore_warning.png"));
			ignoreFollowingErrorsButton.setToolTipText(
					Translatrix.getTranslationString("formeditor.formController.ignoreWarningsToolTip"));
			ignoreFollowingErrorsButton.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent e)
				{
					ignoreErrorsStart = System.currentTimeMillis();
					errorDialog.setVisible(false);
				}
			});
			
			errorDialog = new GECAMedBaseDialogImpl(
					MainFrame.getInstance(), 
					"ScriptException", 
					GECAMedBaseDialog.OK_OPTION,
					mainPanel);
			errorDialog.addButton(ignoreFollowingErrorsButton);
			errorDialog.setSize(600, 600);
			
			errorScriptTextComponent.setCaretPosition(0);
		}
		
		errorMessageLabel.setText("<html>"+e.getMessage());
		errorScriptTextComponent.setText(script);
		try
		{
			int 	line 		= e.getLineNumber()-1;
			int 	lineCount 	= errorScriptTextComponent.getLineCount();
 			int 	dot 		= errorScriptTextComponent.getLineStartOffset(line);
 			int 	mark 		= errorScriptTextComponent.getLineEndOffset(line);
			Caret 	caret 		= errorScriptTextComponent.getCaret();
			
			if (line - 10 < 0)
				 errorScriptTextComponent.setCaretPosition(0);
			else errorScriptTextComponent.setCaretPosition(line - 10);
			
			if (line + 10 >= lineCount)
				 errorScriptTextComponent.setCaretPosition(lineCount-1);
			else errorScriptTextComponent.setCaretPosition(line + 10);
			
			caret.setDot(dot);
			caret.moveDot(mark-1);
		} 
		catch (BadLocationException ble) 
		{ 
			logger.log(Level.ERROR, e.getMessage(), e);
		}
		finally
		{
			errorDialog.showCenteredDialog();
		}
	}
	
	
	/* ======================================== */
	// 			SCRIPTING ENGINE
	/* ======================================== */
	
	public void executeCodeAt (String listenerScript, EventObject event) 
	{
		if (model == null 
				|| model.isBeingModified() 
				|| listenerScript.length() == 0)
			// no deposit code 
			return;
		// else: execute the "code" at code[index]
		
		// define the engine
		if (engine == null) 
			this.engine = getEngine(model.getGlobalComponents(), this.model.getPanel(), 
					model.getFormContext(), model.getGlobalContext());
		
		engine.put(FIX_KEY_EVENT, event);
		
		StringBuffer script = new StringBuffer(listenerScript);
		script.append(FormWidgets.NEWLINE).append(model.getFunctionsAsOneString());
		
		try {
			// evaluate the code, specified for this event
			engine.eval(script.toString());
		} 
		catch (ScriptException e) 
		{
			showErrorDialog(e, script.toString());
		}
	}
	
	
	private void executeCodeAt (int index, EventObject event) 
	{
		if (code[index] != null && code[index].length() > 0) 
		{
			executeCodeAt (code[index], event);
			if (!model.isModified()
					&& model.isLoaded()
					&& index != CONTENT_RELOADED
					&& index != INIT_METHOD
					&& (model.getFormTab() != null 
					|| (model.getSubForm() != null 
							&& model.getSubForm().getParentFormModel() != null)))
			{
				model.setModified(true);
			}
		} 
		else if (!model.isModified()
				&& model.isLoaded()
				&& (model.getFormTab() != null
				// there are only changes, if smth. is typed into an text component
				&& ((index == KEY_RELEASED
						&& (event.getSource() instanceof JTextComponent
								|| event.getSource() instanceof JTable))
				|| (index == PROPERTY_CHANGE
						&& event.getSource() instanceof JDateChooser
						&& "date".equals(((PropertyChangeEvent)event).getPropertyName()))
				// or the state of an object is changed
				|| index == ITEM_STATE_CHANGED
				|| (event.getSource() instanceof JDateChooser
						&& index == CARET_UPDATED))))
		{
			model.setModified(true);
		}
	}
	
	
	
	public static ScriptEngine getEngine (Map<String, JComponent> components, 
			JPanel backgroundPanel, HashMap<Object, Object> formContext, HashMap<Object, Object> globalContext) 
	{
		HashMap<String, JComponent> componentMap 	= new HashMap<String, JComponent>();
		ScriptEngine 				engine 		 	= new ScriptEngineManager().getEngineByName("rhino");
		JComponent 					c;
		
		for (String key : components.keySet()) 
		{
			c = components.get(key);
			if (c instanceof EditableComponent) 
			{
				/* if key contains a '.' don't put it in the engine, 
				 * because the variable cannot be called later on anyway.
				 */
				if (!key.contains("."))
					engine.put(	 key, ((EditableComponent)c).getComponent());
				componentMap.put(key, ((EditableComponent)c).getComponent());
			}
			else 
			{
				if (!key.contains("."))
					engine.put(	 key, c);
				componentMap.put(key, c);
			}
		}
		
//		engine.put(FIX_KEY_COMPONENT_LIST, 	componentList);
		engine.put(FIX_KEY_COMPONENTS, 		components);
		engine.put(FIX_KEY_PATIENT, 		GECAMedModule.getCurrentPatient().clone());
		engine.put(FIX_KEY_PHYSICIAN, 		GECAMedModule.getCurrentPhysician().clone());
		engine.put(FIX_KEY_MAIN_PANEL, 		backgroundPanel);
//		engine.put(FIX_KEY_CELLCONSTRAINTS,	new CellConstraints());
		engine.put(FIX_KEY_FORM_CONTEXT, 	formContext);
		engine.put(FIX_KEY_GLOBAL_CONTEXT, 	globalContext);
		engine.put(FIX_KEY_SCRIPT_HELPER, 	scriptHelper);
		
		return engine;
	}
	
	
	
	/* ======================================== */
	// 			EVENTS
	/* ======================================== */
	
	public void propertyChange (PropertyChangeEvent e) {
		executeCodeAt(PROPERTY_CHANGE, e);
	}
	public void mouseClicked(MouseEvent e) {
		executeCodeAt(MOUSE_CLICKED, e);
	}
	public void mouseEntered(MouseEvent e) {
		executeCodeAt(MOUSE_ENTERED, e);
	}
	public void mouseExited(MouseEvent e) {
		executeCodeAt(MOUSE_EXITED, e);
	}
	public void mousePressed(MouseEvent e) {
		executeCodeAt(MOUSE_PRESSED, e);
	}
	public void mouseReleased(MouseEvent e) {
		executeCodeAt(MOUSE_RELEASED, e);
	}	
	public void mouseDragged(MouseEvent e) {
		executeCodeAt(MOUSE_DRAGGED, e);
	}	
	public void mouseMoved(MouseEvent e) {
		executeCodeAt(MOUSE_MOVED, e);
	}	
	public void mouseWheelMoved(MouseWheelEvent e) {
		executeCodeAt(MOUSE_WHEEL_MOVED, e);
	}	
	public void focusGained(FocusEvent e) {
		executeCodeAt(FOCUS_GAINED, e);
	}	
	public void focusLost(FocusEvent e) {
		executeCodeAt(FOCUS_LOST, e);
	}	
	public void keyPressed(KeyEvent e) {
		executeCodeAt(KEY_PRESSED, e);
	}	
	public void keyReleased(KeyEvent e) {
		executeCodeAt(KEY_RELEASED, e);
	}	
	public void keyTyped(KeyEvent e) {
		executeCodeAt(KEY_TYPED, e);
	}	
	public void caretUpdate(CaretEvent e) {
		executeCodeAt(CARET_UPDATED, e);
	}	
	public void actionPerformed(ActionEvent e) {
		executeCodeAt(ACTION_PERFORMED, e);
	}	
	public void itemStateChanged(ItemEvent e) {
		executeCodeAt(ITEM_STATE_CHANGED, e);
	}	
	public void stateChanged(ChangeEvent e) {
		executeCodeAt(STATE_CHANGED, e);
	}
	public void contentReloaded(EventObject e) {
		executeCodeAt(CONTENT_RELOADED, e);
	}
}