
/*
 *  Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
*/
package oracle.javacloud.resource;

import oracle.javacloud.filebrowser.FileDesc;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;

/**
 * Provides root resource.
 *
 */
@Path("/")
public class AppResource {

    private static Logger logger = Logger.getLogger(AppResource.class.getName());

    /**
     * Method handling HTTP GET requests to list the contents of a directory with url pattern matching 'list/{dir.*}'.
     * @param dir directory to be listed
     * @return array of files in the directory as APPLICATION_JSON_TYPE response
     */
    @GET
    @Path("list/{dir:.*}")
    public Response listFiles(@PathParam("dir") String dir) {
        logger.info("trying to open directory: " + dir);
        File fs = new File("/", dir);
        try {
            File[] list = fs.listFiles();
            List<FileDesc> fileList = new ArrayList<FileDesc>();
            for (File f : list)  {
                FileDesc fd = new FileDesc();
                fd.setFilename(f.getName());
                fd.setDir(f.isDirectory());
                fd.setCanRead(f.canRead());
                fd.setCanDelete(f.getParentFile().canWrite());
                fd.setHidden(f.isHidden());
                fileList.add(fd);
            }
            logger.info("open directory: " + fs.getAbsolutePath());
            return Response.status(200).entity(fileList.toArray()).header("Content-Type", "application/json").type(MediaType.APPLICATION_JSON_TYPE).build();
        } catch (NullPointerException e) {
            if (fs.exists()) {
                logger.warning("access to directory: " + dir + " is denied");
                return Response.status(403).entity("access denied").type(MediaType.TEXT_PLAIN_TYPE).build();
            } else {
                logger.warning("directory: " + dir + " do not exist");
                return Response.status(404).entity("directory not exist").type(MediaType.TEXT_PLAIN_TYPE).build();
            }
        } catch (Throwable t) {
            logger.warning("open directory: " + dir + " error");
            logger.log(Level.WARNING, t.getMessage(), t);
            return Response.status(500).entity(t.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build();
        }
    }

    /**
     * Method handling HTTP GET requests to open the file with url pattern matching 'open/{filename.*}'.
     * @param filename file to be opened
     * @return content of file as TEXT_PLAIN_TYPE or APPLICATION_OCTET_STREAM_TYPE response according to the file MIME type
     */
    @GET
    @Path("open/{filename:.*}")
    public Response openFile(@PathParam("filename") String filename) {
        logger.info("trying to open file: " + filename);
        try {
            File fs = new File("/", filename);
            byte[] fileData = readFile(fs);
            Response.ResponseBuilder response = Response.status(200).entity(fileData);
            if (filename.endsWith(".txt")) {
                logger.info("open text file: " + fs.getAbsolutePath());
                response.type(MediaType.TEXT_PLAIN_TYPE);
            } else if (filename.endsWith(".pdf")) {
                logger.info("open pdf file: " + fs.getAbsolutePath());
                response.type("application/pdf");
            } else {
                logger.info("download file: " + fs.getAbsolutePath());
                response.type(MediaType.APPLICATION_OCTET_STREAM_TYPE);
            }
            return response.build();
        } catch (FileNotFoundException e) {
            logger.warning("file: " + filename + " not found");
            return Response.status(404).entity(e.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build();
        } catch (Throwable t) {
            logger.warning("open file: " + filename + " error");
            logger.log(Level.WARNING, t.getMessage(), t);
            return Response.status(500).entity(t.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build();
        }
    }

    /**
     * Method to read the content of file.
     * @param file file to be read
     * @return file content as byte array
     * @throws IOException
     */
    private byte[] readFile(File file) throws IOException {
        byte[] fileData = new byte[(int)file.length()];
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        DataInputStream dis = new DataInputStream(bis);
        dis.readFully(fileData);
        dis.close();
        bis.close();
        return fileData;
    }

    /**
     * Method handling HTTP PUT requests to create a new directory with url pattern matching 'create/{dir.*}'.
     * @param dir directory to be created
     * @return entity with different status code as response
     */
    @PUT
    @Path("create/{dir:.*}")
    public Response mkDir(@PathParam("dir") String dir) {
        logger.info("trying to create new directory: " + dir);
        try {
            File fs = new File("/", dir);
            if (fs.exists()) {
                logger.warning("directory: " + dir + " already exist");
                return Response.status(409).entity("directory exist").type(MediaType.TEXT_PLAIN_TYPE).build();
            }
            fs.mkdir();
            if (!fs.exists()) {
                logger.warning("create directory: " + dir + " error");
                return Response.status(500).entity("mkdir error").type(MediaType.TEXT_PLAIN_TYPE).build();
            }
            logger.info("create directory: " + fs.getAbsolutePath());
            return Response.status(201).entity(null).build();
        } catch (Throwable t) {
            logger.warning("create directory: " + dir + " error");
            return Response.status(500).entity(t.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build();
        }
    }

    /**
     * Method handling HTTP POST requests to upload a file with url pattern matching 'upload/{filename.*}'.
     * @param filename file to be uploaded
     * @return entity with different status code as response
     */
    @POST
    @Path("upload/{filename:.*}")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response uploadFile(
            @PathParam("filename") String filename,
            @FormDataParam("file") InputStream in,
            @FormDataParam("file")FormDataContentDisposition fileDesc) {
        String location = filename + "/" + fileDesc.getFileName();
        logger.info("trying to upload file: " + fileDesc.getFileName() + " to location: " + location);
        try {
            writeFile(location, in);
            logger.info("upload file: " + fileDesc.getFileName() + " to location: " + location);
            return Response.status(200).entity(null).build();
        } catch (Throwable t) {
            logger.warning(t.getMessage());
            return Response.status(500).entity(null).build();
        }

    }

    /**
     * Method to write inputstream to a file in specified location.
     * @param location
     * @param in inputstream
     * @throws IOException
     */
    private void writeFile(String location, InputStream in) throws IOException {
        BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(new File("/", location)));
        DataOutputStream dout = new DataOutputStream(bo);
        int read;
        while ((read = in.read()) != -1) {
            dout.write(read);
        }
        dout.flush();
        dout.close();
        bo.close();
    }

    /**
     * Method handling HTTP DELETE requests to delete a file/directory with url pattern matching 'del/{filename.*}'.
     * @param filename file to be deleted
     * @return entity with different status code as response
     */
    @DELETE
    @Path("del/{filename:.*}")
    public Response deleteFile(@PathParam("filename") String filename) {
        logger.info("trying to delete file: " + filename);
        try {
            File fs = new File("/", filename);
            if (fs.isDirectory() && fs.list().length > 0) {
                logger.warning("delete file error, " + fs.getAbsolutePath() + " is not empty");
                return  Response.status(405).entity(null).build();
            }
            logger.log(Level.FINER, "exist: " + fs.exists());
            if (!fs.exists()) {
                logger.warning("delete file error, " + fs.getAbsolutePath() + " does not exist");
                return Response.status(404).entity(null).build();
            }
            fs.delete();
            logger.info("delete file: " + fs.getAbsolutePath());
            return Response.status(201).entity(null).build();
        } catch (Throwable t) {
            logger.warning("delete file: " + filename + " error");
            return Response.status(500).entity(t.getMessage()).type(MediaType.TEXT_PLAIN_TYPE).build();
        }
    }
}
