package com.capgemini.framework.common;

import com.capgemini.reports.exceptions.NotFoundException;
import com.google.common.io.Files;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Class for common file management.
 **/
@Slf4j
public final class FileHelper {

    /**
     * Default char set (ISO format).
     **/
    private static final String CHARSET_ISO = "ISO-8859-1";
    private static final String FILE_DOES_NOT_EXIST = "File does not exist: ";

    /**
     * Constructor.
     **/
    private FileHelper() {
        // Constructor of helper classes must be private
    }

    /**
     * Return the default character encoding.
     *
     * @return Charset encoding
     **/
    private static Charset getDefaultCharset() {
        return Charset.forName(CHARSET_ISO);
    }

    /**
     * Check file existence (with verbose mode by default).
     *
     * @param aFilePath File which must be checked
     * @return True when the file exists
     */
    public static boolean fileExist(final String aFilePath) {
        return fileExist(aFilePath, true);
    }

    /**
     * Check file existence.
     *
     * @param aFilePath File which must be checked
     * @param isVerbose True for verbose mode, else the silent mode is used
     * @return True when the file exists
     */
    public static boolean fileExist(final String aFilePath, final boolean isVerbose) {
        boolean status = true;

        if (aFilePath == null) {
            if (isVerbose) {
                log.debug("File name is null");
            }
            return false;
        }

        File theFile = new File(aFilePath);
        if (!theFile.exists() || theFile.isDirectory()) {
            if (isVerbose) {
                log.debug(FILE_DOES_NOT_EXIST + theFile.getAbsolutePath());
            }
            status = false;
        }

        return status;
    }

    /**
     * Read a text file and findAll its content.
     *
     * @param aSourceFilePath parsed source file path
     * @return string with text file content
     */
    public static String read(final String aSourceFilePath) {

        // Check file existence
        if (!fileExist(aSourceFilePath)) {
            log.error(FILE_DOES_NOT_EXIST + aSourceFilePath);
            return null;
        }

        // Read file content
        return read(getStream(aSourceFilePath));
    }

    /**
     * Read a text file and findAll its content.
     *
     * @param aFileReader File reader
     * @return string with text file content
     */
    public static String read(final InputStreamReader aFileReader) {
        StringBuilder theContent = new StringBuilder();

        try (BufferedReader reader = new BufferedReader(aFileReader)) {

            // Browse lines of file
            String theCurrentLine = reader.readLine();
            while (theCurrentLine != null) {
                theContent.append(theCurrentLine);
                theContent.append(StringHelper.LINE_FEED);
                theCurrentLine = reader.readLine();

            }
        } catch (IOException e) {
            log.error("Unable to read file", e);
        } finally {
            try {
                if (aFileReader != null) {
                    aFileReader.close();
                }
            } catch (IOException e) {
                log.error("Unable to close the file", e);
            }
        }

        // Return lines list
        return theContent.toString();
    }

    /**
     * Instantiate an input stream from a specific input file path.
     *
     * @param aSourceFilePath File path of the stream
     * @return Created instance of InputStreamReader
     **/
    private static InputStreamReader getStream(final String aSourceFilePath) {
        InputStreamReader theFileReader = null;
        try (FileInputStream theInputStream = new FileInputStream(aSourceFilePath)) {
            // Do not use FileReader it use default encoding of platform
            theFileReader = new InputStreamReader(theInputStream, getDefaultCharset());
        } catch (IOException e) {
            log.error("Unable to access file: " + aSourceFilePath, e);
        }
        // Do not close theFileReader stream, it should be closed later by calling method

        return theFileReader;
    }

    /**
     * Deletes all the files in a folder that have been modified before the provided date.
     *
     * @param files a {@link List} of {@link File}s
     * @return a list of all the deleted files' names
     */
    public static List<String> deleteFiles(final List<File> files) {
        List<String> deletedFileNames = new ArrayList<>();
        for (File file : files) {
            if (file.isFile()) {
                final String fileName = file.getName();
                if (file.delete()) {
                    log.info("Le fichier [{}] a été supprimé ", fileName);
                    deletedFileNames.add(fileName);
                } else {
                    log.warn("Le fichier [{}] n'a pas pu être supprimé ", fileName);
                }
            }
        }
        return deletedFileNames;
    }

    /**
     * Lists all the {@link File}s in a folder that have been modified before the provided date.
     *
     * @param folder   the flder
     * @param deadline the deadline
     * @return a list of all the {@link File}s
     */
    public static List<File> listFilesBefore(final File folder, final Calendar deadline) {
        List<File> listing = new ArrayList<>();
        for (File file : folder.listFiles()) {
            if (file.isFile()) {
                Calendar fileMod = Calendar.getInstance();
                final long lastModified = file.lastModified();
                fileMod.setTime(new Date(lastModified));
                if (fileMod.before(deadline)) {
                    listing.add(file);
                }
            }
        }
        return listing;
    }

    /**
     * Copy an original file to a target location.
     *
     * @param original the original {@link File}
     * @param target   the target {@link File}
     * @throws NotFoundException if the original does not exist or similar kind of I/O error occurs
     */
    public static void copy(final File original, final File target) throws NotFoundException {
        try {
            Files.copy(original, target);
        } catch (IOException e) {
            throw new NotFoundException("Unable to copy the file from " + original.toString() + " to " + target.toString(), e);
        }
    }
}
