/*
 * Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package lib;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 *
 * @author Alistair Neil <info@dazzleships.net>
 */
public class SimpleFile {

    public static final int PERIOD_MINUTES = 0;
    public static final int PERIOD_HOURS = 1;
    public static final int PERIOD_DAYS = 2;
    public static final int NOTFOUND = 0;
    public static final int FILEDELETEOK = 1;
    public static final int FILEDELETEFAIL = 2;
    private final int blockreadsize = 1024;
    private BufferedReader br;
    private BufferedWriter bw;
    private ZipOutputStream zos;
    private File f;
    private String filter = "";

    public SimpleFile() {
    }

    /**
     * Constructor
     *
     * @param pathname
     */
    public SimpleFile(String pathname) {
        f = new File(pathname);
    }

    /**
     * Set filename
     *
     * @param pathname
     */
    public void setFileName(String pathname) {
        f = new File(pathname);
    }

    /**
     * Set file
     *
     * @param file
     */
    public void setFile(File file) {
        f = file;
    }

    /**
     * Get file
     *
     * @return file
     */
    public File getFile() {
        return f;
    }

    /**
     * Close file and any resources such as buffered readers/writers that are
     * attached to it.
     */
    public void closeFile() {
        if (br != null) {
            try {
                br.close();
            } catch (IOException ex) {
                Logger.getLogger(SimpleFile.class.getName()).log(Level.SEVERE, null, ex);
            }
            br = null;
        }
        if (bw != null) {
            try {
                bw.close();
            } catch (IOException ex) {
                Logger.getLogger(SimpleFile.class.getName()).log(Level.SEVERE, null, ex);
            }
            bw = null;
        }
    }

    /**
     * Sets the file filter
     *
     * @param filter
     */
    public void setFileFilter(String filter) {
        this.filter = filter;
    }

    /**
     * Gets the current file filter
     *
     * @return Returns the file filter
     */
    public String getFileFilter() {
        return filter;
    }

    public static String getSeparator() {
        return File.separator;
    }

    /**
     * Creates a new folder
     *
     * @return True if successful
     */
    public boolean createFolder() {
        if (f.exists()) {
            return true;
        }
        return f.mkdirs();
    }

    /**
     * Creates a new folder
     *
     * @param path The location of the new folder
     * @return True if successful
     */
    public static boolean createFolder(String path) {
        File fl = new File(path);
        if (fl.exists()) {
            return true;
        }
        return fl.mkdirs();
    }

    /**
     * Create a new file
     *
     * @return True if file exist or is created
     */
    public boolean createFile() {
        boolean createNewFile = true;
        if (!f.exists()) {
            try {
                createFolder(f.getParent());
                createNewFile = f.createNewFile();
            } catch (IOException ex) {
                createNewFile = false;
            }
        }
        return createNewFile;
    }

