package lu.tudor.santec.gecamed.letter.test;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;

import javax.script.ScriptEngine;
import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import lu.tudor.santec.gecamed.addressbook.ejb.entity.beans.Contact;
import lu.tudor.santec.gecamed.core.ejb.session.beans.ListManagerBean;
import lu.tudor.santec.gecamed.core.ejb.session.interfaces.ListManagerInterface;
import lu.tudor.santec.gecamed.core.test.TestUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.letter.ejb.entity.beans.LetterPlaceholder;
import lu.tudor.santec.gecamed.letter.ejb.session.beans.LetterPlaceholderBean;
import lu.tudor.santec.gecamed.letter.ejb.session.interfaces.LetterPlaceholderInterface;
import lu.tudor.santec.gecamed.letter.gui.placeholders.ScriptingManager;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Office;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Physician;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Site;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.Patient;
import lu.tudor.santec.gecamed.usermanagement.ejb.entity.beans.GecamedUser;

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;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: PlaceholderTester.java,v $
 */

public class PlaceholderTester // extends Thread
{
	/* ======================================== */
	// CONSTANTS
	/* ======================================== */
	
	private static int CONTINUE_NORMAL					= 0;
	private static int CONTINUE_REEVALUATE_PLACEHOLDER	= 1;
	private static int CONTINUE_REEVALUATE_TYPE			= 2;
	private static int CONTINUE_RESTART					= 3;
	private static int CONTINUE_EXIT					= 4;
	
	
	
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static Logger logger;
	
	
//	private Path				globalFunctionsScript;
//	private Path				typeFunctionsScript;
	private Path				testScript;
	
	private	JFrame				errorDialog;
	private int					continueOption;
	private	JLabel				errorDescriptionLabel;
	private JTextArea			errorMessageArea;
	private RSyntaxTextArea		errorScriptArea;
	
	private ScriptingManager	scriptingManager;
	
	
	
	/* ======================================== */
	// CONSTRUCTORS
	/* ======================================== */
	
	public PlaceholderTester (Integer patientId, Integer userId, Integer officeId, 
			Integer siteId, Integer physicianId, Integer contactId) throws IOException
	{
		if (logger == null)
			logger = Logger.getLogger(PlaceholderTester.class.getName());
		
		// load the beans to initialize the ScriptingManager
		ListManagerInterface manager = ((ListManagerInterface) ManagerFactory.getRemote(ListManagerBean.class));
		
		Office		office		= manager.getBean(Office.class,		officeId);
		Site		site		= manager.getBean(Site.class,		siteId);
		Physician	physician	= manager.getBean(Physician.class,	physicianId);
		Patient		patient		= manager.getBean(Patient.class,	patientId);
		GecamedUser	user		= manager.getBean(GecamedUser.class,userId);
		Contact		contact		= manager.getBean(Contact.class,	contactId);
		
		List<Contact>	contactList	= new ArrayList<Contact>(1);
		contactList.add(contact);
		
		// initialize the ScriptingManager
		ScriptingManager.initializeStaticContext(office, user, site);
		scriptingManager = new ScriptingManager();
		scriptingManager.initializeEngine(contactList, patient, physician);
		
		// create the temp files
//		String	tmpDirPath	= System.getProperty("java.io.tmpdir") + File.pathSeparator + "gecamed";
//		File	tmpDir		= new File(tmpDirPath);
//		tmpDir.mkdirs();
		
//		globalFunctionsScript	= Files.createTempFile("gecamed" + File.separator + "global_js_functions_", ".js");
//		typeFunctionsScript		= Files.createTempFile("gecamed" + File.separator + "type_js_functions_", ".js");
		testScript				= Files.createTempFile("js_test_script_", ".js");
	}
	
	
	
