package usda.weru.weps.startup;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileInputStream;
import de.schlichtherle.truezip.file.TFileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.swing.SwingWorker;
import org.apache.log4j.Logger;

/**
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class PreviousVersionResolver {

    private static final Logger LOGGER = Logger.getLogger(PreviousVersionResolver.class);

    /**
     *
     * @param root
     * @param current
     * @return
     */
    public static TFile findPreviousSettingsDirectory(TFile root, ReleaseVersion current) {

        Map<ReleaseVersion, TFile> versions = new HashMap<ReleaseVersion, TFile>();

        TFile[] children = root.listFiles();
        if (children == null) {
            return null;
        }
        boolean rootHadAConfigFile = false;
        for (TFile child : children) {

            //must be a directory
            if (!child.isDirectory()) {
                if (child.getName().toLowerCase().contains(".cfg")) {
                    rootHadAConfigFile = true;
                }
                continue;
            }

            if(current.c_version.length == 0) return null;
            //ignore directories that don't match the release
            ReleaseVersion childVersion = ReleaseVersion.valueOf(child);
            if (childVersion == null || !current.isReleaseEqual(childVersion)) {
                continue;
            }

            //add the version to the list of maybes
            versions.put(childVersion, child);
        }

        //is there a root?  This is for backwards compatability with the single folder configs
        if (rootHadAConfigFile) {
            ReleaseVersion rootVersion = new ReleaseVersion(current.c_release, -1);
            versions.put(rootVersion, root);
        }

        List<ReleaseVersion> sortedVersions = new LinkedList<ReleaseVersion>(versions.keySet());
        //sort them into order
        Collections.sort(sortedVersions);

        for (ReleaseVersion version : sortedVersions) {
            //only return something previous, just in case the user is running two versions of weps
            if (current.compareTo(version) >= 0) {
            } else {
                return versions.get(version);
            }
        }

        //default, did not find a previous version
        return null;
    }

    /**
     *
     * @param previous
     * @param current
     * @return
     */
    public static boolean importSettings(TFile previous, TFile current) {
        SettingsImportWorker worker = new SettingsImportWorker(previous, current);
        try {
            Thread thread = new Thread(worker, "Settings import thread.");
            thread.start();

            while (!worker.isDone() || worker.c_progress != null) {
                Thread.yield();
            }

            return worker.get();
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.error("Unknown import error.", e);
            return false;
        }

    }

    private static class SettingsImportWorker extends SwingWorker<Boolean, TFile> {

        private final TFile c_previous;
        private final TFile c_current;
        private ImportProgressDialog c_progress;
        private int c_progressCount;

        public SettingsImportWorker(TFile previous, TFile current) {
            c_previous = previous;
            c_current = current;
        }

        @Override
        protected Boolean doInBackground() throws Exception {

            //setup the progress dialog
            int fileCount = fileCount(c_previous);
            c_progress = new ImportProgressDialog(null, true);
            c_progress.setMaximum(fileCount);

            //if the current version's parent is the previous then we need to be careful about what we copy
            boolean fromRoot = c_current.getParentFile().equals(c_previous);

            if (!c_current.exists()) {
                LOGGER.debug("Making directory: " + c_current.getAbsolutePath());
                boolean madeDirs = c_current.mkdirs();

                if (!madeDirs) {
                    //this is really bad
                    LOGGER.debug("Failed to make directory: " + c_current.getAbsolutePath());
                    return false;
                }
            }

            TFile[] children = c_previous.listFiles();

            if (children == null) {
                return true;
            }

            for (TFile child : children) {

                if (fromRoot) {
                    //if it's from root we don't copy ourselves and we don't copy other version folders
                    if (child.equals(c_current)) {
                        continue;
                    } else if (ReleaseVersion.valueOf(child) != null) {
                        publishRecursive(child);
                        continue;
                    }
                }

                //let's skip the logs, lots of data there we don't need.
                if (child.isDirectory() && ("log".equals(child.getName()) || "tmp".equals(child.getName()))) {
                    LOGGER.debug("Skipping: " + child.getAbsolutePath());
                    publishRecursive(child);
                    continue;
                }

                if (child.isDirectory()) {
                    LOGGER.debug("Copying directory: " + child.getAbsolutePath());
                    boolean copied = copyDirectory(child, new TFile(c_current, child.getName()));
                    if (!copied) {
                        LOGGER.debug("Copy Failed: " + child.getAbsolutePath());
                        return false;
                    }
                } else {
                    boolean copied = copyFile(child, new TFile(c_current, child.getName()));
                    LOGGER.debug("Copying file: " + child.getAbsolutePath());
                    if (!copied) {
                        LOGGER.debug("Copy Failed: " + child.getAbsolutePath());
                        return false;
                    }
                }
            }

            return true;
        }

        private void publishRecursive(TFile file) {
            publish(file);
            TFile[] children = file.listFiles();
            if (children != null) {
                for (TFile child : children) {
                    publishRecursive(child);
                }
            }
        }

        @Override
        protected void process(List<TFile> chunks) {
            synchronized (this) {
                if (isDone() || isCancelled()) {
                    return;
                }
            }
            final ImportProgressDialog progress = c_progress;
            if (progress == null) {
                LOGGER.debug("Progress dialog is null. Unable to update progress.");
                return;
            }

            if (!progress.isVisible()) {
                LOGGER.debug("Making import progress dialog visible.");
                progress.setVisible(true);
            }
            for (TFile chunk : chunks) {
                c_progressCount += 1;
                progress.setProgress(c_progressCount);
            }

        }

        @Override
        protected void done() {
            synchronized (this) {
                if (c_progress != null) {
                    LOGGER.debug("Closing import progress dialog.");
                    c_progress.close();
                    c_progress = null;
                } else {
                    LOGGER.debug("No import progress dialog to close.");
                }
            }
        }

        private int fileCount(TFile file) {
            int depth = 1;

            TFile[] chidren = file.listFiles();

            if (chidren != null) {
                for (TFile child : chidren) {
                    depth += 1;
                    if (child.isDirectory()) {
                        depth += fileCount(child);
                    }
                }
            }

            return depth;
        }

        private boolean copyDirectory(TFile from, TFile to) {
            if (from.isDirectory()) {
                try {
                    if (!to.exists()) {
                        boolean created = to.mkdirs();
                        if (!created) {
                            LOGGER.error("unable to create directory: " + to.getAbsolutePath());
                            return false;
                        }
                    }
                    java.io.File[] children = from.listFiles();
                    for (java.io.File child : children) {
                        TFile childFrom = new TFile(child);
                        TFile childTo = new TFile(to, child.getName());
                        boolean copied = copyDirectory(childFrom, childTo);
                        if (!copied) {
                            LOGGER.error("unable to copy file: " + childTo.getAbsolutePath());
                            return false;
                        }
                    }
                } finally {
                    publish(from);
                }
            } else {
                boolean copied = copyFile(from, to);
                if (!copied) {
                    LOGGER.error("unable to copy file: " + to.getAbsolutePath());
                    return false;
                }
            }

            return true;

        }

        private boolean copyFile(TFile from, TFile to) {
            try {
                if (from.exists() && from.isFile()) {
                    if (from.getAbsoluteFile().equals(to.getAbsoluteFile())) {
                        //System.err.println("ERROR copyFile: can't copy file from its own!");
                        return false;
                    }
                    TFile dir = to.getParentFile();
                    if (!dir.isDirectory()) {
                        LOGGER.debug("Making directory: " + dir.getAbsolutePath());
                        boolean created = dir.mkdirs();
                        if (!created) {
                            LOGGER.error("Unable to create required directory structure: " + dir.getAbsolutePath());
                            return false;
                        }
                    }
                    byte buffer[] = new byte[100000];
                    TFileInputStream fromReader = null;
                    TFileOutputStream toWriter = null;
                    try {
                        fromReader = new TFileInputStream(from);
                        toWriter = new TFileOutputStream(to);
                        int byteRead = 0;
                        while ((byteRead = fromReader.read(buffer)) != -1) {
                            toWriter.write(buffer, 0, byteRead);
                            toWriter.flush();
                        }

                    } catch (IOException e) {
                        LOGGER.error("Unknown copy error: " + from.getAbsolutePath() + " to " + to.getAbsolutePath(), e);
                        return false;
                    } finally {
                        try {
                            fromReader.close();
                        } catch (IOException e) {
                            LOGGER.error("Error closing file stream: " + from.getAbsolutePath(), e);
                        }

                        try {
                            toWriter.close();
                        } catch (IOException e) {
                            LOGGER.error("Error closing file stream: " + to.getAbsolutePath(), e);
                        }

                    }
                    return true;
                } else {
                    return false;
                }
            } finally {
                publish(from);
            }
        }
    }
}
