/* 
  Copyright (C) 2013 Raquel Pau and Albert Coroleu.
 
 Walkmod is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 Walkmod is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public License
 along with Walkmod.  If not, see <http://www.gnu.org/licenses/>.*/
package org.walkmod.scripting;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

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

import org.apache.log4j.Logger;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
import org.walkmod.exceptions.WalkModException;
import org.walkmod.query.QueryEngine;
import org.walkmod.query.QueryEngineAware;
import org.walkmod.walkers.VisitorContext;

import groovy.lang.GroovyClassLoader;

public class ScriptProcessor implements QueryEngineAware {

	private String language = "groovy";

	private ScriptEngine engine = null;

	private String location;

	private String content;

	private static Logger log = Logger.getLogger(ScriptProcessor.class);

	private QueryEngine queryEngine;

	public void initialize(VisitorContext context, Object node) {
		if (engine == null) {
			ScriptEngineManager factory = new ScriptEngineManager(context.getClassLoader());
			engine = factory.getEngineByName(language);
			if (engine instanceof GroovyScriptEngineImpl) {
				((GroovyScriptEngineImpl) engine).setClassLoader(new GroovyClassLoader(context.getClassLoader(),
						new CompilerConfiguration()));
			}
		}

		if (queryEngine == null) {
			Map<String, Object> parameters = new HashMap<String, Object>();
			parameters.put("language", "groovy");
			List<String> includes = new LinkedList<String>();
			includes.add("query.alias.groovy");
			parameters.put("includes", includes);

			Object bean = context.getBean("org.walkmod.query.ScriptingQueryEngine", parameters);
			if (bean != null) {
				if (bean instanceof QueryEngine) {
					queryEngine = (QueryEngine) bean;
				}

			} else {
				throw new WalkModException("Query Engine not found");
			}
		}

		Map<String, Object> params = new HashMap<String, Object>();
		params.put("node", node);
		queryEngine.initialize(context, params);
	}

	public void visit(Object node, VisitorContext ctx) {
		initialize(ctx, node);

		Bindings bindings = engine.createBindings();
		bindings.put("node", node);
		bindings.put("context", ctx);
		bindings.put("query", getQueryEngine());
		if (content != null) {
			try {
				engine.eval(content, bindings);
			} catch (ScriptException e) {
				log.error("The file " + e.getFileName() + " has an error at line: " + e.getLineNumber() + ", column: "
						+ e.getColumnNumber());
				throw new WalkModException(e);
			}
		} else {
			if (location != null) {
				Reader reader = null;
				File file = new File(location).getAbsoluteFile();
				if (file.exists()) {
					try {
						reader = new FileReader(file);
					} catch (FileNotFoundException e) {
						throw new WalkModException(e);
					}
				} else {
					URL uri = ctx.getClassLoader().getResource(location);
					if (uri != null) {
						try {
							reader = new FileReader(new File(uri.getFile()));
						} catch (FileNotFoundException e) {
							throw new WalkModException(e);
						}
					}
				}
				if (reader != null) {
					try {
						engine.eval(reader, bindings);
					} catch (ScriptException e) {
						throw new WalkModException(e);
					}
				}
			}
		}
	}

	public void setLanguage(String language) {
		this.language = language;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public void setQueryEngine(QueryEngine queryEngine) {
		this.queryEngine = queryEngine;
	}

	public QueryEngine getQueryEngine() {
		return queryEngine;
	}

	public Object query(String query) {
		if (queryEngine != null) {
			return queryEngine.resolve(query);
		}
		return null;
	}

	public Object query(Object context, String query) {
		if (queryEngine != null) {
			return queryEngine.resolve(context, query);
		}
		return null;
	}

}