/**_____________________________________________________________________________
 *
 *                                 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 ca.nrc.cadc.net.TransientException;
import ca.nrc.cadc.util.FileMetadata;
import ca.nrc.cadc.vos.DataNode;
import it.inaf.oats.vospacebackend.exceptions.ExceptionMessage;
import it.inaf.oats.vospacebackend.exceptions.VOSpaceBackendException;
import it.inaf.oats.vospacebackend.utils.ConfigReader;

import ca.nrc.cadc.vos.Node;
import ca.nrc.cadc.vos.server.NodeID;
import ca.nrc.cadc.vos.VOSURI;
import ca.nrc.cadc.vos.NodeNotFoundException;
import ca.nrc.cadc.vos.VOS;

import java.sql.SQLException;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.UUID;
import java.util.logging.Level;

import org.apache.log4j.Logger;
import org.apache.commons.io.FileUtils;

/**
 *
 * @author bertocco
 */
public class VOSpaceBackPosix implements VOSpaceBackend {
    
    private static String documentRoot;
    private static String tmpStorageRoot;
    private static final String CONFIG_FILE_NAME = "VOSpace.properties";
    private static final Logger log = Logger.getLogger(VOSpaceBackImplFactory.class);
    
    public VOSpaceBackPosix() throws VOSpaceBackendException {
        
        try {
            ConfigReader myConf = new ConfigReader(CONFIG_FILE_NAME);
        
            this.documentRoot = myConf.getProperty("fs.posix.document.root");     
            this.tmpStorageRoot = myConf.getProperty("fs.posix.tmp.storage.root"); 
        } catch (Exception e) {
            ExceptionMessage exMsg = new ExceptionMessage();
            log.debug(MessageFormat.format(exMsg.getMessage("UNABLE_TO_READ_PROPERTIES"), CONFIG_FILE_NAME));
            throw new VOSpaceBackendException(                   
               MessageFormat.format(exMsg.getMessage("UNABLE_TO_READ_PROPERTIES"), CONFIG_FILE_NAME));
            
        }
        log.debug("VOSpace Backend Document Root = " + this.documentRoot);
        log.debug("VOSpace Backend Temporary Document Root = " + this.tmpStorageRoot);
        
    }
    

    public boolean createFile(String vosuri, String tmp_file, String md5_sum) 
                           throws VOSpaceBackendException, SQLException, IOException {
        
        log.debug("Entering in vospacebackendposix.createfile");
        VOSpaceBackMetadata metadata = new VOSpaceBackMetadata();
        
        log.debug("md5_checksum received in backendposix.createFile : " + md5_sum);
        String relativePath = this.createPathFromString(md5_sum);
        
        // Gets metadata on frontend   
        DatabaseNodePersistenceExt dbNodePers = new DatabaseNodePersistenceExt();
        Node myNode = null;
        try {
            myNode = dbNodePers.get(new VOSURI(vosuri));
        } catch (Exception e) {
            log.debug("Exception getting node from persistence.");
        }
        NodeID nodeID = (NodeID)myNode.appData;
        Long myNodeID = nodeID.getID();
        
        if (!(myNode instanceof DataNode)) {
            log.debug("Node instance NOT found");
            throw new VOSpaceBackendException("Node instance NOT found");
        } 
        
        DataNode myDataNode = (DataNode)myNode;
        
        
        
        try {
            // Needs syncronisation BEGIN
            dbNodePers.setBusyState(myDataNode, VOS.NodeBusyState.notBusy, VOS.NodeBusyState.busyWithWrite);
        } catch (TransientException ex) {
            log.debug("TransientException in dbNodePers.setBusyState");
            java.util.logging.Logger.getLogger(VOSpaceBackPosix.class.getName()).log(Level.SEVERE, null, ex);
            throw new VOSpaceBackendException("TransientException in dbNodePers.setBusyState");
        }
        HashMap putReqData = metadata.setPutRequest(tmp_file, myNodeID.toString()); 
        
        boolean myresult = (boolean)putReqData.get("ifSuccessful");
        if(!myresult) {
            log.debug("Fails updating table NodeStoredFileAndNode");
            throw new VOSpaceBackendException("Node instance NOT found");
        }   
        
        // sets backend metadata
        HashMap fileMetadata = metadata.setFile(tmp_file, md5_sum, relativePath);
        
        // Writes the file on the file system
        boolean result = (boolean)fileMetadata.get("ifSuccessful");
        if (result) {
            log.debug("File metadata successfully saved. I'm going to store the file content");
            this.fileFromTmpToFinalStorageArea(tmp_file, relativePath, "MOVE"); 
        } else
            log.debug("File backend metadata NOT saved. Need to abort the operation");
        // Set front-end metadata and not busy
        FileMetadata nodeMetadata = new FileMetadata();
        nodeMetadata.setMd5Sum(md5_sum);
        // INSERT A CORRECT FileSize !!!!!!!!!!!!!!!!!!!!
        nodeMetadata.setContentLength(345678L);
        //nodeMetadata.setContentLength("file_size");
        try {
            dbNodePers.setFileMetadata(myDataNode, nodeMetadata, false);
        } catch (Exception e) {
            log.debug("Exception doing databasePersistence.setFileMetadat.");
            throw new VOSpaceBackendException("Node instance NOT found");
        }
   
        // Needs syncronisation END
        return result;
                   
    }
    
