/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.web.clientproject.sites;

import org.netbeans.modules.web.clientproject.api.sites.SiteHelper;
import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import javax.swing.JComponent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.modules.web.clientproject.ClientSideProjectConstants;
import org.netbeans.modules.web.clientproject.spi.SiteTemplateImplementation;
import org.netbeans.modules.web.clientproject.util.FileUtilities;
import org.netbeans.modules.web.clientproject.util.FileUtilities.ZipEntryFilter;
import org.netbeans.modules.web.clientproject.util.FileUtilities.ZipEntryTask;
import org.openide.filesystems.FileObject;
import org.openide.util.ChangeSupport;
import org.openide.util.EditableProperties;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;

/**
 * Special {@link SiteTemplateImplementation} (not registered in SFS).
 */
public class SiteZip implements SiteTemplateImplementation {

    static final Logger LOGGER = Logger.getLogger(SiteZip.class.getName());

    private static final String USED_TEMPLATES = "last.templates"; //NOI18N
    private static final String SEPARATOR = "=s e p="; //NOI18N
    private static final ZipEntryFilter NB_TEMPLATE_FILTER = new ZipEntryFilter() {
        @Override
        public boolean accept(ZipEntry zipEntry) {
            return !zipEntry.isDirectory()
                    && zipEntry.getName().equals(ClientSideProjectConstants.TEMPLATE_DESCRIPTOR);
        }
    };

    private Customizer cust;

    @Override
    public String getId() {
        return "ARCHIVE"; // NOI18N
    }

    @NbBundle.Messages("SiteZip.name=Archive File")
    @Override
    public String getName() {
        return Bundle.SiteZip_name();
    }

    @Override
    public String getDescription() {
        return getName();
    }

    public Customizer getCustomizer() {
        cust = new Customizer();
        return cust;
    }

    @Override
    public boolean isPrepared() {
        return getArchiveFile().isFile();
    }

    @Override
    public void prepare() throws IOException {
        assert !EventQueue.isDispatchThread();
        assert !isPrepared();
        String template = cust.panel.getTemplate();
        assert isRemoteUrl(template) : "Remote URL expected: " + template; //NOI18N
        SiteHelper.download(template, getArchiveFile(), null); // NOI18N
    }

    @Override
    public void configure(final ProjectProperties projectProperties) {
        assert !EventQueue.isDispatchThread();
        assert isPrepared();
        try {
            FileUtilities.runOnZipEntries(getArchiveFile(), new ZipEntryTask() {
                @Override
                public void run(ZipEntry zipEntry) {
                    // noop
                }
                @Override
                public void run(InputStream zipEntryInputStream) {
                    EditableProperties templateProperties = new EditableProperties(false);
                    try {
                        templateProperties.load(zipEntryInputStream);
                        projectProperties.setSiteRootFolder(templateProperties.getProperty(ClientSideProjectConstants.PROJECT_SITE_ROOT_FOLDER))
                                .setSourceFolder(templateProperties.getProperty(ClientSideProjectConstants.PROJECT_SOURCE_FOLDER))
                                .setTestFolder(templateProperties.getProperty(ClientSideProjectConstants.PROJECT_TEST_FOLDER));
                    } catch (IOException ex) {
                        LOGGER.log(Level.WARNING, "Error while reading file", ex);
                    }
                }
            }, NB_TEMPLATE_FILTER);
        } catch (IOException ex) {
            LOGGER.log(Level.WARNING, "Error while reading zip file", ex);
        }
    }

    @Override
    public void apply(FileObject projectDir, ProjectProperties projectProperties, ProgressHandle handle) throws IOException {
        assert !EventQueue.isDispatchThread();
        if (!isPrepared()) {
            // not correctly prepared, user has to know about it already
            LOGGER.info("Template not correctly prepared, nothing to be applied"); //NOI18N
            return;
        }
        SiteHelper.unzipProjectTemplate(getTargetDir(projectDir, projectProperties), getArchiveFile(), handle, ClientSideProjectConstants.TEMPLATE_DESCRIPTOR);
        registerTemplate(cust.panel.getTemplate());
    }

