package lu.tudor.santec.gecamed.letter.gui.placeholders;

import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import lu.tudor.santec.gecamed.address.ejb.session.beans.AddressManagerBean;
import lu.tudor.santec.gecamed.address.ejb.session.interfaces.AddressManagerInterface;
import lu.tudor.santec.gecamed.addressbook.ejb.entity.beans.Contact;
import lu.tudor.santec.gecamed.core.gui.GECAMedLists;
import lu.tudor.santec.gecamed.core.gui.GECAMedModule;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.core.utils.printing.ireport.UtilFormatter;
import lu.tudor.santec.gecamed.letter.ejb.entity.beans.LetterPlaceholder;
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.patient.ejb.session.interfaces.IncidentManager;
import lu.tudor.santec.gecamed.patient.gui.PatientManagerModule;
import lu.tudor.santec.gecamed.patient.gui.settings.ConsultationEntryConfig;
import lu.tudor.santec.gecamed.patient.gui.settings.PatientModuleSettings;
import lu.tudor.santec.gecamed.usermanagement.ejb.entity.beans.GecamedUser;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class ScriptingManager
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	private static final String	PATIENT			= "PATIENT";
	
	private static final String PHYSICAN		= "PHYSICIAN";
	
	private static final String	PHYSICAN_LIST	= "PHYSICIAN_LIST";
	
	private static final String	OFFICE			= "OFFICE";
	
	private static final String	MAIN_CONTACT	= "MAIN_CONTACT";
	
	private static final String	COPY_CONTACT	= "COPY_CONTACT";
	
	private static final String	CONTACT_LIST	= "CONTACT_LIST";
	
	private static final String	USER			= "USER";
	
	private static final String SITE			= "SITE";
	
	private static final String FORMATTER		= "FORMATTER";
	
