/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/

package fr.gouv.culture.sdx.repository;

import fr.gouv.culture.sdx.exception.SDXException;
import fr.gouv.culture.sdx.exception.SDXExceptionCode;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.ContextKeys;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.rdbms.hsql.HSQLDB;
import fr.gouv.culture.sdx.utils.save.SaveParameters;
import fr.gouv.culture.sdx.utils.save.Saveable;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;

import java.io.File;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

/**The same as JDBCRespository, but the method for the table creation query is overridden.
 *
 */
public class HSQLRepository extends JDBCRepository {

    protected String dbDirPath = null;
    protected HSQLDB hsqldb = null;
    protected String DATABASE_DIR_NAME = "_hsql";

    public String getDatabaseDirectoryName() {
        return DATABASE_DIR_NAME;
    }


    public void configure(Configuration configuration) throws ConfigurationException {
        super.configure(configuration);

        if (Utilities.checkString(super.dsi))
            return;//we have been given a dsi that should point to a server instance of HSQL

        //since we don't want to talk to cocoon for this  we should provide necessary setup
        //for communicating with the same database
        //should be similar to {TOMCAT_HOME}/webapps/{sdx | cocoonAppName}/WEB-INF/sdx/

        try {
            this.hsqldb = (HSQLDB) Utilities.getObjectFromContext(ContextKeys.SDX.Application.HSQL_DATABASE_OBJECT, getContext());
            if (this.hsqldb == null)
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_HSQLDB_NULL, null, null);
            if (!Utilities.checkString(this.hsqldb.getDbDirPath())) {
                //setting the path if it isn't already set
                String basePath = Utilities.getStringFromContext(ContextKeys.SDX.Application.DATABASE_DIRECTORY_PATH, super.getContext());
                if (Utilities.checkString(basePath))
                    this.dbDirPath = basePath + getDatabaseDirectoryName() + File.separator;

                if (Utilities.checkString(this.dbDirPath)) {
                    Utilities.checkDirectory(this.dbDirPath, super.getLog());
                    this.hsqldb.setDbDirPath(this.dbDirPath);
                }
            }

            super.tableName = super.getId();//using only the id now as this allows app to be more portable
        } catch (SDXException e) {
            throw new ConfigurationException(e.getMessage(), e);
        }


    }

    /** Returns an SQL query that could create the table.
     * <p>
     * This query should not have any parameter.
     * @return The query.
     */
    protected String getTableCreationQuery() {
        //changed dataType of FIELD_DATA_TRANSFORMED from "BINARY NOT NULL" TO only "BINARY"
        return "CREATE CACHED TABLE " + getTableName() + " ( " + FIELD_ID + " VARCHAR(255) NOT NULL, " + FIELD_DATA + " BINARY, PRIMARY KEY (" + FIELD_ID + "))";
    }
    
    /**
     * Implementation of statement with a limit parameter
     * HSQL implementation
	 * 	SELECT LIMIT &gt;offset&lt; &gt;number&lt; 
	 * 			DISTINCT * FROM &gt;table_name&lt;); 
	 */
	protected String getEntriesWithLimitQuery(long offset, long number) {
		String query = "SELECT LIMIT " + String.valueOf(offset) + " " + String.valueOf(number);
		query += " * FROM " + getTableName()+";";
		return query;
	}

    /** Initializes the repository.
     *
     * If there are no tables in the database,
     * we create the necessary table
     *
     * @throws SDXException  */
    public void init() throws SDXException {

        //verifying that we have a unique table name
        if (this.hsqldb != null) this.hsqldb.registerTableName(this.getTableName());

        /** First try to access the database and see if the tables exist. */
        Connection conn = null;
        ResultSet rs = null;

        JDBCRepositoryConnection jdbcConn = (JDBCRepositoryConnection) getConnection();
        try {
            conn = jdbcConn.getConnection();
            DatabaseMetaData dbmd = conn.getMetaData();
            //TODO: FIXME when hsql bug is correctect, using .toUpperCase() here to compensate for a bug in hsql
            rs = dbmd.getTables(null, null, getTableName().toUpperCase(), null);
            if (!rs.next()) {
                // The table doesn't exist, so we should create it.
                createTable(conn);
            }
        } catch (SQLException e) {
            String[] args = new String[2];
            args[0] = this.getId();
            args[1] = e.getMessage();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_INIT_REPO, args, e);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    String[] args = new String[2];
                    args[0] = this.getId();
                    args[1] = e.getMessage();
                    throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_CLOSE_RESULT_SET, args, e);
                }
            }
            releaseConnection(jdbcConn);
        }
    }

    protected String getTableName() {
        return handleUnsupportedTokens(super.getTableName());
    }

    /** Gets a connection for manipulating repository's content.
     * @return The connection.
     * @throws SDXException
     */
    public RepositoryConnection getConnection() throws SDXException {
        if (Utilities.checkString(super.dsi)) {
            return super.getConnection();
        } else {

            try {
                //local hsqldb in standalone mode
                JDBCRepositoryConnection conn = new JDBCRepositoryConnection(getSQLConnection());
                conn.enableLogging(super.getLog());
                return conn;
            } catch (SQLException e) {
                String[] args = new String[2];
                args[0] = this.getId();
                args[1] = e.getMessage();
                throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_GET_CONNECTION, args, e);
            }
        }

    }

    private synchronized Connection getSQLConnection() throws SDXException {
        if (this.hsqldb == null)
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_HSQLDB_NULL, null, null);

        return this.hsqldb.getSQLConnection();
    }

    public void releaseConnection(RepositoryConnection conn) throws SDXException {
        if (Utilities.checkString(super.dsi))
            super.releaseConnection(conn);
        else {
            //with hsqldb in stand alone mode we never want to loose the connection
            //so we do nothing here
        }
    }

    public synchronized void optimize() throws SDXException {
        JDBCRepositoryConnection jdbcConn = null;
        try {
            jdbcConn = new JDBCRepositoryConnection(getSQLConnection());
            Connection conn = jdbcConn.getConnection();
            String queryString = getOptimizeQuery();
            Template template = new Template(conn, queryString);
            template.execute(new QueryExecutor() {
            }, Template.MODE_EXECUTE_UPDATE);
        } catch (SDXException e) {
            String[] args = new String[1];
            args[0] = super.getId();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_OPTIMIZE, args, e);
        } catch (SQLException e) {
            String[] args = new String[1];
            args[0] = super.getId();
            throw new SDXException(super.getLog(), SDXExceptionCode.ERROR_OPTIMIZE, args, e);
        } finally {
            releaseConnection(jdbcConn);
        }
    }

    protected String getOptimizeQuery() {
        return "CHECKPOINT";
    }

    protected boolean initToSax(){
    	if(!super.initToSax())
    		return false;
    	else{
    		this._xmlizable_objects.put("Base_Directory",this.dbDirPath);
    		return true;
    	}
    }
    
    
	/** Save the repository
	 * @see fr.gouv.culture.sdx.utils.save.Saveable#backup(fr.gouv.culture.sdx.utils.save.SaveParameters)
	 */
	public void backup(SaveParameters save_config) throws SDXException {
		super.backup(save_config);
		if(save_config != null)
			if(save_config.getAttributeAsBoolean(Saveable.ALL_SAVE_ATTRIB,false))
			{
				save_config.setAttribute(Node.Name.TYPE,"HSQL");	
			}
	}
	
	/** Restore the repository
	 * @see fr.gouv.culture.sdx.utils.save.Saveable#restore(fr.gouv.culture.sdx.utils.save.SaveParameters)
	 */
	public void restore(SaveParameters save_config) throws SDXException {
		super.restore(save_config);
	}
}
