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

public class SimpleSTSP {

    private final DbConnector connector;
    private final Windowing   window; // for the input
    private final OutputColumnset out; // for the output
    private final double          tolerance;

    private static final int INDEXONATTRIB = 2;

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

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

    protected double getTolerance() {
	return this.tolerance;
    }

    /**
     * @param dbc source of database connection if not used as stored procedure
     * @param source source table
     * @param time   time column in source table
     * @param column source column in source table
     *
     * @param target     target table to be created
     * @param timeStart  column for the start of the time intervals in the target
     * @param timeEnd    column for the end of the time intervals in the target
     * @param averageCol attribute for the average of intervals in the target
     * @param incCol     attribute for the increase within intervals in the target
     *
     * @param tolerance parameter specifying allowed deviation within one interval
    */
    public SimpleSTSP(BusinessDbConnectionSource dbc,
    		  String source, String time, String column, String target,
		      String timeStart, String timeEnd, String averageCol,
		      String incCol, double tolerance)
	throws SQLException, TimeOperatorException
	{
		this.connector = new DbConnector(dbc);
		this.tolerance = Math.abs(tolerance);
		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, DataType.NUMBER);
		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 timeStartAttr =
			new Attribute(timeStart, timeDT, DataType.NUMBER);
		final Attribute timeEndAttr =
			new Attribute(timeEnd, timeDT, DataType.NUMBER);
		final Attribute averageColAttr =
			new Attribute(averageCol, columnDT, DataType.NUMBER);
		final Attribute incColAttr =
			new Attribute(incCol, columnDT, DataType.NUMBER);
		Attribute[] allAttributes =
			new Attribute[] {
				timeStartAttr,
				timeEndAttr,
				averageColAttr,
				incColAttr };
		this.out =
			new OutputColumnset(
				target,
				allAttributes,
				this.connector.getConnection());
		// Prepare Windowing:
		this.window = new Windowing(1, 1, this.connector);
		if (!this
			.window
			.open(
				"SELECT "
					+ columnAttr.getConvertedInput()
					+ ", "
					+ timeAttr.getConvertedInput()
					+ " FROM "
					+ source)) {
			DbConnector.infoOutput("Error while opening " + source);
			throw new TimeOperatorException(
				"Could not read from table '" + source + "'!");
		}
	}
 

        
    /**
     * 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. -->
     * <p>
     * The calculation itself: Always starts a new interval which is
     * filled until the tolerance criterion for adding another value is
     * violated. Stops, if there is no new value for starting another
     * interval.</p>
     */
    public void calc() throws TimeOperatorException {
		TimeValue tv = nextTimeValue();
		if (tv == null) {
		    return;
		}
		while (tv != null) {
		    tv = startNewInterval(tv);
		}

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

    /**
     * @param values a <i>TimeValueArray</i> object to be inserted
     * into the target table
     * */
    private void insert(TimeValueArray values) throws TimeOperatorException {
		Object[] i = new Object[] { new Long(values.getStartTime()),
				    new Long(values.getLatestTime()),
				    new Double(values.getAverageValue()),
				    new Double(values.getIncreaseByTime()) };
		this.getOutputColumnset().insert(i);
    }


    private TimeValue startNewInterval(TimeValue tv) throws TimeOperatorException
    {
		if (tv == null) {
		    return null;
		}
		TimeValueArray values = new TimeValueArray(tv);

		// Add to 'values' until end of input table or the
		// 'intoTimeValueArray' returns false:
		while ( (tv = nextTimeValue()) != null && intoTimeValueArray(values, tv) );
	
		// Then insert the interval as a new tuple into the target table:
		insert(values);
		
		// Return the TimeValue object not inserted, or 'null' if end of table:
		return tv;
    }


    /**
     * This method encapsulates the criterion for rejecting to add a
     * 'TimeValue' to an existing interval. In this case the method
     * returns 'false'.
     *
     * The criterion used: extrapolating by average increase over time
     * differs by more than 'tolerance' (parameter of the operator)
     * from the new 'TimeValue'.
    */
    private boolean intoTimeValueArray(TimeValueArray values, TimeValue tv)
    {
		double refValue = values.extrapolateValueFor(tv.getTimeAsLong());
		if (Math.abs(refValue - tv.getValue()) > this.getTolerance()) {
		    return false;
		}
		values.add(tv);
		return true;
    }


    /**
     * Reads a time-value tuple from the database.
     * @return a 'TimeValue' object representing the read time-value tuple.
     * @throws NumberFormatException if the read tuple cannot be parsed
     * */
    private TimeValue nextTimeValue() throws NumberFormatException
    {
		String[] ret = window.getNextWnd();
		if ((ret == null || ret.length < 1)) {
		    return null;
		}
		return (new TimeValue(window.getTimeFirstEntry(), ret[0]));
    }


    /** 
     * This function has to be used in the database as stored
     * procedure to calculate a simple aggregation of time series to
     * intervals.  For parameter information please refer to the
     * documentation of the constructor.
     */
    public static void dbSTSP(Object dbc,
    			  String source, String time, String column,
			      String target, String timeStart, String timeEnd,
			      String averageCol, String incCol,
			      double tolerance)
	throws SQLException, TimeOperatorException
    {
    	BusinessDbConnectionSource dbcCasted =
			(dbc instanceof BusinessDbConnectionSource) ?
				((BusinessDbConnectionSource) dbc) : null;
		
		SimpleSTSP stsp = new SimpleSTSP(dbcCasted, source, time, column, target, timeStart,
										 timeEnd, averageCol, incCol, tolerance);
		stsp.calc();
		stsp = null;
    }

}