//	private static final String SETTINGS		= "SETTINGS";
	
	private static final String DB_MANAGER		= "DATABASE_MANAGER";
	
	private static final String SCRIPT_CONTENT	= "SCRIPT_CONTEXT";
	
	private static final String CLASS_LOADER	= "CLASS_LOADER";
	
	
	
	/* ======================================== */
	// 		MEMBERS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(ScriptingManager.class.getName());
	
	private static UtilFormatter		formatter;
	
	private static ScriptEngineManager	scriptManager;
	
	private static boolean				initialized	= false;
	
	
	private ScriptEngine	engine;
	
	private Contact[]		contacts;
	
	private int				contactIndex;
	
	private Collection<LetterPlaceholder>	placeholders;
	
	private Map<String, String>				data;
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	public void initializeEngine (Contact contact)
	{
		List<Contact>	contacts	= new ArrayList<Contact>(1);
		
		
		contacts.add(contact);
		initializeEngine(contacts);
	}
	
	
	public void initializeEngine (List<Contact> contactList)
	{
		initializeEngine(contactList, GECAMedModule.getCurrentPatient(), MainFrame.getCurrentPhysician());
	}
	
	
	public void initializeEngine (List<Contact> contactList, Patient currentpatient, Physician currentPhysician)
	{
		if (!initialized)
			initializeStaticContext(MainFrame.getCurrentOffice(), MainFrame.getCurrentUser(), MainFrame.getCurrentSite());
		
//		this.engine = scriptManager.getEngineByName("JavaScript");
		this.engine = scriptManager.getEngineByName("rhino");
		this.engine.put(PATIENT,		currentpatient);
		this.engine.put(PHYSICAN,		currentPhysician);
		this.engine.put(SCRIPT_CONTENT,	new HashMap<String, Object>());
		this.engine.put(CLASS_LOADER,	new JavaScriptClassLoader());
		
		// load the physician and patient into the formatter
		addAdditionalTranslations();
		formatter.setDefaultPatient(currentpatient);
		formatter.setDefaultPhysician(currentPhysician);
		
		// set the contact
		if (contactList == null)
			contactList = new ArrayList<Contact>();
		
		this.engine.put(CONTACT_LIST,	contactList);
		
		if (contactList.isEmpty())
		{
			this.engine.put(MAIN_CONTACT, null);
			this.contacts		= null;
		}
		else 
		{
			this.engine.put(MAIN_CONTACT, contactList.get(0));
			this.contacts		= new Contact[contactList.size()];
			this.contacts		= contactList.toArray(this.contacts);
			this.contactIndex	= 0;
		}
	}
	
	
	public ScriptEngine	getEngine ()
	{
		return engine;
	}
	
	
	public void loadScripts (Collection<LetterPlaceholder> placeholders)
	{
		this.placeholders	= placeholders;
		this.data			= new HashMap<String, String>();
		
		if (contacts != null && contacts.length > 0)
			this.engine.put(COPY_CONTACT, contacts[contactIndex]);
		
		// load the global & type functions 
		loadFunctions(NewPlaceholdersConfig.loadFunctions(placeholders));
		
		// evaluate the placeholders
		for (LetterPlaceholder p : this.placeholders)
		{
			evalPlaceholder(p);
		}
	}
	
	
	public Map<String, String> nextData ()
	{
		if (contacts != null 
				&& contacts.length > 0 
				&& contactIndex > 0)
		{
			this.engine.put(COPY_CONTACT, contacts[contactIndex]);
			
			for (LetterPlaceholder p : this.placeholders)
			{
				if (p.getType() != null 
						&& (p.getType().toUpperCase().equals("CONTACT")
						||  p.getType().toUpperCase().startsWith("CONTACT_")))
					evalPlaceholder(p);
			}
		}
		contactIndex++;
		
		return data;
	}
	
	
	public void test (Reader script, Contact contact)
	{
		initializeEngine(contact);
		
		try
		{
			Object result = engine.eval(script);
			logger.info("Testscript successfull!\nResult: " + result);
		}
		catch (ScriptException e)
		{
			logger.log(Level.ERROR, "Error in test script", e);
		}
	}
	
	
	/**
	 * Enable this for debugging JavaScript.
	 * 
	 * @return weather or not the test script button in the tool menu bar shall be shown
	 */
	public static boolean showScriptTestButton ()
	{
		return true;
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	private void evalPlaceholder (LetterPlaceholder p)
	{
		Object	result;
		try
		{
			result	= engine.eval(p.getScript());
		}
		catch (Exception e)
		{
			logger.log(Level.ERROR, "Error in script of LetterPlaceholder " + p.getName() + 
					"\nSCRIPT:\n"+p.getScript()+"\n\n"+e.getMessage(), e);
			result = "<<ScriptError: "+p.getName()+">>";
		}
		
		if (result == null 
				|| !(result instanceof CharSequence))
		{
			logger.warn("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()));
//			if (result == null)
//				 result	= "<<NullError: "+p.getName()+">>";
//			else 
//				result	= "<<TypeError: "+p.getName()+">>";
			if (result == null)
				result	= "";
			else 
				result	= String.valueOf(result);
		}
		
		logger.debug("\tEval: " + p.getName() + " to " + String.valueOf(result));
		data.put(p.getName(), String.valueOf(result));
	}
	
	
	private static List<Physician> getAllPhysicians ()
	{
		return GECAMedLists.getListCopy(Physician.class);
//		return ((OfficeManagerInterface) ManagerFactory.getRemote(OfficeManagerBean.class))
//    			.getAllPhysicians();
	}
	
	
	private void loadFunctions (Collection<LetterPlaceholder> functions)
	{
		for (LetterPlaceholder f : functions)
		{
			if (f.getName() == null)
			{
				try
				{
					this.engine.eval(f.getScript());
				}
				catch (ScriptException e)
				{
					logger.log(Level.ERROR, "Error while loading script function:\n" + 
							f.getScript() + "\n\n" + e.getMessage());
				}
			}
		}
	}
	
	
	public static void initializeStaticContext (Office office, GecamedUser user, Site site)
	{
		if (!initialized)
		{
			try
			{
				formatter	= new UtilFormatter((AddressManagerInterface) ManagerFactory.getRemote(AddressManagerBean.class));
				formatter.setDefaultOffice(office);
				formatter.setDefaultUser(user);
	//			UtilSettings	settings	= formatter.getSettings();
	//			settings.loadSettings();
				
				// initialize the engine manager
				scriptManager	= new ScriptEngineManager();
				Bindings	bindings	= scriptManager.getBindings();
				bindings.put(PHYSICAN_LIST,	getAllPhysicians());
				bindings.put(OFFICE,		office);
				bindings.put(USER,			user);
				bindings.put(SITE,			site);
				bindings.put(DB_MANAGER,	new DatabaseManager());
				bindings.put(FORMATTER, 	formatter);
	//			bindings.put(SETTINGS, 		settings);
				
				addAdditionalTranslations();
				
				initialized = true;
			}
			catch (Exception e)
			{
				logger.error("Error while initializing stastic script context. This may cause errors in the script.", e);
			}
		}
	}
	
	
	private static void addAdditionalTranslations ()
	{
		UtilFormatter	formatter	= (UtilFormatter) scriptManager
				.getBindings().get(FORMATTER);
		Set<String>		entries		= new HashSet<String>();
		
		
		if (PatientManagerModule.getInstance() == null)
		{
			logger.warn("The PatientManagerModule is not initialized.\n"
					+ "This should only happen for test mode, where no GECAMed client is started ...");
			
			entries.add(PatientModuleSettings.SOAPA_DEFAULTS);
			entries.add(PatientModuleSettings.SOAPO_DEFAULTS);
			entries.add(PatientModuleSettings.SOAPP_DEFAULTS);
			entries.add(PatientModuleSettings.SOAPS_DEFAULTS);
			entries.add(PatientModuleSettings.CONS1_DEFAULTS);
			entries.add(PatientModuleSettings.CONS2_DEFAULTS);
			entries.add(PatientModuleSettings.CONS3_DEFAULTS);
			entries.add(PatientModuleSettings.SICKLEAVE_DEFAULTS);
		}
		else
		{
			// set entry settings
			PatientModuleSettings patientGlobalSettings = PatientManagerModule.getInstance().patientSettings;
			
			// set values or default values of consultation entries
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.SOAP_A));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.SOAP_O));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.SOAP_P));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.SOAP_S));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.CONS_1));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.CONS_2));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.CONS_3));
			entries.add((String) patientGlobalSettings.getValue(IncidentManager.SICK_LEAVE));
		}
		
		ConsultationEntryConfig cEntry;
		for (String value : entries)
		{
			cEntry	= null;
			try
			{
				cEntry = new ConsultationEntryConfig(value);
				formatter.addTranslation("patient.incident." + cEntry.getId(), cEntry.getTranslatedName());
				formatter.addTranslation("patient.incident.code." + cEntry.getId(), cEntry.getCodeLabel());
			}
			catch (Exception e)
			{
				logger.error("Error while adding consulation translations for entry \"" + 
						(cEntry == null ? value : ("ID: " + cEntry.getId() + " | Name: " + cEntry.getName() + " | Code label: " + cEntry.getCodeLabel())) + 
						"\"", e);
			}
		}
		
		scriptManager.getBindings().put(FORMATTER, formatter);
	}
}
