package hiro.yoshioka.sql.engine;

import hiro.yoshioka.sdh2.ResultSetDataHolder2;
import hiro.yoshioka.sql.DominoSQL;
import hiro.yoshioka.sql.IRequestListener;
import hiro.yoshioka.sql.ITransactionSQL;
import hiro.yoshioka.sql.params.ConnectionProperties;
import hiro.yoshioka.util.FileUtil;
import hiro.yoshioka.util.StringUtil;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Request extends Thread {
	public enum SCRIPTING_CONST {
		FILE_NAME, FILES, LOG
	};

	public enum STATUS_CONST {
		PRE, EXECUTE, DONE;

		public String getSymbol() {
			switch (this) {
			case PRE:
				return "/";
			case EXECUTE:
				return "*";
			case DONE:
				return "-";
			}
			return "?";
		}
	};

	public final SQLOperationType operation;

	public boolean makeBlob;
	ResultSetDataHolder2 fRdh;
	public boolean requestForUpdatable;

	private STATUS_CONST status = STATUS_CONST.PRE;

	Throwable e;
	/** Classloader to be used when running the script. */
	private ClassLoader scriptLoader;

	List<File> interceptorsList;

	Set<URL> externalJarSet;

	Set<IRequestListener> fListener;

	public boolean result;

	protected ConnectionProperties properties;

	protected Properties extra_properties = new Properties();

	protected Log log = LogFactory.getLog(getClass());

	protected ScriptEngine[] engines;
	protected static final ScriptEngine[] EMPTY_ENGINES = new ScriptEngine[0];

	// ----------------------------------------------

	public Request(SQLOperationType operation) {
		this.operation = operation;
	}

	public Request(SQLOperationType operation, ConnectionProperties properties) {
		this.operation = operation;
		this.properties = properties;
	}

	public void setRDH(ResultSetDataHolder2 rdh) {
		if (rdh != null) {
			log.debug("rdh size=" + rdh.getRowCount());
		}
		fRdh = rdh;
		fRdh.setForUpdate(this.requestForUpdatable);
	}

	public ResultSetDataHolder2 getRdh() {
		return fRdh;
	}

	public void setResult(boolean b) {
		fRdh = new ResultSetDataHolder2(new String[] { "Result" }, null,
				properties.getDatabaseType());
		fRdh.setConnectionDisplayString(properties.getDisplayString());
		fRdh.addRow(new String[] { String.valueOf(b) });
	}

	public void addInterceptor(File script) {
		if (interceptorsList == null) {
			interceptorsList = new ArrayList<File>();
		}
		interceptorsList.add(script);
	}

	public void addExternalJar(File externalJarFile) {
		if (externalJarSet == null) {
			externalJarSet = new HashSet<URL>();
		}
		try {
			externalJarSet.add(externalJarFile.toURL());
		} catch (MalformedURLException e) {
		}
	}

	public static void main(String[] args) {
		Thread currentThread = Thread.currentThread();
		ClassLoader originalLoader = currentThread.getContextClassLoader();
		File ff = new File("/Users/yonsama/tmp/piyo.jar");
		URLClassLoader loader = null;
		try {
			loader = new URLClassLoader(new URL[] { ff.toURL() });
			// Class clsss = loader.loadClass("piyo.PiyoPiyo");
			// System.out.println("cl=" + clsss);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		currentThread.setContextClassLoader(loader);

		ScriptEngineManager manager = new ScriptEngineManager();
		ScriptEngine engine = manager.getEngineByName("JavaScript");
		// engine.getContext().
		try {
			engine.eval("print('hoge  ');");
			engine.eval("print('piyo  ');");
			engine.eval("print('' + new Packages.piyo.PiyoPiyo() );");
		} catch (ScriptException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	protected void createScriptEngines() {
		if (interceptorsList == null) {
			this.engines = EMPTY_ENGINES;
			return;
		}
		this.engines = new ScriptEngine[this.interceptorsList.size()];
		for (int i = 0; i < this.interceptorsList.size(); i++) {
			ScriptEngineManager manager = new ScriptEngineManager();
			this.engines[i] = manager.getEngineByName("JavaScript");
			try {

				this.engines[i].eval(FileUtil.getText(interceptorsList.get(i)));
				this.engines[i].put(SCRIPTING_CONST.FILE_NAME.name(),
						interceptorsList.get(i).getName());
				this.engines[i].put(SCRIPTING_CONST.FILES.name(),
						interceptorsList.toArray(new File[interceptorsList
								.size()]));
				this.engines[i].put(SCRIPTING_CONST.LOG.name(), log);
			} catch (Exception e) {
				log.fatal(e);
			}
		}
	}

	public Properties getExtra_properties() {
		return extra_properties;
	}

	public void addExtra_Property(String key, String value) {
		this.extra_properties.setProperty(key, value);
	}

	public synchronized void addListener(IRequestListener listerner) {
		if (listerner == null) {
			return;
		}
		if (fListener == null) {
			fListener = new HashSet<IRequestListener>();
		}
		fListener.add(listerner);
	}

	public STATUS_CONST getStatus() {
		return status;
	}

	public String toString() {
		return String.format("Request[%s] status[%s] hasException[%b]",
				operation, status, hasException());
	}

	public Throwable getException() {
		return e;
	}

	public boolean hasException() {
		return e != null;
	}

	public void run() {
		execute();
	}

	public synchronized void beginTask(String taskName, int row) {
		if (fListener != null) {
			for (Iterator<IRequestListener> ite = fListener.iterator(); ite
					.hasNext();) {
				IRequestListener obj = ite.next();
				obj.beginTask(taskName, row);
			}
		}
	}

	public synchronized void subTask(String subTaskName) {
		if (fListener != null) {
			for (Iterator<IRequestListener> ite = fListener.iterator(); ite
					.hasNext();) {
				IRequestListener obj = ite.next();
				obj.subTask(subTaskName);
			}
		}
	}

	public synchronized void worked(int i) {
		if (fListener != null) {
			for (Iterator<IRequestListener> ite = fListener.iterator(); ite
					.hasNext();) {
				IRequestListener obj = ite.next();
				obj.worked(i);
			}
		}
	}

	public synchronized void pre() {
		if (fListener != null) {
			for (IRequestListener listener : fListener) {
				try {
					listener.called_pre(this, operation);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	final public boolean execute() {
		ITransactionSQL sql = SQLServerThread.getSQLServer().getTransactionSQL(
				properties);
		System.out.println("executed... sql=" + sql);
		if (sql == null) {
			log.debug("SQLServerThread cant execute sql request. no connection properteis["
					+ properties + "]");
			return false;
		}
		properties.countUpRequest();
		pre();
		Thread currentThread = Thread.currentThread();
		ClassLoader originalLoader = currentThread.getContextClassLoader();
		if (externalJarSet != null && externalJarSet.size() > 0) {
			try {
				URLClassLoader loader = new URLClassLoader(
						externalJarSet.toArray(new URL[externalJarSet.size()]));
				currentThread.setContextClassLoader(loader);
			} catch (Exception e1) {
			}
		}

		createScriptEngines();
		if (engines.length > 0) {
			before();
		}
		currentThread.setContextClassLoader(originalLoader);
		try {
			this.status = STATUS_CONST.EXECUTE;
			sql.updateLatestRequestTime();
			result = sql.doOperation(operation, this);
		} catch (Throwable e) {
			log.fatal("Operation error", e);
			if (e instanceof SQLException) {
				SQLException se = (SQLException) e;
				if (DominoSQL.DOMINO_SQL_EXCEPTION_STAUS.equals(se
						.getSQLState())) {
					e = se.getCause();
				} else {
					SQLException e2 = se.getNextException();
					if (e2 != null) {
						log.fatal("Operation error next Exception", e2);
					}
				}
			}
			this.e = e;
		}
		properties.countEndOfRequest();
		endOperation(result);
		if (engines.length > 0) {
			if (externalJarSet != null && externalJarSet.size() > 0) {
				try {
					URLClassLoader loader = new URLClassLoader(
							externalJarSet.toArray(new URL[externalJarSet
									.size()]));
					currentThread.setContextClassLoader(loader);
				} catch (Exception e1) {
				}
			}
			after();
			currentThread.setContextClassLoader(originalLoader);
		}
		System.err.println("before done process");
		this.status = STATUS_CONST.DONE;
		done();
		return result;
	}

	public void setThrowable(Throwable e) {
		this.e = e;
	}

	protected void endOperation(boolean operationResult) {
	}

	protected void before() {
	}

	protected void after() {
	}

	protected Object getDoneArgObject() {
		return null;
	}

	public synchronized void done() {
		if (fListener != null) {
			Object o = getDoneArgObject();
			for (Iterator<IRequestListener> ite = fListener.iterator(); ite
					.hasNext();) {
				IRequestListener obj = ite.next();
				try {
					obj.called_done(this, operation, properties, o);
				} catch (Exception e) {
					log.warn(StringUtil.EMPTY_STRING, e);
				}
			}
		}
	}

	public ConnectionProperties getConnectionProperties() {
		return properties;
	}
}