/**_____________________________________________________________________________
 *
 *                                 OATS - INAF
 *  Osservatorio Astronomico di Tireste - Istituto Nazionale di Astrofisica
 *  Astronomical Observatory of Trieste - National Institute for Astrophysics
 * ____________________________________________________________________________
 *
 * Copyright (C) 20016  Istituto Nazionale di Astrofisica
 * 
 * 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 2
 * 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, write to the Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * _____________________________________________________________________________
 **/

package it.inaf.oats.vospacebackend.implementation;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ResultSet;
import java.util.HashMap;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.log4j.Logger;

import it.inaf.oats.vospacebackend.exceptions.ExceptionMessage;
import it.inaf.oats.vospacebackend.exceptions.VOSpaceBackendException;

/*
 * FileRecord myParams is used as input record
 * HashMap myResult is used as output record
 * @author bertocco
 */
public class VOSpaceBackMetadata {
    
    /**
     * This class has two attributes:
     * <p>
     * Logger to perform logging operations
     * <p>
     * HashMap which will contain the metadata set identifying a stored/to store file
     * This hashmap fields will are:
     * boolean ifSuccessfull if db query is successful or not
     * String "original_file_name"
     * String "stored_file_name"
     * String "md5_checksum"
     * String "relative_path"
     */
    
    protected Logger log;
    
    protected HashMap myResult; 

    /** 
     * Create the log and the HashMap that will be used to store the database
     * query result to return. The field "ifSuccessful" will contain a boolean 
     * value true if operation successfully executed and false if not.
     * Other fields will be added containing the query result set fields, 
     * depending on the executed query.
     */
    public VOSpaceBackMetadata() {
        
        log = Logger.getLogger(VOSpaceBackMetadata.class);  
        myResult = new <String, Object>HashMap();
        myResult.put("ifSuccessful", false);
        
    }
    /**
     * 
     * @param fileName Original file name (provided by the client) of the file to store
     * @param stored_f_name name used to store the file in the file system
     * @param md5Checksum MD5 checksum of the file to store
     * @param relativePath relative path (after the root) of the file to store
     * @return boolean true if operation successful false if not
     * @throws VOSpaceBackendException
     */
    public HashMap setFile(String stored_f_name, String md5Checksum, 
                         String relativePath) 
                                  throws VOSpaceBackendException, SQLException {
       
        String myOp = "SET";
        String myQuery = "INSERT INTO cadctest.StoredFiles " +
                "(stored_file_name, md5_checksum, relative_path)" +
                " VALUES (?, ?, ?);";
        FileRecord myParams = new FileRecord(stored_f_name,
                                                    md5Checksum, relativePath);
        
        
        myResult = excuteQuery(myOp, myQuery, myParams);
        
        return myResult;
        
    }
    
    public HashMap getFile(String orig_f_name) 
                                  throws VOSpaceBackendException, SQLException {
        
        String myOp = "GET";
        
        String myQuery = "SELECT * FROM cadctest.StoredFiles " +
                "WHERE original_file_name=?";
        
        FileRecord myParams = new FileRecord(orig_f_name, "", "");
        
        myResult = excuteQuery(myOp, myQuery, myParams);
        
        return myResult;
        
    }
    
    public boolean deleteFile(String orig_f_name) 
                                  throws VOSpaceBackendException, SQLException {
        
        String myOp = "DELETE";
        String myQuery = "DELETE FROM cadctest.StoredFiles WHERE original_file_name=?";
        
        FileRecord myParams = new FileRecord(orig_f_name, "", "");
        
        myResult = excuteQuery(myOp, myQuery, myParams);
        
        return (boolean)myResult.get("ifSuccessful");
        
    }
    
    
    public HashMap getPutRequest(String stored_f_name) 
                                  throws VOSpaceBackendException, SQLException {
        
        String myOp = "GET_PUT_REQ";
        
        String myQuery = "SELECT * FROM cadctest.NodeStoredFileAndNode " +
                "WHERE storedFileID=?";
        
        FileRecord myParams = new FileRecord(stored_f_name, "", "", "");
        
        myResult = excuteQuery(myOp, myQuery, myParams);
        
        return myResult;
        
    }
    
    
    public HashMap setPutRequest(String stored_f_name, String nodeID) 
                                  throws VOSpaceBackendException, SQLException {
       
        String myOp = "SET_PUT_REQ";
        String myQuery = "INSERT INTO cadctest.NodeStoredFileAndNode " +
                "(nodeID, storedFileID)" +
                " VALUES (?, ?);";
        FileRecord myParams = new FileRecord(stored_f_name, "", "", nodeID);
        
        
        myResult = excuteQuery(myOp, myQuery, myParams);
        
        return myResult;
        
    }
    
