package it.inaf.oats.vospacebackend;

import it.inaf.oats.vospacebackend.implementation.VOSpaceBackPosix;
import it.inaf.oats.vospacebackend.implementation.VOSpaceBackMetadata;

import ca.nrc.cadc.util.PropertiesReader;
import it.inaf.oats.vospacebackend.implementation.VOSpaceBackend;
import it.inaf.oats.vospacebackend.implementation.VOSpaceBackImplFactory;
import it.inaf.oats.vospacebackend.exceptions.ExceptionMessage;
import it.inaf.oats.vospacebackend.exceptions.VOSpaceBackendException;
import it.inaf.oats.vospacebackend.utils.ConfigReader;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.UUID;
import java.util.HashMap;

import org.apache.log4j.Logger;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;  
import org.apache.commons.lang.exception.ExceptionUtils;

import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.restlet.resource.Delete;
import org.restlet.resource.ServerResource;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.representation.FileRepresentation;

import org.restlet.ext.fileupload.RestletFileUpload;
       
import org.restlet.data.MediaType;
import org.restlet.data.Status;

/**
 *
 * @author bertocco
 */
public class VOSpaceBackendResource extends ServerResource {
    
    protected Logger log = Logger.getLogger(VOSpaceBackendResource.class);

    @Post
    public Representation doPost(Representation entity) throws Exception {
        
    Representation result = null;
    log.info("Entering in POST operation");
    
    if (entity != null) {
        
        if (MediaType.MULTIPART_FORM_DATA.equals(entity.getMediaType(), true)) {
            log.info("Correctly Using MULTIPART_FORM_DATA");
            
            try {
                result = this.uploadFile(entity);
            } catch (Exception e) {                
                result = this.printMessage(e.getMessage());               
            }
            
            
        }  else {
            // POST request with unexpected type.
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            result = this.printMessage("POST request with unexpected type.");
        }    
            
            
        } else {
            // POST request with no entity.
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            result = this.printMessage("POST request with no entity.");
        }
        
    return result;
    
    }

    @Get
    public Representation doGet(){
        
        Representation result = null;
        log.info("Entering in GET operation");
        String fileName = (String)getRequestAttributes().get("fileToManage");
        log.debug("File to download is: " + fileName);
        try {       
            result = this.downloadFile(fileName);
        } catch (Exception e) {  
            //setStatus(Status.CLIENT_ERROR_NOT_FOUND);
            result = this.printMessage("GET request: failed to download file.");  
        }  
        
        return result;
    }
    
    
    @Delete
    public Representation doDelete(){
        
        Representation result;
        
        log.info("Entering in DELETE operation");
        String fileName = (String)getRequestAttributes().get("fileToManage");
        log.debug("File to delete is: " + fileName);
        try {       
            result = this.deleteFile(fileName);
        } catch (Exception e) {         
            result = this.printMessage("DELETE request: failed to delete file " + fileName);  
        }   
         
        return result;  
    }
    
    private Representation uploadFile(Representation entity) throws Exception {
                
        Representation result = null;
        
        // 1/ Create a factory for disk-based file items
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(1000240);
        log.info("Factory created");
        // 2/ Create a new file upload handler based on the Restlet
        // FileUpload extension that will parse Restlet requests and
        // generates FileItems.
        RestletFileUpload upload = new RestletFileUpload(factory);
        log.info("RestletFileUpload created");
        // 3/ Request is parsed by the handler which generates a
        // list of FileItems
        FileItemIterator fileIterator = null;
        try {
            fileIterator = upload.getItemIterator(entity);
        } catch (Exception e) {
            log.info(e.toString());
        }
        log.info("Iterator created");

        // Process only the uploaded item called "fileToUpload"
        // and return back
        boolean found = false;            
        while (fileIterator.hasNext() && !found) {
            FileItemStream fi = fileIterator.next(); 
            if (fi.getFieldName().equals("fileToUpload")) {
                found = true;
                // Read the original file name
                String origFileName = fi.getName();
                // Check if fileName is present in metadata DB
                VOSpaceBackMetadata metadataDB = new VOSpaceBackMetadata();        
                HashMap myMetadata;    
                myMetadata = metadataDB.getFile(origFileName);
                if ((boolean)myMetadata.get("ifSuccessful")) {
                    log.debug("File Name already present in the database");
                    result = this.printMessage("File Name already present in the database.");
                } else {
                    // Read the file content
                    InputStream is = fi.openStream();
                    byte[] data = IOUtils.toByteArray(is);
                    // Calculate the file MD5 checksum (so will not need to re-read 
                    // the file a new time later
                    String md5sum = new String(DigestUtils.md5Hex(data));
                    log.debug("MD5Sum del file: " + md5sum);  
                    // Calculate a unique file name to store the file                
                    String unique_file_id_str = UUID.randomUUID().toString();
                    log.debug("Unique file identifyer " + unique_file_id_str);
                
                    log.debug("FieldName = " + fi.getFieldName());
                    log.debug("Field " + fi.getFieldName() + " found");
                    // Get temporary document root from configuration file
                    String tmpStorageRoot = new String();
                    try {
                        ConfigReader myConf = new ConfigReader("VOSpace.properties");
                        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"), "VOSpace.properties"));
                        throw new VOSpaceBackendException(MessageFormat.format(
                             exMsg.getMessage("PROPERTY_NOT_FOUND"), "fs.posix.tmp.storage.root", "VOSpace.properties"));
                    }
                    // Create the temporary directory, if needed
                    File path = new File(tmpStorageRoot);
                    if (!path.exists()) {
                        boolean status = path.mkdirs();
                    }
                    // Seve the temporary file in temporary location with the new unique name
                    File savedUploadedFile = new File(path + File.separator + unique_file_id_str);
                    FileOutputStream outStream = new FileOutputStream(savedUploadedFile);
                    outStream.write(data);
                    outStream.close();
                    if (this.storeUploadedFile(unique_file_id_str, origFileName, md5sum)) {  
                        setStatus(Status.SUCCESS_OK); 
                        result = this.printMessage("File successfully uploaded");
                    } else {                  
                        result = this.printMessage("File NOT Uploaded! Something went wrong.");
                    }
                   
                }
            
            }
        }
        
        return result;
        
    }
    