	/* ======================================== */
	// CLASS BODY
	/* ======================================== */

//	@Override
//	public void run ()
//	{
//		try
//		{
//			testPlaceholders();
//		}
//		catch (Exception e)
//		{
//			logger.error("Error while testing the ", e);
//		}
//	}
	
	
	public void testPlaceholders () throws IOException
	{
		LetterPlaceholderInterface	manager	= (LetterPlaceholderInterface) ManagerFactory.getRemote(LetterPlaceholderBean.class);
		List<String>				placeholderTypes;
		String						type;
		List<LetterPlaceholder>		placehoders;
		LetterPlaceholder			p;
		String						globalFunctions;
		String						typeFunctions;
		FileReader					reader;
		ScriptEngine				engine;
		Object						result;
		int							continueOption	= CONTINUE_NORMAL;
		
		
		logger.info("Java Version: " + System.getProperty("java.version"));
		
		engine				= scriptingManager.getEngine();
		
		// get all placeholder types
		placeholderTypes	= new ArrayList<String>(manager.getPlaceholderTypes());
		// get the global functions
		globalFunctions		= manager.getFunctions((String) null) + "\n\n";
		
		for (int typeIndex = 0; typeIndex < placeholderTypes.size(); typeIndex++)
		{
			type		 	= placeholderTypes.get(typeIndex);
			logger.info("Testing placeholders of type \"" + type + "\"");
			typeFunctions	= manager.getFunctions(type) + "\n\n";
			placehoders		= new ArrayList<LetterPlaceholder>(manager.getPlaceholdersByType(type));
			
			for (int placeholderIndex = 0; placeholderIndex < placehoders.size(); placeholderIndex++)
			{
				p				= placehoders.get(placeholderIndex);
				continueOption	= CONTINUE_NORMAL;
				if (p == null || p.getScript() == null || p.getScript().trim().isEmpty())
				{
					logger.warn("Skipping placeholder" + (p != null ? " \"" + p.getName() + "\"" : ""));
					continue;
				}
				
				if (testScript.toFile().exists())
				{
					try
					{
						Files.delete(testScript);
					}
					catch (Exception e)
					{
						testScript = Files.createTempFile("js_test_script_", ".js");
					}
				}
				testScript	= Files.write(testScript, globalFunctions.getBytes("UTF-8"), 
						StandardOpenOption.WRITE, StandardOpenOption.CREATE);
				testScript	= Files.write(testScript, typeFunctions.getBytes("UTF-8"), 
						StandardOpenOption.WRITE, StandardOpenOption.APPEND);
				testScript	= Files.write(testScript, p.getScript().getBytes("UTF-8"), 
						StandardOpenOption.WRITE, StandardOpenOption.APPEND);
				reader = new FileReader(testScript.toFile());
				
				try
				{
					logger.info("Testing placeholder " + p.getName());
					result = engine.eval(reader);
					
					if (result == null 
							|| !(result instanceof CharSequence))
					{
						throw new Exception("The return value of a script of a LetterParameter must be a CharSequence (e.g. a String)"
								+ " and must not be null.\n" + 
								"But the result of LetterParameter " + p.getName() + " was " + 
								(result == null ? "null" : result.getClass().getSimpleName()));
					}
				}
				catch (Exception e)
				{
					logger.warn("Error executing JavaScript test", e);
					continueOption	= showErrorDialog(p, e);
					
					if (continueOption == CONTINUE_EXIT)
					{
						return;
					}
					else if (continueOption == CONTINUE_REEVALUATE_PLACEHOLDER)
					{
						placeholderIndex--;
						continue;
					}
					else if (continueOption == CONTINUE_REEVALUATE_PLACEHOLDER)
					{
						typeIndex--;
						break;
					}
				}
				finally
				{
					if (testScript.toFile().exists())
					{
						try
						{
							Files.delete(testScript);
						}
						catch (Exception e)
						{
							testScript.toFile().deleteOnExit();
						}
					}
				}
			}
		}
	}
	
	
	