    private HashMap excuteQuery(String operation, String query, FileRecord fileToStore) 
                                                throws VOSpaceBackendException, SQLException {
        
        PreparedStatement preparedStatementInsert = null;
       
        Connection dbConnection = null;
        
        try {
	    dbConnection = getDBConnection();
            log.debug("Database connection get");
        } catch (SQLException e) {
            log.fatal(e);
            ExceptionMessage exMsg = new ExceptionMessage();
            throw new VOSpaceBackendException(ExceptionMessage.getMessage("UNABLE_TO_GET_DB_CONNECTION"));
	}

	try {
	    dbConnection.setAutoCommit(false);
            log.debug("Autocommit set false");
	} catch (SQLException e) {
            log.fatal(e);
            ExceptionMessage exMsg = new ExceptionMessage();
            throw new VOSpaceBackendException(ExceptionMessage.getMessage("ERROR_DISABLING_DB_AUTOCOMMIT"));
	}
        
	// Starts JDBC Transaction
        PreparedStatement preparedQuery = null;
	try {
            
            HashMap fileToStoreFields = fileToStore.getFileRecord();
            ResultSet rs = null;   
            int rowCounter = 0;
            switch (operation) {
                case "SET":
                    log.debug("Going to prepare query for SET operation");
                    preparedQuery = dbConnection.prepareStatement(query);
                    preparedQuery.setString(1, (String)fileToStoreFields.get("stored_file_name"));
                    preparedQuery.setString(2, (String)fileToStoreFields.get("md5_checksum"));
                    preparedQuery.setString(3, (String)fileToStoreFields.get("relative_path")); 
                    log.debug("Going to execute query");
                    preparedQuery.executeUpdate();
                    log.debug("Query executed");
                    dbConnection.commit();
                    log.debug("Query committed");                    
                    myResult.put("ifSuccessful", true);
                    break;
                case "GET":
                    preparedQuery = dbConnection.prepareStatement(query);
                    preparedQuery.setString(1, (String)fileToStoreFields.get("original_file_name"));
                    rs = preparedQuery.executeQuery();
                    rowCounter = 0;
                    while (rs.next()) {
                        rowCounter = rowCounter +1;
                        myResult.put("original_file_name", rs.getString("original_file_name"));
                        myResult.put("stored_file_name", rs.getString("stored_file_name"));
                        myResult.put("md5_checksum", rs.getString("md5_checksum"));
                        myResult.put("relative_path", rs.getString("relative_path")); 
                    }
                    if (rowCounter == 0) {
                        log.debug("GET: query successfully executed. File not found");                      
                        myResult.put("ifSuccessful", false);
                    } else {
                        log.debug("GET: query successfully executed. File found");
                        myResult.put("ifSuccessful", true);
                    }
                    break;
                case "DELETE": 
                    preparedQuery = dbConnection.prepareStatement(query);
                    preparedQuery.setString(1, (String)fileToStoreFields.get("original_file_name")); 
                    preparedQuery.executeUpdate();
                    dbConnection.commit();                    
                    myResult.put("ifSuccessful", true);
                    break;
                case "GET_PUT_REQ":
                    preparedQuery = dbConnection.prepareStatement(query);
                    preparedQuery.setString(1, (String)fileToStoreFields.get("nodeID"));
                    preparedQuery.setString(2, (String)fileToStoreFields.get("stored_file_name"));
                    rs = preparedQuery.executeQuery();
                    rowCounter = 0;
                    while (rs.next()) {
                        rowCounter = rowCounter +1;
                        myResult.put("stored_file_name", rs.getString("stored_file_name"));
                        myResult.put("nodeID", rs.getString("nodeID"));
                    }
                    if (rowCounter == 0) {
                        log.debug("GET_PUT_REQ: query successfully executed. File " + (String)fileToStoreFields.get("stored_file_name") + " not found");                      
                        myResult.put("ifSuccessful", false);
                    } else {
                        log.debug("GET_PUT_REQ: query successfully executed. File " + (String)fileToStoreFields.get("stored_file_name") + " found");
                        myResult.put("ifSuccessful", true);
                    }
                    break;
                case "SET_PUT_REQ":                   
                    log.debug("Going to prepare query for SET operation");
                    preparedQuery = dbConnection.prepareStatement(query);
                    preparedQuery.setString(1, (String)fileToStoreFields.get("nodeID"));
                    preparedQuery.setString(2, (String)fileToStoreFields.get("stored_file_name"));
                    log.debug("Going to execute query");
                    preparedQuery.executeUpdate();
                    log.debug("Query executed");
                    dbConnection.commit();
                    log.debug("Query committed");                    
                    myResult.put("ifSuccessful", true);
                    break;                    
                    
                default:                  
                    myResult.put("ifSuccessful", false);
                    ExceptionMessage exMsg = new ExceptionMessage();
                    log.fatal(ExceptionMessage.getMessage("DB_OPERATION_NOT_RECOGNIZED"));
                    throw new VOSpaceBackendException(ExceptionMessage.getMessage("DB_OPERATION_NOT_RECOGNIZED"));                    
                }
            
            dbConnection.setAutoCommit(true);
                        
            dbConnection.close();
            
        } catch (SQLException e) {

            log.error("SQLException exception executing SET file" + e.getMessage());
	    dbConnection.rollback();

	} finally {

            if (preparedQuery != null) {
		preparedQuery.close();
            }

            if (dbConnection != null) {
		dbConnection.close();
            }
            
	}
        
        return myResult;
       
    }
    
    private Connection getDBConnection() throws SQLException, VOSpaceBackendException {

        DataSource ds = this.getDataSource();
        Connection dbConnection = ds.getConnection();
        			               
	return dbConnection;

    }
    
    
    protected DataSource getDataSource() throws VOSpaceBackendException {
        
        DataSource ds = null;
        
        try {
            Context initContext = new InitialContext();
            Context envContext = (Context) initContext.lookup("java:comp/env");
            String vosNodesDataSourceName = "jdbc/cadctest";
            ds = (DataSource) envContext.lookup(vosNodesDataSourceName);
        } catch (NamingException ex) {
            log.fatal(ex);
            ExceptionMessage exMsg = new ExceptionMessage();
            throw new VOSpaceBackendException(ExceptionMessage.getMessage("DATABASE_DRIVER_NOT_FOUND"));
        }
        
        return ds;
    }
    
}