    private boolean storeUploadedFile(String tmp_file_name, String orig_f_name, 
                                      String md5_sum) throws Exception {
        boolean stored = false;
        log.debug("Entering in storeUploadedFile");
        VOSpaceBackImplFactory myVOSpaceFactory = new VOSpaceBackImplFactory(); 
        log.debug("myVOSpaceFactory created");
        VOSpaceBackend myVOSpace = myVOSpaceFactory.getVOSpaceBackImpl();
        log.debug("myVOSpace get");
        stored = myVOSpace.createFile(orig_f_name, tmp_file_name, md5_sum);
        log.debug("File stored: " + stored);
        return stored;
                
    }
    
    private Representation downloadFile(String fileName) throws Exception {
        
        Representation result;
        
        log.debug("Entering in downloadFile");
        VOSpaceBackImplFactory myVOSpaceFactory = new VOSpaceBackImplFactory(); 
        log.debug("myVOSpaceFactory created");
        VOSpaceBackend myVOSpace = myVOSpaceFactory.getVOSpaceBackImpl();
        log.debug("myVOSpace get");
        File fileToDownload = myVOSpace.returnFile(fileName);
        if (fileToDownload != null ) {  
            log.debug("File found, fileToDownload is not null");
            FileRepresentation fr = new FileRepresentation(fileToDownload.getAbsolutePath(),
                                                             MediaType.APPLICATION_OCTET_STREAM);
            result = fr; 
            setStatus(Status.SUCCESS_OK); 
 
        } else {  
            log.debug("File NOT found, fileToDownload is null");                     
            result = this.printMessage("Unable to download file. Something went wrong!"); 
            setStatus(Status.SERVER_ERROR_INTERNAL);
        }
        
        return result;
    }
    
    private Representation deleteFile(String fileName) throws Exception {
         
        Representation result = null;
                   
        log.debug("Entering in deleteFile");
        VOSpaceBackImplFactory myVOSpaceFactory = new VOSpaceBackImplFactory(); 
        log.debug("myVOSpaceFactory created");
        VOSpaceBackend myVOSpace = myVOSpaceFactory.getVOSpaceBackImpl();
        log.debug("myVOSpace delete");
        try {       
            if(myVOSpace.deleteFile(fileName)) {
                log.debug("DELETE request: file " + fileName +  " removed.");
                result = this.printMessage("DELETE request: file " + fileName +  " removed.");                 
                setStatus(Status.SUCCESS_OK); 
            } else {
                log.debug("DELETE request: failed to remove file " + fileName);
                result = this.printMessage("DELETE request: failed to remove file " + fileName); 
                setStatus(Status.SERVER_ERROR_INTERNAL);
            }
        } catch (Exception e) { 
            log.debug("DELETE request: failed to remove file " + fileName);
            result = this.printMessage("DELETE request: failed to remove file " + fileName); 
                setStatus(Status.SERVER_ERROR_INTERNAL);  
        }   
        
        return result;
    }
    
    
    private Representation printMessage(String error) {
        
            StringBuilder sb = new StringBuilder("");
            sb.append(error);
            sb.append("\n");
            return new StringRepresentation(sb.toString(), MediaType.TEXT_PLAIN);
    }

}
