package org.richfaces.realworld.util;

import java.io.*;
import java.util.zip.*;


/**
 * Command line program to copy a file to another directory.
 *
 * @author Marco Schmidt
 */

//TODO nick - COPYRIGHT!!!
public class FileUtils {
    // constant values for the override option
    public static final int OVERWRITE_ALWAYS = 1;
    public static final int OVERWRITE_NEVER = 2;
    public static final int OVERWRITE_ASK = 3;

    // program options initialized to default values
    private static final int BUFFER_SIZE = 4 * 1024;
    private static final boolean CLOCK = true;
    private static final boolean COPY_ORIGINAL_TIMESTAMP = true;
    private static final boolean VERIFY = true;
    private static final int OVERRIDE = OVERWRITE_ASK;

    public static Long copyFile(File srcFile, File destFile) throws IOException {
        if (!srcFile.getPath().toLowerCase().endsWith("jpg") && !srcFile.getPath().toLowerCase().endsWith("jpeg")) {
            //TODO nick - should be return null
            return -1L;
        }
        final InputStream in = new FileInputStream(srcFile);
        final OutputStream out = new FileOutputStream(destFile);
        long millis = System.currentTimeMillis();
        CRC32 checksum;
        if (VERIFY) {
            checksum = new CRC32();
            checksum.reset();
        }
        final byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = in.read(buffer);
        while (bytesRead >= 0) {
            if (VERIFY) {
                checksum.update(buffer, 0, bytesRead);
            }
            out.write(buffer, 0, bytesRead);
            bytesRead = in.read(buffer);
        }

        //TODO nick - this should be in finally block
        out.close();
        in.close();
        if (CLOCK) {
            millis = System.currentTimeMillis() - millis;
            System.out.println("Copy file '" + srcFile.getPath() + "' on " + millis / 1000L + " second(s)");
        }
        if (VERIFY) {
            return checksum.getValue();
        } else {
            return null;
        }
    }

    public static Long createChecksum(File file) throws IOException {
        long millis = System.currentTimeMillis();
        final InputStream in = new FileInputStream(file);
        final CRC32 checksum = new CRC32();
        checksum.reset();
        final byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = in.read(buffer);
        while (bytesRead >= 0) {
            checksum.update(buffer, 0, bytesRead);
            bytesRead = in.read(buffer);
        }
        in.close();
        if (CLOCK) {
            millis = System.currentTimeMillis() - millis;
            System.out.println("Checksum for file '" + file.getPath() + "' created on " + millis / 1000L + " second(s)");
        }
        return checksum.getValue();
    }

    /**
     * Determine if data is to be copied to given file. Take into consideration
     * override option and ask user in case file exists and override option is
     * ask.
     *
     * @param file File object for potential destination file
     * @return true if data is to be copied to file, false if not
     */
    public static boolean doCopy(File file) {
        final boolean exists = file.exists();
        if (OVERRIDE == OVERWRITE_ALWAYS || !exists) {
            return true;
        } else if (OVERRIDE == OVERWRITE_NEVER) {
            return false;
        } else if (OVERRIDE == OVERWRITE_ASK) {
            return readYesNoFromStandardInput("File exists. Overwrite (y/n)?");
        } else {
            throw new InternalError("Program error. Invalid "
                    + "value for override: " + OVERRIDE);
        }
    }

    public static void main(String[] args) throws IOException {
        // make sure there are exactly two arguments
        if (args.length != 2) {
            System.err.println("Usage: CopyFile SRC-FILE-NAME DEST-DIR-NAME");
            System.exit(1);
        }
        // make sure the source file is indeed a readable file
        final File srcFile = new File(args[0]);
        if (!srcFile.isFile() || !srcFile.canRead()) {
            System.err.println("Not a readable file: " + srcFile.getName());
            System.exit(1);
        }
        // make sure the second argument is a directory
        final File destDir = new File(args[1]);
        if (!destDir.isDirectory()) {
            System.err.println("Not a directory: " + destDir.getName());
            System.exit(1);
        }
        // create File object for destination file
        final File destFile = new File(destDir, srcFile.getName());

        // check if copying is desired given overwrite option
        if (!doCopy(destFile)) {
            return;
        }

        // copy file, optionally creating a checksum
        final Long checksumSrc = copyFile(srcFile, destFile);

        // copy timestamp of last modification
        if (COPY_ORIGINAL_TIMESTAMP) {
            if (!destFile.setLastModified(srcFile.lastModified())) {
                System.err.println("Error: Could not set timestamp of copied file.");
            }
        }

        // optionally verify file
        if (VERIFY) {
            System.out.print("Verifying destination file...");
            final Long checksumDest = createChecksum(destFile);
            if (checksumSrc.equals(checksumDest)) {
                System.out.println(" OK, files are equal.");
            } else {
                System.out.println(" Error: Checksums differ.");
            }
        }
    }

    /**
     * Print a message to standard output and read lines from standard input
     * until yes or no (y or n) is entered.
     *
     * @param message informative text to be answered by user
     * @return user answer, true for yes, false for no.
     */
    public static boolean readYesNoFromStandardInput(String message) {
        System.out.println(message);
        final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        try {
            Boolean answer = null;

            String line = in.readLine();
            while (line != null) {
                line = line.toLowerCase();
                if ("y".equals(line) || "yes".equals(line)) {
                    answer = Boolean.TRUE;
                    break;
                } else if ("n".equals(line) || "no".equals(line)) {
                    answer = Boolean.FALSE;
                    break;
                } else {
                    System.out.println("Could not understand answer (\"" + line
                            + "\"). Please use y for yes or n for no.");
                }

                line = in.readLine();
            }
            if (answer == null) {
                throw new IOException("Unexpected end of input from stdin.");
            }
            in.close();
            return answer;
        } catch (IOException ioe) {
            throw new InternalError(
                    "Cannot read from stdin or write to stdout.");
        }
    }

    public static void copyDirectory(File srcDir, File dstDir)
            throws IOException {

        //TODO nick - skip hidden/system directories
        if (".svn".equals(srcDir.getName())) {
            return;
        }

        if (srcDir.isDirectory()) {
            if (!dstDir.exists()) {
                dstDir.mkdir();
            }

            for (String aChildren : srcDir.list()) {
                copyDirectory(new File(srcDir, aChildren), new File(dstDir, aChildren));
            }
        } else {
            copyFile(srcDir, dstDir);
        }
    }

    public static void deleteDirectory(File dir) throws IOException {
        if (dir.isDirectory()) {
            if (dir.exists()) {
                for (File child : dir.listFiles()) {
                    //TODO nick - add try/finally so that deletion continues if something failed
                    deleteDirectory(child);
                }
            }

        } else {
            if (dir.exists()) {
                final boolean isFileDeleted = dir.delete();
                System.out.println((isFileDeleted ? "OK     " : "CANCEL ") +
                        "Delete file '" + dir.getPath() + '\'');
            }
        }
        dir.delete();
    }

}