    /* Retrieve the file, copy it in the temporary location, return the File */
    public File returnFile(String orig_f_name) 
               throws VOSpaceBackendException, SQLException, IOException {
        
        // Check if fileName is present in metadata DB
        VOSpaceBackMetadata metadataDB = new VOSpaceBackMetadata();
        
        HashMap myMetadata;
        File outFile = null;
        
        myMetadata = metadataDB.getFile(orig_f_name);
        
        if ((boolean)myMetadata.get("ifSuccessful")) {
            log.debug("Metadata record found for file " + orig_f_name);
            log.debug("storedFileName = " + (String)myMetadata.get("stored_file_name"));
            log.debug("relative_path = " + (String)myMetadata.get("relative_path"));
            log.debug("md5_checksum = " + (String)myMetadata.get("md5_checksum"));
            log.debug("original_file_name = " + (String)myMetadata.get("original_file_name"));
            String storedFileName = (String)myMetadata.get("stored_file_name");
            String relativePath = (String)myMetadata.get("relative_path");
            String unique_file_id_str = UUID.randomUUID().toString();
            // Needs syncronisation START
            this.fileFromStorageAreaToTmp(storedFileName, unique_file_id_str, relativePath, "COPY");
            outFile = new File(this.getTmpPath() + storedFileName);
            return outFile;
            // Needs syncronisation END
        } else {
            log.debug("Metadata record NOT found for file " + orig_f_name);
            outFile = null;
            return outFile;
        }

    }

    
    public boolean deleteFile(String orig_f_name) 
               throws VOSpaceBackendException, SQLException {
        
        boolean deleted = false;
        
        // Check if fileName is present in metadata DB
        VOSpaceBackMetadata metadataDB = new VOSpaceBackMetadata();
        
        HashMap myMetadata;
        File outFile = null;
        
        myMetadata = metadataDB.getFile(orig_f_name);
        log.debug("Value of ifSuccessful = " +(boolean)myMetadata.get("ifSuccessful"));
        
        if ((boolean)myMetadata.get("ifSuccessful")) {            
            String storedFileName = (String)myMetadata.get("stored_file_name");
            String relativePath = (String)myMetadata.get("relative_path");
            File fileToDelete = new File(this.getStoragePath(relativePath) + storedFileName);
            log.debug("I'm going to delete from DB the metadata set of file " + orig_f_name);
            // Needs syncronisation START
            metadataDB.deleteFile(orig_f_name);
            log.debug("Metadata set of file " + orig_f_name + " successfully deleted from DB");
            log.debug("I'm going to delete from file system " + fileToDelete);
            deleted = fileToDelete.delete();
            // Needs syncronisation END
            if (deleted)
                log.debug("File successfully deleted");
            else
                log.debug("Failed to delete the file");
        } else {
            log.debug("Failed to delete the file");
            deleted = false;
        }
            
        return deleted;
        
    }
    
