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

class OutputColumnset {

    private final String      tableName;
    private final Attribute[] attributes;
    private final Connection  connection;


    private Connection getConnection() {
	return (this.connection); 
    }

    private String getTableName() {
	return (this.tableName);
    }

    private int getAttributesSize() {
	return (this.attributes.length);
    }

    private Attribute getAttribute(int i) {
	if (i >= 0 && i < getAttributesSize())
	    return (this.attributes[i]);
	return null;
    }

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

    public OutputColumnset(String tableName, Attribute[] attributes, Connection connection)
	throws TimeOperatorException
    {
	this.tableName  = tableName;
	this.attributes = attributes;
	this.connection = connection;

	// Validity checks
	if (this.tableName == null)
	    throw new TimeOperatorException
		("No table name specified in constructor of OutputColumnset.");

	if (this.attributes == null)
	    throw new TimeOperatorException
		("No columns specified in constructor of OutputColumnset.");
	for (int i=0; i<this.attributes.length; i++) {
	    if (this.attributes[i] == null)
		throw new TimeOperatorException
		    ("Null value found in array argument to constructor of OutputColumnset.");
	}

	if (this.connection == null)
	    throw new TimeOperatorException
		("No database connection specified in constructor of OutputColumnset.");


	// Delete the target table if it already exists.
	deleteDatabaseTable();

	// Then create the new target table.
	boolean created = createDatabaseTable();
	if (!created)
	    throw new TimeOperatorException
		("Could not create target table '" + this.getTableName() +
		 "' in constructor of OutputColumnset.");
    }


    public void insert(Object[] values) throws TimeOperatorException {
	if (values == null)
	    throw new TimeOperatorException
		("Trying to insert an array of <null> into table '" + this.getTableName() + "'.");
	
	if (values.length != this.getAttributesSize())
	    throw new TimeOperatorException
		("Trying to insert an array of wrong length into table '" + this.getTableName() + "'.");
	
	String sql = "INSERT INTO " + this.getTableName() + " VALUES ( ";

	for (int i=0; i<values.length; i++) {
	    final String val = this.getAttribute(i).getConvertedOutput(values[i]);
	    if (val == null)
		throw new TimeOperatorException
		    ("Trying to insert an object of unconvertable type into table '"
		     + this.getTableName() + "'.");
	    sql += val + (i < values.length - 1 ? ", " : " )");
	}

	DbConnector.infoOutput("DB Query: " + sql);

	if (!executeUpdateClose(sql))
	    throw new TimeOperatorException
		("An error occurred trying to insert values into table '"
		 + this.getTableName() + "'\nInsert statement was: " + sql);
    }


    /**
     * Creates an index on attribute number i of the output columnset.
     * @param i number of the attribute (see array for constructor) to
     *          create an index on
     * @return <i>true</i> iff an index could be created.
    */
    public boolean createIndexOn(int i) {
	final String attrName = this.getAttribute(i).getName();
	if (attrName == null)
	    return false;

	String sql = "CREATE INDEX " + this.getTableName() + "_idx ON "
	    + this.getTableName() + " ( " + attrName + " )";

	return (this.executeUpdateClose(sql));
    }

    /**
     * Creates an index on attributes number i and j of the output
     * columnset.
     * @param i number of the first attribute (see array for
     *        constructor) to create an index on
     * @param i number of the second attribute
     * @return <i>true</i> iff an index could be created.
    */
    public boolean createIndexOn(int i, int j) {
	if (i == j)
	    return this.createIndexOn(i);
	
	final String attrName1 = this.getAttribute(i).getName();
	final String attrName2 = this.getAttribute(j).getName();
	if (attrName1 == null || attrName2 == null)
	    return false;

	String sql = "CREATE INDEX " + this.getTableName() + "_idx ON "
	    + this.getTableName() + " ( " + attrName1 + ", " + attrName2 + " )";

	return (this.executeUpdateClose(sql));
    }





    // ------------------------------------------------------------------
    //                Helper methods of the constructor
    // ------------------------------------------------------------------

    /* deletes table with name <i>this.tableName</i>
     * @return <i>true</i> iff the table could be created */
    private boolean deleteDatabaseTable() {
	String delSql = "DROP TABLE " + this.getTableName();
	DbConnector.infoOutput("INFO: Deleting " + this.getTableName());

	return this.executeUpdateClose(delSql);
    }

    /* creates the table with name <i>this.tableName</i> and the
     * attributes specified in <i>this.attributes</i>
     * @return <i>true</i> iff the table could be created */
    private boolean createDatabaseTable() {
	String sql = "CREATE TABLE " + this.getTableName() + " (";

	final int size = this.getAttributesSize();
	for (int i=0; i<size; i++) {
	    final String attrName = this.getAttribute(i).getName();
	    final String attrType = this.getAttribute(i).getDataTypeName();
	    sql += attrName + " " + attrType + (i < size - 1 ? ", " : ")");
	}
	DbConnector.infoOutput("INFO: Creating " + sql);

	return this.executeUpdateClose(sql);
    }



    // ------------------------------------------------------------------
    //                Helper methods for database access
    // ------------------------------------------------------------------

    /* Reads an SQL string, does an <i>executeUpdate</i> and returns
     * if it succeded.  All exceptions are caught and result in return
     * value <i>false</i>. */
    private boolean executeUpdateClose(String sql) {
	if (sql == null)
	    return false;

	boolean   ret  = true;
	Statement stmt = null;
	
	try {
	    stmt = this.getConnection().createStatement();
	    stmt.executeUpdate(sql);
	} catch (SQLException e) {
	    DbConnector.infoOutput(e.getMessage());
	    ret = false;
	} finally { closeStatement(stmt); }

	return ret;
    }

    private static void closeStatement(Statement statement) {
	if (statement == null)
	    return;
	
	try {
	    statement.close();
	    statement = null;
	}
	catch (SQLException e) { DbConnector.infoOutput(e.getMessage()); }
    } 

}
