/*
 * #%L
 * gitools-core
 * %%
 * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group
 * %%
 * 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 3 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, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package org.gitools.persistence.locators.filters.zip;

import org.gitools.api.ApplicationContext;
import org.gitools.api.PersistenceException;
import org.gitools.api.analysis.IProgressMonitor;
import org.gitools.api.resource.IResourceFilter;
import org.gitools.api.resource.IResourceLocator;
import org.gitools.persistence.locators.filters.FilterResourceLocator;
import org.gitools.utils.progressmonitor.NullProgressMonitor;
import org.zeroturnaround.zip.ZipUtil;

import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipResourceLocatorAdaptor extends FilterResourceLocator {

    private File tmpFolder;

    private String entryName;

    public ZipResourceLocatorAdaptor(String entryName, IResourceFilter filter, IResourceLocator resourceLocator) {
        super(filter, resourceLocator);
        this.entryName = entryName;
    }

    private ZipResourceLocatorAdaptor(String entryName, String name, String extension, IResourceLocator resourceLocator) {
        super(name, extension, resourceLocator);
        this.entryName = entryName;
    }

    @Override
    public boolean isContainer() {
        return true;
    }

    @Override
    public IResourceLocator getReferenceLocator(String referenceName) throws PersistenceException {

        String extension = ApplicationContext.getPersistenceManager().getFormatExtension(referenceName);
        int extensionIndex = referenceName.indexOf(extension);
        String extensionWithFilters = referenceName.substring(extensionIndex);

        return new ZipResourceLocatorAdaptor(referenceName, referenceName, extensionWithFilters, getParentLocator()) {

            @Override
            public File getWriteFile() {
                try {
                    return new File(getTemporalFolder(), getEntryName());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            protected File getTemporalFolder() throws IOException {
                return ZipResourceLocatorAdaptor.this.getTemporalFolder();
            }

            @Override
            public void close(IProgressMonitor monitor) {
            }

            @Override
            public OutputStream openOutputStream(IProgressMonitor monitor) throws IOException {
                return new FileOutputStream(getWriteFile());
            }
        };
    }

    @Override
    public InputStream openInputStream(IProgressMonitor progressMonitor) throws IOException {
        ZipInputStream in = new ZipInputStream(getParentLocator().openInputStream(progressMonitor));

        ZipEntry entry;
        while ((entry = in.getNextEntry()) != null) {
            if (entryName.equals(entry.getName())) {
                return in;
            }
        }

        // Exact match not found. Return the first with the same extension.
        String extension = getExtension();
        in.close();
        in = new ZipInputStream(getParentLocator().openInputStream(progressMonitor));
        while ((entry = in.getNextEntry()) != null) {
            if (entry.getName().endsWith(extension)) {
                entryName = entry.getName();
                setName(entryName);
                setBaseName(entryName, extension);
                return in;
            }
        }

        throw new PersistenceException("Entry '" + entryName + "' not found in '" + getParentLocator().getURL() + "'");
    }


    @Override
    public OutputStream openOutputStream(IProgressMonitor monitor) throws IOException {

        // Create a temporal folder
        tmpFolder = createTemporalFolder(getURL());

        // Extract all the files
        monitor.title("Extracting to temporal folder...");
        try {
            ZipUtil.unpack(getParentLocator().openInputStream(new NullProgressMonitor()), tmpFolder);

            // Check if we are doing a 'Save as...'
            if (!getWriteFile().equals(getReadFile())) {

                // Rename all entries
                String inName = getReadFile().getName().replace("." + getExtension() + ".zip", "");
                String toName = getBaseName();

                renameAll(getTemporalFolder(), inName, toName);

            }

        } catch (FileNotFoundException e) {
            // It's a all in memory resource
        }

        monitor.title("Copying files...");
        return new FileOutputStream(new File(tmpFolder, entryName));
    }

    @Override
    public void close(IProgressMonitor monitor) {

        try {

            // Compress folder
            monitor.title("Compressing files...");
            ZipUtil.pack(getTemporalFolder(), getWriteFile(), Deflater.NO_COMPRESSION);

            // Close temporal file
            getParentLocator().close(monitor);

            // Remove temporal folder
            deleteFolder(getTemporalFolder());

        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    protected File getTemporalFolder() throws IOException {
        return tmpFolder;
    }

    protected String getEntryName() {
        return entryName;
    }

    /**
     *
     * Creates a temporal folder. If the 'url' is a file then the temporal folder is
     * created next to the file, otherwise is created at the system temporal folder.
     *
     * @param url
     * @return
     * @throws IOException
     */
    private File createTemporalFolder(URL url) throws IOException {

        File tmpFolder;

        if ("file".equals(url.getProtocol())) {
            try {
                File parentFolder = (new File(url.toURI())).getParentFile();
                tmpFolder = File.createTempFile(getName(), "", parentFolder);
            } catch (URISyntaxException e) {
                tmpFolder = File.createTempFile("gitools" + getBaseName(), "");
            }
        } else {
            tmpFolder = File.createTempFile("gitools" + getBaseName(), "");
        }

        tmpFolder.delete();
        tmpFolder.mkdir();

        return tmpFolder;
    }

    private static void renameAll(File folder, String inName, String toName) {

        File[] files = folder.listFiles();
        if(files!=null) { //some JVMs return null for empty dirs
            for(File f : files) {
                String name = f.getName();
                if (!f.isDirectory() && name.contains(inName)) {
                    name = name.replace(inName, toName);
                    File toFile = new File(f.getParentFile(), name);
                    f.renameTo(toFile);
                }
            }
        }
    }


    private static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if(files!=null) { //some JVMs return null for empty dirs
            for(File f: files) {
                if(f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }

}
