package miningmart.storedProcedures;
import java.sql.SQLException;

public class EMF {

    private final DbConnector connector;
    private final Windowing   window; // for the input
    private final OutputColumnset out; // for the output
    private final double  headWeight; // weight of the tail
    private final double  tailWeight; // weight of the head

    private static final int INDEXONATTRIB = 1;

    protected Windowing getWindow() {
	return this.window;
    }

    protected OutputColumnset getOutputColumnset() {
	return this.out;
    }

    protected double getHeadWeight() {
	return this.headWeight;
    }

    protected double getTailWeight() {
	return this.tailWeight;
    }


    /**
     * Constructor
     * @param dbc source of database connection if not used as stored procedure
     * @param source source table
     * @param time   time column of source table
     * @param column value column of source table
     * @param target name of the target table to be created
     * @param timeOut name of the output time attribute
     * @param valueOut name of the output value attribute
     * @param headWeight the head weight parameter
     * @param tailWeight the tail weight parameter
     * @param distance step size for windowing
     * @throws SQLException
     * @throws TimeOperatorException
    */    
    public EMF(BusinessDbConnectionSource dbc,
    	   	   String source, String time, String column,
		       String target, String timeOut, String valueOut,
		       double headWeight, double tailWeight, int distance)
		throws SQLException, TimeOperatorException
    {
		this.connector = new DbConnector(dbc);
		this.headWeight = headWeight;
		this.tailWeight = tailWeight;

		final short timeDT   = DataType.getColumnDataType(source, time, this.connector);
		final short columnDT = DataType.getColumnDataType(source, column, this.connector);

		// Input:
		final Attribute timeAttr   = new Attribute(time,   timeDT,   timeDT);
		final Attribute columnAttr = new Attribute(column, columnDT, DataType.NUMBER);
	
		if (!timeAttr.isConvertable()) {
	    	DataType.wrongDataType(target, timeAttr);
		}
		
		if (!columnAttr.isConvertable()) {
		    DataType.wrongDataType(target, columnAttr);
		}

		// Output:
		final Attribute timeOutAttr  = new Attribute(timeOut,  timeDT,  timeDT);
		final Attribute valueOutAttr = new Attribute(valueOut, columnDT, DataType.NUMBER);
		Attribute[] allAttributes = new Attribute[] { timeOutAttr, valueOutAttr};
		this.out = new OutputColumnset(target, allAttributes, this.connector.getConnection());

		// Prepare windowing:
		this.window = new Windowing(1, distance, this.connector);
		if (!window.open("SELECT " + columnAttr.getConvertedInput() + ", "
				 + timeAttr.getConvertedInput() + " FROM "  + source))
	    {
			DbConnector.infoOutput("Error while opening " + source);
			throw new TimeOperatorException("Could not read from table '" + source + "'!");
	    }
    }


    /**
     * Calculation function, if the average method should be used
     * @param lastVal  last calculated tail value
     * @param thisHead new head value
     * @return the average value
    */
    private double calcAverage(double lastVal, double thisHead) {
	return (lastVal == 0) ? thisHead :
	    lastVal  * this.getTailWeight() +
	    thisHead * this.getHeadWeight();
    }


    /** 
     * Method calc() reads from the column(s) specified in the constructor
     * and writes to the specified target, using windowing. 
     * <!-- Finally an index is created on the target table. -->
     */
    public void calc() throws TimeOperatorException {
		double lastVal = 0;
		double thisVal = 0;  
	    
		// Window array
		String[] ret = null;
		do {
		    ret = this.getWindow().getNextWnd();
	    	if (ret != null) {
				thisVal = calcAverage(lastVal, Double.parseDouble(ret[0]));
				lastVal = thisVal;

				// insert new value into target table
				Object[] i = new Object[] {
			 		new Long(this.getWindow().getTimeLastEntry()),
		    		new Double(thisVal)
				};
	
				this.getOutputColumnset().insert(i);
	    	}
		} while (ret != null);

		// this.getOutputColumnset().createIndexOn(INDEXONATTRIB);
		// *** No index is created in this compiler version! ***

		this.getWindow().close();
		this.connector.close();
    }
    
    /**
     * This function has to be used in the database as a stored
     * procedure for calculating an EMA.
    */
    public static void dbEMA(Object dbc,
		    			     String inputTable, String timeAttrIn, String valueAttrIn,
			    			 String outputTable, String timeAttrOut, String valueAttrOut,
						     double headWeight,  double tailWeight,  int distance)
		throws SQLException, TimeOperatorException
    {
    	BusinessDbConnectionSource dbcCasted =
			(dbc instanceof BusinessDbConnectionSource) ?
				((BusinessDbConnectionSource) dbc) : null;
		
		EMF emf = new EMF(dbcCasted, inputTable, timeAttrIn, valueAttrIn,
			 			  outputTable, timeAttrOut, valueAttrOut,
						  headWeight, tailWeight, distance);
		emf.calc();
		emf = null;
    }
}