    /**
     * Return project dir for NB template, site root otherwise.
     */
    private FileObject getTargetDir(FileObject projectDir, ProjectProperties projectProperties) throws IOException {
        if (FileUtilities.listZipFiles(getArchiveFile(), NB_TEMPLATE_FILTER).isEmpty()) {
            // not nb template
            String siteRootFolder = projectProperties.getSiteRootFolder();
            assert siteRootFolder != null;
            return projectDir.getFileObject(siteRootFolder);
        }
        return projectDir;
    }

    @Override
    public void cleanup() {
        String template = cust.panel.getTemplate();
        if (isRemoteUrl(template)) {
            // noop
            return;
        }
        File archiveFile = getArchiveFile();
        if (archiveFile.isFile()) {
            if (!archiveFile.delete()) {
                archiveFile.deleteOnExit();
            }
        }
    }

    private File getArchiveFile() {
        String template = cust.panel.getTemplate();
        if (!isRemoteUrl(template)) {
            return new File(template);
        }
        // remote file => calculate hash of its url
        CRC32 crc = new CRC32();
        crc.update(template.getBytes(Charset.forName("UTF-8")));
        String filename = String.valueOf(crc.getValue()) + ".zip"; // NOI18N
        LOGGER.log(Level.INFO, "Remote URL \"{0}\" set, downloaded to {1}", new Object[] {template, filename}); //NOI18N
        return new File(SiteHelper.getJsLibsDirectory(), filename);
    }

    private boolean isRemoteUrl(String input) {
        return input.toLowerCase().startsWith("http"); // NOI18N
    }

    public static void registerTemplate(File f) {
        String name = f.getAbsolutePath();
        registerTemplate(name);
    }

    public static void registerTemplate(String name) {
        String templates = NbPreferences.forModule(SiteZip.class).get(USED_TEMPLATES, ""); //NOI18N
        templates = name + SEPARATOR + templates.replace(name+SEPARATOR, ""); //NOI18N
        NbPreferences.forModule(SiteZip.class).put(USED_TEMPLATES, templates);
    }

    public static List<String> getUsedTemplates() {
        String templates = NbPreferences.forModule(SiteZip.class).get(USED_TEMPLATES, ""); //NOI18N
        return Arrays.asList(templates.split(SEPARATOR));
    }

    //~ Inner classes

    public static class Customizer {

        private SiteZipPanel panel = new SiteZipPanel(this);
        private ChangeSupport sup = new ChangeSupport(this);
        private String error = ""; //NOI18N

        public void addChangeListener(ChangeListener listener) {
            sup.addChangeListener(listener);
        }

        public void removeChangeListener(ChangeListener listener) {
            sup.removeChangeListener(listener);
        }

        public JComponent getComponent() {
            return panel;
        }

        @NbBundle.Messages({
            "SiteZip.error.template.missing=Template file must be specified.",
            "SiteZip.error.template.invalid=Template file is invalid (http://... or local file expected).",
            "SiteZip.error.template.notZip=Template file must be ZIP archive (*.zip)."
        })
        public boolean isValid() {
            String tpl = panel.getTemplate();
            if (tpl.isEmpty()) {
                error = Bundle.SiteZip_error_template_missing();
                return false;
            }
            File localTpl = new File(tpl);
            if (!tpl.startsWith("http")  && !localTpl.isFile()) { //NOI18N
                error = Bundle.SiteZip_error_template_invalid();
                return false;
            }
            if (localTpl.isFile()
                    && !localTpl.getName().endsWith(".zip")) { // NOI18N
                error = Bundle.SiteZip_error_template_notZip();
                return false;
            }
            error = null;
            return true;
        }

        public String getErrorMessage() {
            return error;
        }

        public String getWarningMessage() {
            return null;
        }

        void fireChange() {
            sup.fireChange();
        }

    }

}