	/* ======================================== */
	// HELP METHODS
	/* ======================================== */
	
//	private static Path loadFunctions (LetterPlaceholderInterface manager, Path file, String type) throws IOException
//	{
//		// TODO
//		String	functions	= manager.getFunctions(type);
//		
//		if (functions == null)
//			functions = "";
//		
//		return Files.write(file, functions.getBytes("UTF-8"), 
//				StandardOpenOption.CREATE, StandardOpenOption.WRITE);
//	}
	
	
	private int showErrorDialog (LetterPlaceholder p, Throwable t) throws IOException
	{
		if (errorDialog == null)
		{
			CellConstraints cc	= new CellConstraints();
			int				row	= 0;
			JScrollPane		scrollPane;
			JCheckBox		optionBox;
			JButton			continueButton;
			ButtonGroup		group;
			
			// define the panel
			JPanel	panel	= new JPanel(new FormLayout(
					// columns
					"5px, f:p:g, 5px",
					// rows
					  " 5px,f:p,"	// placeholder description
					+ " 5px,f:p,"	// error message
					+ " 5px,f:p:g,"	// JavaScript
					+ "10px,f:p,"	// continue option: continue
					+ " 5px,f:p,"	// continue option: reevaluate current placeholder
					+ " 5px,f:p,"	// continue option: reevaluate current type
					+ " 5px,f:p,"	// continue option: restart
					+ " 5px,f:p,"	// continue option: exit
					+ "10px,f:p,"	// continue button
					+ " 5px"));
			
			// description
			errorDescriptionLabel	= new JLabel();
			panel.add(errorDescriptionLabel, cc.xy(2, row+=2));
			
			// error message and stack
			errorMessageArea		= new JTextArea();
			scrollPane				= new JScrollPane(errorMessageArea);
			scrollPane.setPreferredSize(new Dimension(800, 150));
			panel.add(scrollPane, cc.xy(2, row+=2));
			
			// script
			errorScriptArea			= new RSyntaxTextArea();
			errorScriptArea.setEditable(false);
			errorScriptArea.setLineWrap(false);
			errorScriptArea.setSyntaxEditingStyle(RSyntaxTextArea.SYNTAX_STYLE_JAVASCRIPT);
			RTextScrollPane	rScrollPane	= new RTextScrollPane(errorScriptArea);
			rScrollPane.setPreferredSize(new Dimension(800, 400));
			rScrollPane.setLineNumbersEnabled(true);
			panel.add(rScrollPane, cc.xy(2, row+=2));
			
			// continue option
			group					= new ButtonGroup();
			
			// continue normal
			optionBox				= new JCheckBox("Continue testing");
			optionBox.addItemListener(new ItemListener()
			{
				public void itemStateChanged (ItemEvent e)
				{
					continueOption	= CONTINUE_NORMAL;
				}
			});
			group.add(optionBox);
			panel.add(optionBox, cc.xy(2, row+=2));
			
			// continue current placeholder
			optionBox				= new JCheckBox("Test the current placeholder again");
			optionBox.addItemListener(new ItemListener()
			{
				public void itemStateChanged (ItemEvent e)
				{
					continueOption	= CONTINUE_REEVALUATE_PLACEHOLDER;
				}
			});
			group.add(optionBox);
			panel.add(optionBox, cc.xy(2, row+=2));
			optionBox.setSelected(true);
			
			// continue current type
			optionBox				= new JCheckBox("Test all placeholders of this type again");
			optionBox.addItemListener(new ItemListener()
			{
				public void itemStateChanged (ItemEvent e)
				{
					continueOption	= CONTINUE_REEVALUATE_TYPE;
				}
			});
			group.add(optionBox);
			panel.add(optionBox, cc.xy(2, row+=2));
			
			// restart
			optionBox				= new JCheckBox("Restart testing");
			optionBox.addItemListener(new ItemListener()
			{
				public void itemStateChanged (ItemEvent e)
				{
					continueOption	= CONTINUE_RESTART;
				}
			});
			group.add(optionBox);
			panel.add(optionBox, cc.xy(2, row+=2));
			
			// exit
			optionBox				= new JCheckBox("Exit");
			optionBox.addItemListener(new ItemListener()
			{
				public void itemStateChanged (ItemEvent e)
				{
					continueOption	= CONTINUE_EXIT;
				}
			});
			group.add(optionBox);
			panel.add(optionBox, cc.xy(2, row+=2));
			
			// continue button
			continueButton			= new JButton(new AbstractAction("OK")
			{
				private static final long	serialVersionUID	= 1L;

				public void actionPerformed (ActionEvent e)
				{
					errorDialog.setVisible(false);
				}
			});
			panel.add(continueButton, cc.xy(2, row+=2, CellConstraints.RIGHT, CellConstraints.CENTER));
			
			// create the error Dialog
			errorDialog	= new JFrame();
			errorDialog.setTitle("Error in JavaScript");
			errorDialog.add(panel);
		}
		
		errorDescriptionLabel.setText("Error with placeholder \""+p.getName()+"\"");
		
		StringWriter writer = new StringWriter();
		t.printStackTrace(new PrintWriter(writer));
		errorMessageArea.setText(t.getMessage() + "\n" + writer.toString());
		
		errorScriptArea.setText(new String(Files.readAllBytes(testScript), "UTF-8"));
		
		errorDialog.pack();
		errorDialog.setLocationRelativeTo(null);
		errorDialog.setVisible(true);
		
		while (errorDialog.isVisible())
		{
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				logger.error("Error while waiting for dialog to be closed", e);
			}
		}
		
		return continueOption;
	}
	
	
	
	/* ======================================== */
	// MAIN
	/* ======================================== */
	
	public static void main (String[] args) throws IOException
	{
		TestUtils.initJBossConnection();
		logger = Logger.getLogger(PlaceholderTester.class.getName());
		
		PlaceholderTester tester;
		if (args.length == 0)
		{
			// default
			tester	= new PlaceholderTester(
					27221,	// patient:		Guido BOSCH
					11,		// user:		ferringj
					1,		// office:		only one available
					5,		// site:		Belval
					13,		// physician: 	Jens Ferring
					36019);	// contact: 	Dr. Joern ADLER
		}
		else if (args.length < 6)
		{
			logger.error("Do not specify any argument, to use the default settings or specify 6 arguments, used as follows:\n"
					+ "patientId, userId, officeId, siteId, physicianId, contactId");
			return;
		}
		else
		{
			int i = 0;
			tester	= new PlaceholderTester(
					Integer.valueOf(args[i++]),
					Integer.valueOf(args[i++]),
					Integer.valueOf(args[i++]),
					Integer.valueOf(args[i++]),
					Integer.valueOf(args[i++]),
					Integer.valueOf(args[i++]));
		}
		tester.testPlaceholders();
		
		logger.info("TESTING COMPLETED !!!");
	}
}
