package org.studiorailgun;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Utilities for dealing with files
 */
public class FileUtils {

    /**
     * Creates the gson instance
     */
    static {
        //init gson
        GsonBuilder gsonBuilder = new GsonBuilder();
        gson = gsonBuilder.create();
    }
    
    //used for serialization/deserialization in file operations
    static Gson gson;
    
    //maximum number of attempt to read the file
    static final int maxReadFails = 3;
    //Timeout duration between read attempts
    static final int READ_TIMEOUT_DURATION = 5;
    /**
     * Reads a file to a string
     * @param f The file
     * @return The string
     */
    public static String readFileToString(File f){
        String rVal = "";
        BufferedReader reader;
        try {
            reader = Files.newBufferedReader(f.toPath());
            int failCounter = 0;
            boolean reading = true;
            StringBuilder builder = new StringBuilder("");
            while(reading){
                if(reader.ready()){
                    failCounter = 0;
                    int nextValue = reader.read();
                    if(nextValue == -1){
                        reading = false;
                    } else {
                        builder.append((char)nextValue);
                    }
                } else {
                    failCounter++;
                    if(failCounter > maxReadFails){
                        reading = false;
                    } else {
                        try {
                            TimeUnit.MILLISECONDS.sleep(READ_TIMEOUT_DURATION);
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                    }
                }
            }
            rVal = builder.toString();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return rVal;
    }
    
    
    public static String readStreamToString(InputStream resourceInputStream){
        String rVal = "";
        BufferedReader reader;
        try {
            reader = new BufferedReader(new InputStreamReader(resourceInputStream));
            int failCounter = 0;
            boolean reading = true;
            StringBuilder builder = new StringBuilder("");
            while(reading){
                if(reader.ready()){
                    failCounter = 0;
                    int nextValue = reader.read();
                    if(nextValue == -1){
                        reading = false;
                    } else {
                        builder.append((char)nextValue);
                    }
                } else {
                    failCounter++;
                    if(failCounter > maxReadFails){
                        reading = false;
                    } else {
                        try {
                            TimeUnit.MILLISECONDS.sleep(READ_TIMEOUT_DURATION);
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                    }
                }
            }
            rVal = builder.toString();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return rVal;
    }
    
    
    /**
     * Sanitizes a relative file path, guaranteeing that the initial slash is correct
     * @param filePath The raw file path
     * @return The sanitized file path
     */
    public static String sanitizeFilePath(String filePath){
        String rVal = new String(filePath);
        rVal = rVal.trim();
        if(rVal.startsWith("./")){
            return rVal;
        } else if(!rVal.startsWith("/")){
            rVal = "/" + rVal;
        }
        return rVal;
    }
    
    /**
     * Serializes an object to a filepath
     * @param filePath The filepath
     * @param object The object
     */
    public static void serializeObjectToFilePath(String filePath, Object object){
        Path path = new File(filePath).toPath();
        try {
            Files.write(path, gson.toJson(object).getBytes());
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Loads an object from the assets folder
     * @param <T> The type of object
     * @param file The file to load from
     * @param className The class of the object inside the file
     * @return The file
     */
    public static <T>T loadObjectFromFile(File file, Class<T> className){
        T rVal = null;
        try {
            rVal = gson.fromJson(Files.newBufferedReader(file.toPath()), className);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return rVal;
    }
    
    /**
     * Checks if a directory exists
     * @param fileName
     * @return true if directory exists, false otherwise
     */
    public static boolean checkFileExists(String fileName){
        File targetDir = new File(sanitizeFilePath(fileName));
        if(targetDir.exists()){
            return true;
        } else {
            return false;
        }
    }
    
    
    /**
     * Trys to create a directory
     * @param directoryName
     * @return true if directory was created, false if it was not
     */
    public static boolean createDirectory(String directoryName){
        String sanitizedPath = sanitizeFilePath(directoryName);
        File targetDir = new File(sanitizedPath);
        if(targetDir.exists()){
            return false;
        } else {
            return targetDir.mkdirs();
        }
    }
    
    /**
     * Lists the files in a directory
     * @param directoryName The path of the directory
     * @return A list containing the names of all files inside that directory
     */
    public static List<String> listDirectory(String directoryName){
        List<String> rVal = new LinkedList<String>();
        String sanitizedPath = sanitizeFilePath(directoryName);
        File targetDir = new File(sanitizedPath);
        String[] files = targetDir.list();
        for(String name : files){
            rVal.add(name);
        }
        return rVal;
    }

    /**
     * Recursively deletes a path
     * @param path The path
     */
    public static void recursivelyDelete(String path){
        File file = new File(path);
        if(file.isDirectory()){
            for(File child : file.listFiles()){
                recursivelyDelete(child.getAbsolutePath());
            }
        }
        if(file.exists()){
            try {
                Files.delete(file.toPath());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    /**
     * Gets a file's path relative to a given directory
     * @param file The file
     * @param directory The directory
     * @return The relative path
     */
    public static String relativize(File file, File directory){
        return directory.toURI().relativize(file.toURI()).getPath();
    }
    
    

    /**
     * Computes the checksum of an object
     * @param object The object
     * @return The checksum
     * @throws IOException Thrown on io errors reading the file
     * @throws NoSuchAlgorithmException Thrown if MD5 isn't supported
     */
    public static String getChecksum(Serializable object) throws IOException, NoSuchAlgorithmException {
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(baos.toByteArray());
            StringBuffer builder = new StringBuffer();
            for(byte byteCurr : bytes){
                builder.append(String.format("%02x",byteCurr));
            }
            return builder.toString();
        } finally {
            oos.close();
            baos.close();
        }
    }


    
    
}
