/*
 * 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.maven.apisupport;

import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.namespace.QName;
import org.netbeans.api.project.Project;
import org.netbeans.modules.maven.api.FileUtilities;
import org.netbeans.modules.maven.api.ModelUtils;
import org.netbeans.modules.maven.api.customizer.ModelHandle2;
import org.netbeans.modules.maven.api.customizer.support.SelectedItemsTable;
import org.netbeans.modules.maven.api.customizer.support.SelectedItemsTable.SelectedItemsTableModel;
import org.netbeans.modules.maven.model.ModelOperation;
import org.netbeans.modules.maven.model.pom.Build;
import org.netbeans.modules.maven.model.pom.Configuration;
import org.netbeans.modules.maven.model.pom.POMExtensibilityElement;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.modules.maven.model.pom.Plugin;
import org.netbeans.modules.maven.spi.customizer.SelectedItemsTablePersister;
import org.openide.util.HelpCtx;

/**
 * Panel showing list of public packages for a module, also implements
 * persistence to/from pom.xml
 *
 * @author Dafe Simonek
 */
public class PublicPackagesPanel extends javax.swing.JPanel implements SelectedItemsTablePersister, HelpCtx.Provider {
    private static final String ALL_SUBPACKAGES = ".*";
    private static final String ALL_SUBPACKAGES_2 = ".**";
    private static final int COALESCE_LIMIT = 2;
    private static final String PUBLIC_PACKAGE = "publicPackage";
    private static final String PUBLIC_PACKAGES = "publicPackages";

    private final SelectedItemsTableModel tableModel;
    private final ModelHandle2 handle;
    private final Project project;
    private ModelOperation<POMModel> operation;

    /** Creates new form PublicPackagesPanel */
    public PublicPackagesPanel(ModelHandle2 handle, Project prj) {
        this.handle = handle;
        this.project = prj;
        tableModel = new SelectedItemsTableModel(this);

        initComponents();

        jScrollPane1.getViewport().setBackground(exportTable.getBackground());
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        exportTable = new SelectedItemsTable(tableModel);

        jLabel1.setLabelFor(exportTable);
        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(PublicPackagesPanel.class, "PublicPackagesPanel.jLabel1.text")); // NOI18N

        jScrollPane1.setViewportView(exportTable);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel1)
                .addContainerGap(411, Short.MAX_VALUE))
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 517, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 211, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTable exportTable;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JScrollPane jScrollPane1;
    // End of variables declaration//GEN-END:variables

    @Override
    public SortedMap<String, Boolean> read() {
        SortedMap<String, Boolean> pkgMap = new TreeMap<String, Boolean>();

        SortedSet<String> packageNames = FileUtilities.getPackageNames(project);
        for (String pkgName : packageNames) {
            pkgMap.put(pkgName, Boolean.FALSE);
        }

        String[] publicPkgs = PluginBackwardPropertyUtils.getPluginPropertyList(project,
                PUBLIC_PACKAGES, PUBLIC_PACKAGE,
                "manifest"); //NOI18N

        if (publicPkgs != null) {
            for (int i = 0; i < publicPkgs.length; i++) {
                String prefix = null;
                String curPkg = publicPkgs[i];
                if (curPkg.endsWith(ALL_SUBPACKAGES)) {
                    prefix = curPkg.substring(0, curPkg.length() - ALL_SUBPACKAGES.length());
                } else if (curPkg.endsWith(ALL_SUBPACKAGES_2)) {
                    prefix = curPkg.substring(0, curPkg.length() - ALL_SUBPACKAGES_2.length());
                }
                if (prefix == null) {
                    pkgMap.put(curPkg, Boolean.TRUE);
                } else {
                    for (String pkgName : packageNames) {
                        if (pkgName.startsWith(prefix)) {
                            pkgMap.put(pkgName, Boolean.TRUE);
                        }
                    }
                }
            }
        }

        return pkgMap;
    }

    @Override
    public void write(SortedMap<String, Boolean> items) {
        if (operation != null) {
            handle.removePOMModification(operation);
        }
        final SortedMap<String, Boolean> selItems = new TreeMap<String, Boolean>(items);
        operation = new ModelOperation<POMModel>() {

            @Override
            public void performOperation(POMModel pomModel) {
        Build build = pomModel.getProject().getBuild();
        boolean selEmpty = true;
        for (Boolean selected : selItems.values()) {
            if (selected) {
                selEmpty = false;
                break;
            }
        }

        Plugin nbmPlugin = null;
        if (build != null) {
            nbmPlugin = PluginBackwardPropertyUtils.findPluginFromBuild(build);
        } else {
            build = pomModel.getFactory().createBuild();
            pomModel.getProject().setBuild(build);
        }
        Configuration config = null;
        if (nbmPlugin != null) {
            config = nbmPlugin.getConfiguration();
        } else {
            nbmPlugin = pomModel.getFactory().createPlugin();
            nbmPlugin.setGroupId(MavenNbModuleImpl.GROUPID_APACHE);
            nbmPlugin.setArtifactId(MavenNbModuleImpl.NBM_PLUGIN);
            nbmPlugin.setExtensions(Boolean.TRUE);
            build.addPlugin(nbmPlugin);
        }

        if (config == null) {
            config = pomModel.getFactory().createConfiguration();
            nbmPlugin.setConfiguration(config);
        }

        List<POMExtensibilityElement> configElems = config.getConfigurationElements();
        POMExtensibilityElement packages = null;
        for (POMExtensibilityElement elem : configElems) {
            if (PUBLIC_PACKAGES.equals(elem.getQName().getLocalPart())) {
                packages = elem;
                break;
            }
        }

        if (selEmpty) {
            if (packages != null) {
                config.removeExtensibilityElement(packages);
            }
            return;
        }

        packages = ModelUtils.getOrCreateChild(config, PUBLIC_PACKAGES, pomModel);
        List<POMExtensibilityElement> elems = packages.getAnyElements();
        for (POMExtensibilityElement elem : elems) {
            packages.removeAnyElement(elem);
        }
        POMExtensibilityElement publicP;

        for (String elemText : getPublicPackagesForPlugin(selItems)) {
            publicP = pomModel.getFactory().createPOMExtensibilityElement(
                    new QName(PUBLIC_PACKAGE));
            publicP.setElementText(elemText);
            packages.addExtensibilityElement(publicP);
        }

    }
        };
        handle.addPOMModification(operation);
    }

    /**
     * Transforms public packages in form of "selected items" map into
     * set of strings describing public packages in maven-nbm-plugin syntax.
     */
    public static SortedSet<String> getPublicPackagesForPlugin (SortedMap<String, Boolean> selItems) {
        SortedSet<String> result = new TreeSet<String>();
        Set<String> processed = new HashSet<String>();
        for (Entry<String, Boolean> entry : selItems.entrySet()) {
            if (entry.getValue() && !processed.contains(entry.getKey())) {
                boolean allSubpackages = true;
                Set<String> processedCandidates = new HashSet<String>();
                String prefix = entry.getKey() + ".";
                for (String key : selItems.keySet()) {
                    if (key.startsWith(prefix)) {
                        if (selItems.get(key)) {
                            processedCandidates.add(key);
                        } else {
                            allSubpackages = false;
                            break;
                        }
                    }
                }
                if (allSubpackages && processedCandidates.size() > COALESCE_LIMIT) {
                    result.add(entry.getKey() + ALL_SUBPACKAGES);
                    processed.addAll(processedCandidates);
                } else {
                    result.add(entry.getKey());
                }
            }
        }

        return result;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx("maven_settings");
    }    
}