    /**
     * Gets the file list at the specified directory location based on filter
     * contents
     *
     * @return An array of Files
     */
    public File[] getFileList() {

        File[] arrFilesCache;
        if (filter.isEmpty()) {
            arrFilesCache = f.listFiles();
        } else {
            arrFilesCache = f.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.getPath().contains(filter);
                }
            });
        }
        return arrFilesCache;
    }

    /**
     * Deletes the supplied list of files, be very carefull with this
     *
     * @param files
     */
    public void deleteFileList(File[] files) {
        for (File fx : files) {
            if (fx.isFile()) {
                boolean delete = fx.delete();
            }
        }
    }

    /**
     * Delete folder and its contents using specified folder path
     *
     * @param folder
     * @return boolean True if successful
     */
    public static boolean deleteFolder(String folder) {
        if (OSFunction.isLinux() && folder.contentEquals("/")) {
            return false;
        }
        return deleteFolder(new File(folder));
    }

    /**
     * Delete a folder and all of its contents works recursively
     *
     */
    private static boolean deleteFolder(File folder) {
        if (!folder.exists()) {
            return false;
        }
        for (File fx : folder.listFiles()) {
            if (fx.isDirectory()) {
                deleteFolder(fx);
            } else {
                boolean delete = fx.delete();
            }
        }
        return folder.delete();
    }

    /**
     * Wipe folder and its contents using specified folder path
     *
     * @param folder
     */
    public static void secureWipeFolder(String folder) {
        if (OSFunction.isLinux() && folder.contentEquals("/")) {
            return;
        }
        secureWipeFolder(new File(folder));
    }

    /**
     * Wipe folder and its contents using specified File
     *
     * @param folder
     */
    public static void secureWipeFolder(File folder) {
        if (!folder.exists()) {
            return;
        }
        for (File fx : folder.listFiles()) {
            if (fx.isDirectory()) {
                secureWipeFolder(fx);
            } else {
                secureWipe(fx);
            }
        }
        folder = renameTo(folder, "0000000");
        folder.delete();
    }

    /**
     * Wipe file using specified filename
     *
     * @param filename
     */
    public static void secureWipe(String filename) {
        secureWipe(new File(filename));
    }

    /**
     * Wipe file using specified File
     *
     * @param f
     */
    public static void secureWipe(File f) {

        if (!f.exists()) {
            return;
        }

        FileOutputStream fos = null;
        byte b[] = new byte[4096];
        long i = f.length() / 4096;
        int remainder = (int) (f.length() % 4096);
        byte r[] = new byte[remainder];
        try {
            // Overwrite with zeros
            fos = new FileOutputStream(f, false);
            while (i-- > 0) {
                fos.write(b);
                fos.flush();
            }
            fos.write(r);
            fos.close();
            // Zero file length
            b = new byte[0];
            fos = new FileOutputStream(f, false);
            fos.write(b);
        } catch (Exception ex) {
            Logger.getLogger(SimpleFile.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                // Rename file
                f = renameTo(f, "0000000000");
                // Delete file
                f.delete();
            } catch (Exception ex) {
            }
        }
    }

    public static File renameTo(File f, String newname) {
        String path = f.getParent();
        if (path != null) {
            path += File.separator + newname;
            File nf = new File(path);
            if (f.renameTo(nf)) {
                return nf;
            }
        }
        return f;
    }

    /**
     * Copy multiple files from one folder to another
     *
     * @param from The from path
     * @param to The to path
     * @param ignore
     */
    public static void copyFolderContents(String from, String to, String ignore) {
        try {
            File ourFile = new File(from);
            for (File fx : ourFile.listFiles()) {
                if (!ignore.contains(fx.getName())) {
                    copyFromTo(fx.getAbsolutePath(), to + fx.getName());
                }
            }
        } catch (Exception ex) {
            Logger.getGlobal().logp(Level.WARNING, "SimpleFile", "copyFolderContents()", "", ex);
        }
    }

    /**
     * Returns an array of filenames, array if populated from the offset value
     * onwards
     *
     * @param offset
     * @return An array of filenames
     */
    public String[] getFilenames(int offset) {

        String filename;
        File[] fileList = getFileList();
        String[] result = new String[fileList.length + offset];
        try {
            for (int i = 0; i < fileList.length; i++) {
                filename = fileList[i].getName();
                result[i + offset] = filename.substring(0, filename.indexOf('.'));
            }
            return result;
        } catch (Exception ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "getFilenames(int offset)", "", ex);
            return null;
        }
    }

    /**
     * Test to see if path exists
     *
     * @return Returns true if file exists
     */
    public boolean exists() {
        return f.exists();
    }

    /**
     * Test to see if path exists
     *
     * @param pathname The pathname to check for
     * @return Returns true if file exists
     */
    public static boolean exists(String pathname) {
        return new File(pathname).exists();
    }

    /**
     * Deletes file
     *
     * @return Returns true if successful
     */
    public boolean delete() {
        return f.delete();
    }

    /**
     * Deletes the specified file
     *
     * @param filename
     * @return Returns true if successful
     */
    public static boolean delete(String filename) {
        return new File(filename).delete();
    }

    /**
     *
     * @param toname
     * @return Returns true if successful
     */
    public boolean renameFile(String toname) {
        return f.renameTo(new File(toname));
    }

    /**
     * Opens a buffered file for read
     *
     */
    public void openBufferedRead() {

        try {
            br = new BufferedReader(new FileReader(f));
        } catch (FileNotFoundException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "openBufferedRead(int filehandle)", "", ex);
        }
    }

    /**
     * Use this if you need access to a file within a jar
     *
     */
    public void openBufferedResource() {

        try {
            String path = f.getPath();
            path = path.replace('\\', '/');
            InputStream is = getClass().getResourceAsStream(path);
            br = new BufferedReader(new InputStreamReader(is));
        } catch (Exception ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "openBufferedRead(int filehandle)", "", ex);
        }
    }

    /**
     * Opens a buffered file for append
     *
     */
    public void openBufferedAppend() {

        try {
            bw = new BufferedWriter(new FileWriter(f, true));
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "openBufferedAppend()", "", ex);
        }
    }

    /**
     * Read a single line from a text file
     *
     * @return Contents as String
     */
    public String readLine() {
        try {
            return br.readLine();
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "readLine(int filehandle)", "", ex);
        }
        return null;
    }

    /**
     * Read bytes from previously opened BufferedReader
     *
     * @return Returns the entire contents from a file
     */
    public String readEntireFile() {

        char[] cb = new char[blockreadsize];
        StringBuilder sb = new StringBuilder();
        int bytes;
        try {
            while (br.ready()) {
                bytes = br.read(cb);
                sb.append(cb, 0, bytes);
            }
            return sb.toString();
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "readEntireFile()", "", ex);
        }
        return "";
    }

    public FileInputStream openFileInputStream(String filename) {
        try {
            return new FileInputStream(new File(filename));
        } catch (FileNotFoundException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "openFileInputStream()", "", ex);
            return null;
        }
    }

    /**
     * Opens a file output stream
     *
     * @return Returns Opened file output stream
     */
    public FileOutputStream openFileOutputStream() {
        try {
            return new FileOutputStream(f);
        } catch (FileNotFoundException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "openFileOutputStream()", "", ex);
            return null;
        }
    }

    /**
     * Opens a buffered file for write
     *
     */
    public void openBufferedWrite() {

        try {
            bw = new BufferedWriter(new FileWriter(f));
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "openBufferedWrite()", "", ex);
        }
    }

    public void flushWriter() {
        try {
            bw.flush();
        } catch (IOException ex) {
        }
    }

    /**
     * Write to a previously opened output file
     *
     * @param data The data to be written
     * @param newlines The number of newlines to generate
     */
    public void writeFile(String data, int newlines) {

        try {
            bw.write(data);
            while (newlines > 0) {
                bw.newLine();
                newlines--;
            }
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(),
                    "writeToFile(" + data + "," + String.valueOf(newlines) + ")", "", ex);
        }
    }

    /**
     * Write to a previously opened output file
     *
     * @param data The character data to be written
     * @param nochars
     */
    public void writeFile(char[] data, int nochars) {

        try {
            bw.write(data, 0, nochars);
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "writeToFile()", "", ex);
        }
    }

    /**
     * Copy file to
     *
     * @param frompath
     * @param topath
     */
    public static void copyFromTo(String frompath, String topath) {
        try {
            Path source = new File(frompath).toPath();
            Path dest = new File(topath).toPath();
            Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, "SimpleFile", "copyFromTo()", "", ex);
        }
    }

    public static void mergeFiles(String srcpath1, String srcpath2, String destpath) {
        try (OutputStream os = new FileOutputStream(new File(destpath))) {
            Path source = new File(srcpath1).toPath();
            Files.copy(source, os);
            source = new File(srcpath2).toPath();
            Files.copy(source, os);
            os.close();
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, "SimpleFile", "mergeFiles", "", ex);
        }
    }

    /**
     * Get the age of a specified file
     *
     * @param filepath
     * @param period
     * @return Age of file as float
     */
    public static float getAgeOfFile(String filepath, int period) {
        File fx = new File(filepath);
        if (!fx.exists()) {
            return -1;
        }
        float fltAge = System.currentTimeMillis() - fx.lastModified();
        switch (period) {
            case PERIOD_MINUTES:
                return fltAge / 60000;
            case PERIOD_HOURS:
                return fltAge / (60000 * 60);
            case PERIOD_DAYS:
                return fltAge / (60000 * 60 * 24);
        }
        return -1;
    }

    /**
     * Test to see our file is older in minutes, returns true if it is
     *
     * @param filename
     * @param minutes
     * @return boolean
     */
    public static boolean isMinutesOlder(String filename, long minutes) {
        long lngAge = System.currentTimeMillis() - new File(filename).lastModified();
        return lngAge > (60000 * minutes);
    }

    /**
     * Test to see our file is older in hours, returns true if it is
     *
     * @param filename
     * @param hours
     * @return boolean
     */
    public static boolean isHoursOlder(String filename, long hours) {
        long lngAge = System.currentTimeMillis() - new File(filename).lastModified();
        return lngAge > (60000 * 60 * hours);
    }

    /**
     * Test to see our file is older in days, returns true if it is
     *
     * @param filename
     * @param days
     * @return boolean
     */
    public static boolean isDaysOlder(String filename, long days) {
        long lngAge = System.currentTimeMillis() - new File(filename).lastModified();
        return lngAge > (60000 * 60 * 24 * days);
    }

    /**
     * Opens a zipstream. returns false if it fails
     *
     * @param level
     * @return an open zipstream
     */
    public boolean openZipStream(int level) {
        try {
            zos = new ZipOutputStream(new FileOutputStream(f.getAbsolutePath()));
            if (level < 0 || level > 9) {
                level = 6;
            }
            zos.setLevel(level);
            return true;
        } catch (FileNotFoundException ex) {
        }
        return false;
    }

    /**
     * Close a zip stream
     */
    public void closeZipStream() {
        if (zos != null) {
            try {
                zos.close();
            } catch (IOException ex) {
            }
        }
    }

    /**
     * Add files to a zip file
     *
     * @param srcfiles An array of Files
     * @return boolean True if successful
     */
    public boolean addFilesToZip(File... srcfiles) {
        FileInputStream in = null;
        if (srcfiles == null || srcfiles.length == 0) {
            return false;
        }
        try {
            for (File fx : srcfiles) {
                in = new FileInputStream(fx);
                // name the file inside the zip  file
                zos.putNextEntry(new ZipEntry(fx.getName()));
                byte[] b = new byte[2048];
                int count;
                while ((count = in.read(b)) > 0) {
                    zos.write(b, 0, count);
                }
                in.close();
            }
            return true;
        } catch (IOException ex) {
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
            }
        }
        return false;
    }

    /**
     * Extracts a zip file to a supplied location
     *
     * @param destpath Destination for contents of zip file
     * @return True if successful
     */
    public boolean extractZipTo(String destpath) {
        FileOutputStream fos;
        final int BUFFER = 2048;
        try {
            FileInputStream fis = new FileInputStream(f);
            try (ZipInputStream zin = new ZipInputStream(new BufferedInputStream(fis))) {
                ZipEntry entry;
                while ((entry = zin.getNextEntry()) != null) {
                    int count;
                    byte data[] = new byte[BUFFER];
                    try {
                        fos = new FileOutputStream(destpath + File.separator + entry.getName());
                    } catch (FileNotFoundException ex) {
                        continue;
                    }
                    try (BufferedOutputStream dest = new BufferedOutputStream(fos, 2048)) {
                        while ((count = zin.read(data, 0, BUFFER)) != -1) {
                            dest.write(data, 0, count);
                        }
                        dest.flush();
                        dest.close();
                    }
                    fos.close();
                }
                zin.close();
                fis.close();
            }
            return true;
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "extractZipTo()", "", ex);
        }
        return false;
    }

}