    public String createPathFromString(String initialStr) throws VOSpaceBackendException{
        
        log.debug("initialStr = " + initialStr);
        log.debug("initialStr.substring(initialStr.length()-2, initialStr.length())" + initialStr.substring(initialStr.length()-2));
        log.debug("initialStr.length()-4, initialStr.length()-2)" + initialStr.substring(initialStr.length()-4, initialStr.length()-2)); 
        
        String relativePath = null;
        try{
            relativePath = new String(File.separator + 
                              initialStr.substring(initialStr.length()-4, initialStr.length()-2) +                              
                              File.separator +
                              initialStr.substring(initialStr.length()-2, initialStr.length()));
        } catch (Exception e) {
            log.debug("Exception creating partial path from string " + initialStr);
            throw new VOSpaceBackendException(e);
        }
        log.debug("relative path = " + relativePath);
        
        return relativePath;
        
    }
    
    public void fileFromTmpToFinalStorageArea(String tmpfile, String relPath, String operation)
                                 throws VOSpaceBackendException {
    
        
        File tmpFile = new File(this.getTmpPath() + tmpfile);  
        File finalStoredFile = new File(this.getStoragePath(relPath) + tmpfile);  
        
        log.debug("tmpStoredFile is: " + tmpFile);
        log.debug("finalStoredFile is: " + finalStoredFile);
        
        this.operateOnFiles(tmpFile, finalStoredFile, operation);
               
    }
    
    public void fileFromStorageAreaToTmp(String stored, String tmp, String relativePath, String operation)
                                 throws VOSpaceBackendException {
        
        File storedFile = new File(this.getStoragePath(relativePath) + stored);
        File tmpFile = new File(this.getTmpPath() + tmp);
         
        log.debug("storedFile is: " + storedFile);
        log.debug("tmpFile is: " + tmpFile);

        this.operateOnFiles(storedFile, tmpFile, operation);

    }
    
    
    public void operateOnFiles (File A, File B, String operation) throws VOSpaceBackendException {
                    
        log.debug("File A is: " + A);
        log.debug("File B is: " + B);
        log.debug("Operation required is " + operation);

        switch (operation) {
            case "MOVE":  
                this.moveFileAToFileB(A, B);
                break;
            case "COPY":  
                this.copyFileAToFileB(A, B);
                break;
            default: 
                log.debug("Error in operation required");
                throw new VOSpaceBackendException("Error in operation required");
        }
        
    }
        
        
    public boolean checksBeforeCopyOrMove(File A, File B)
                                            throws VOSpaceBackendException {
        
        boolean checkOK = false;
    
        if (!A.exists()) {
            log.debug("Move operation impossible: source file" + A.getAbsolutePath() 
                                                          + "does not exists.");
            throw new VOSpaceBackendException("Operation impossible: source file" 
                                              + A.getAbsolutePath() + "does not exists.");             
        }       
        
        String absolutePathB = B.getAbsolutePath();
        String pathB = absolutePathB.substring(0,absolutePathB.lastIndexOf(File.separator)); 
        File pathBFile = new File(pathB);
        if (!pathBFile.exists()) {
            try {
                checkOK = pathBFile.mkdirs();
            } catch (Exception e) {
                log.debug("Exception creating the final destination directory of file "
                                                      + B.getAbsolutePath());
                throw new VOSpaceBackendException(e);                
            }
        } else if (pathBFile.isDirectory()){
            checkOK = true;
        } else {
            log.debug("File " + pathB + " already exsists, but is not a directory.");
            checkOK = false;
        }
        
        return checkOK;
    
    }

    public void moveFileAToFileB (File A, File B) throws VOSpaceBackendException {
                    
        if (this.checksBeforeCopyOrMove(A, B)) {
            try {
                FileUtils.moveFile(A, B);
            } catch (Exception e) {
                log.debug("Exception moving temporary copy of uploaded file in its final destination directory");
                throw new VOSpaceBackendException(e);                
            }
        }
        
    }
    
    public void copyFileAToFileB (File A, File B) throws VOSpaceBackendException {
                    
        if (this.checksBeforeCopyOrMove(A, B)) {
            try {
                FileUtils.copyFile(A, B);
            } catch (Exception e) {
                log.debug("Exception moving temporary copy of uploaded file in its final destination directory");
                throw new VOSpaceBackendException(e);                
            }
        }
        
    }
    
    private String getStoragePath(String relativePath) {
        
        String storagePath = this.documentRoot + relativePath + File.separator;
        return storagePath;
        
    }
    
    private String getTmpPath() {
        
        String tmpFilePath = this.tmpStorageRoot + File.separator;
        return tmpFilePath;
        
    }
        
}
