/*
 *  Draco Econometrics
 *  Copyright (C) 2008-2009 Approximatrix, LLC
 *  http://www.approximatrix.com
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.approximatrix.draco.application.plugin;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.jruby.RubyArray;
import org.jruby.RubyObject;
import org.jruby.runtime.builtin.IRubyObject;

import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.math.EquationEvaluation;

public abstract class Plugin implements PluginData {

	private static final String PLUGIN_LANG = "ruby";
	private static final String SCRIPT_MODEL_BEAN = "model";
	
	private static final String PLUGIN_INIT_SCRIPT = "/pengine/plugin_init.template";
	private static final String PLUGIN_REQUIRE_TOKEN = "$$script";
	private static final String PLUGIN_CLASS_TOKEN = "$$class";
	
	private static String plugin_template = null;
	
    private String name = null;
    private PluginType type = PluginType.analysis;
    
    private HashMap<String,String> process_menu = null;
    private HashMap<String,String> option_menu = null;
     
    private SelectionColumnList selectColumns = null;
    
    private String script = null;
    private String scriptClass = null;
    
    private StatTableModel model = null;
    
    private BSFManager manager = null;
    
    private IRubyPlugin robject = null;
    
    private String results = null;
    
    private static String LoadInitScript() {
    	if(plugin_template == null) {
    		InputStream is = Plugin.class.getResourceAsStream(PLUGIN_INIT_SCRIPT);
    		StringBuffer buffer = new StringBuffer();
    		try {
    			int b;
    			while((b = is.read())>0) {
    				buffer.append((char)b);
    			}
    			is.close();
    		} catch (IOException ioe) {
    			buffer.append("import java.lang.System\nSystem.err.println('Error loading plugin init resource (io)')");
    		} catch (NullPointerException npe) {
    			buffer.append("import java.lang.System\nSystem.err.println('Error loading plugin init resource (null)')");
    		}
    		plugin_template = buffer.toString();
    	}
		
		return new String(plugin_template);
	}
    
    public Plugin(String name, PluginType type, String scriptClass, String script) {
        this.name = name;
        this.type = type;
        this.script = script;
        this.scriptClass = scriptClass;
        
        process_menu = new HashMap<String,String>();
        option_menu = new HashMap<String,String>();
        
    }
    
    /* (non-Javadoc)
	 * @see com.approximatrix.draco.application.plugin.PluginData#getScriptClass()
	 */
    public String getScriptClass() {
        return scriptClass;
    }
    
    /** Returns the file name, with valid path, that contains the plugin code
     * 
     * @return a path to the script file that can be used directly with a <code>require</code> call
     */
    public String getScriptRequire() {
        return script;
    }
    
    /* (non-Javadoc)
	 * @see com.approximatrix.draco.application.plugin.PluginData#getName()
	 */
    public String getName() {
        return name;
    }
    
    /* (non-Javadoc)
	 * @see com.approximatrix.draco.application.plugin.PluginData#getVersion()
	 */
    public int getVersion() {
    	return -1;
    }
    
    /* (non-Javadoc)
	 * @see com.approximatrix.draco.application.plugin.PluginData#getOrigin()
	 */
    public String getOrigin() {
    	return null;
    }
    
    public void setProcessMenu(String text, String command) {
        process_menu.put(text,command);
    }
    
    public void setOptionMenu(String text, String command) {
        option_menu.put(text,command);
    }
    
    public void initialize(StatTableModel model) {
    	this.model = model;
    	this.manager = new BSFManager();
    	this.robject = (IRubyPlugin)generateObject();
    	
    	if(robject == null)
    		return;
    	
    	//this.selectColumns = new ArrayList<VariableSelectionColumn>();
    	
    	try {
    		this.selectColumns = this.robject.columns();
    	} catch(ClassCastException cce) {
    		this.selectColumns = new SelectionColumnList();
    	} catch(NullPointerException npe) {
    		System.err.println("A null pointer exception has been thrown...");
    		npe.printStackTrace();
    		this.selectColumns = new SelectionColumnList();
    	} catch(Exception e) {
    		System.err.println("Surprising Exception!");
    		e.printStackTrace();
    	}
    	
    	
    }  
    
    public void execute() {
    	if(robject == null) {
    		results = "Plugin was not loaded properly...";
    		return;
    	}
    		
    	if(results == null) {
    		try {
    			robject.run();
    		} catch(OutOfMemoryError oome) {
    			robject = null;
    			results = "Plugin Failure: Out Of Memory...";
    		}
    	}	
    }
    
    /* (non-Javadoc)
	 * @see com.approximatrix.draco.application.plugin.PluginData#getResults()
	 */
    public String getResults() {
    	if(results == null)
    		results = robject.results();
    	return results;
    }
    
    public void dispose() {
    	
    }
    
    private IRubyPlugin generateObject() {
    	IRubyPlugin obj = null;
    	try {
			manager.declareBean(SCRIPT_MODEL_BEAN, this.model, StatTableModel.class);
			obj = (IRubyPlugin)manager.eval(PLUGIN_LANG, "(java)", 1, 1, buildScript());
		} catch(BSFException be) {
			System.err.println("Error performing plugin initialization...");
			be.printStackTrace();
		} catch(ClassCastException cce) {
			System.err.println("Unexpected object returned during plugin initialization.");
		} finally {
			cleanupBeans();
		}
		return obj;
    }
    
    private String buildScript() {
    	String script = LoadInitScript();
    	//System.out.println(script.replace(PLUGIN_REQUIRE_TOKEN, this.script).replace(PLUGIN_CLASS_TOKEN, this.scriptClass));
    	return script.replace(PLUGIN_REQUIRE_TOKEN, this.script).replace(PLUGIN_CLASS_TOKEN, this.scriptClass);
    }
    
    private void cleanupBeans() {
		try {
			manager.undeclareBean(SCRIPT_MODEL_BEAN);
		} catch(BSFException be) {
			System.err.println("Error cleaning up BSF beans during Plugin initialization...");
			be.printStackTrace();
		}
	}

	/* (non-Javadoc)
	 * @see com.approximatrix.draco.application.plugin.PluginData#getType()
	 */
	public PluginType getType() {
		return type;
	}

	public List<VariableSelectionColumn> getSelectionColumns() {
		return selectColumns;
	}
	
    public SimpleOptionList getOptions() {
        if(robject != null) 
            return robject.options();
        else
            return null;
    }
    
    /** This method is meant to generate a plugin instance for calculations.
     * 
     * @return a plugin instance
     */
    public abstract Plugin createInstance();

}
