/*
 * 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.java.j2semodule;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.ProjectGenerator;
import org.netbeans.spi.project.support.ant.PropertyUtils;

import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.SpecificationVersion;
import org.openide.util.Exceptions;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.NbBundle;
import org.openide.util.Parameters;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Creates a J2SEModularProject from scratch according to some initial configuration.
 */
public class J2SEModularProjectGenerator {
    
    private static final String DEFAULT_PLATFORM_ID = "default_platform";   //NOI18N
    private static final String PROP_PLATFORM_ANT_NAME = "platform.ant.name";    //NOI18N
    private static final String METRICS_LOGGER = "org.netbeans.ui.metrics.projects"; //NOI18N
    private static final String J2SE_MODULAR_METRICS_LOGGER = "org.netbeans.ui.metrics.j2se-modular";  //NOI18N
    
    enum Action {
        CREATE("USG_PROJECT_CREATE", "USG_PROJECT_CREATE_J2SE_MODULAR"),   //NOI18N
        OPEN("USG_PROJECT_OPEN", "USG_PROJECT_OPEN_J2SE_MODULAR"),       //NOI18N
        CLOSE("USG_PROJECT_CLOSE", "USG_PROJECT_CLOSE_J2SE_MODULAR");     //NOI18N
        
        private final String genericLogMessage;
        private final String specificLogMessage;
        
        private Action(
            @NonNull final String genericLogMessage,
            @NonNull final String specificLogMessage) {
            assert genericLogMessage != null;
            assert specificLogMessage != null;
            this.genericLogMessage = genericLogMessage;
            this.specificLogMessage = specificLogMessage;
        }
        
        @NonNull
        public String getGenericLogMessage() {
            return genericLogMessage;
        }
        
        @NonNull
        public String getSpecificLogMessage() {
            return specificLogMessage;
        }
    }

    private J2SEModularProjectGenerator() {}
    
    /**
     * Create a new empty J2SE modular project.
     * @param dir the top-level directory (need not yet exist but if it does it must be empty)
     * @param name the name for the project
     * @return the helper object permitting it to be further customized
     * @throws IOException in case something went wrong
     */
    public static AntProjectHelper createProject(final File dir, final String name, final JavaPlatform platform) throws IOException {
        Parameters.notNull("dir", dir); //NOI18N
        Parameters.notNull("name", name);   //NOI18N
        
        final FileObject dirFO = FileUtil.createFolder(dir);
        final AntProjectHelper[] h = new AntProjectHelper[1];
        dirFO.getFileSystem().runAtomicAction(() -> {
            final SpecificationVersion sourceLevel = getSourceLevel(platform);
            h[0] = createProject(dirFO, name, sourceLevel, "src", "classes", "tests", platform.getProperties().get(PROP_PLATFORM_ANT_NAME));   //NOI18N
            final J2SEModularProject p = (J2SEModularProject) ProjectManager.getDefault().findProject(dirFO);
            ProjectManager.getDefault().saveProject(p);
            try {
                ProjectManager.mutex().writeAccess((Mutex.ExceptionAction<Void>) () -> {
                    ProjectManager.getDefault().saveProject (p);
                    ProjectUtils.getSources(p).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_MODULES);
                    return null;
                });
            } catch (MutexException ex) {
                Exceptions.printStackTrace(ex.getException());
            }
            
            dirFO.createFolder("src"); //NOI18N
        });
        return h[0];
    }

    private static AntProjectHelper createProject(
            FileObject dirFO,
            String name,
            SpecificationVersion sourceLevel,
            String srcRoot,
            String srcRootPath,
            String testSrcRootPath,
            @NonNull final String platformId
            ) throws IOException {
        final String antName = PropertyUtils.getUsablePropertyName(name);
        AntProjectHelper h = ProjectGenerator.createProject(dirFO, J2SEModularProject.TYPE);
        Element data = h.getPrimaryConfigurationData(true);
        Document doc = data.getOwnerDocument();
        Element nameEl = doc.createElementNS(J2SEModularProject.PROJECT_CONFIGURATION_NAMESPACE, "name"); // NOI18N
        nameEl.appendChild(doc.createTextNode(name));
        data.appendChild(nameEl);
        EditableProperties ep = h.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
        if (!DEFAULT_PLATFORM_ID.equals(platformId)) {
            final Element platformEl = doc.createElementNS(J2SEModularProject.PROJECT_CONFIGURATION_NAMESPACE, "explicit-platform");   //NOI18N
            platformEl.setAttribute("explicit-source-supported", "true"); //NOI18N
            data.appendChild(platformEl);
        }
        Element sourceRoots = doc.createElementNS(J2SEModularProject.PROJECT_CONFIGURATION_NAMESPACE,"source-roots");  //NOI18N
        if (srcRoot != null) {
            Element root = doc.createElementNS (J2SEModularProject.PROJECT_CONFIGURATION_NAMESPACE,"root");   //NOI18N
            root.setAttribute ("id","src.dir");   //NOI18N
            root.setAttribute ("pathref","src.dir.path");   //NOI18N
            sourceRoots.appendChild(root);
            ep.setProperty("src.dir", srcRoot); // NOI18N
            ep.setProperty("src.dir.path", srcRootPath); // NOI18N
        }
        data.appendChild (sourceRoots);
        Element testRoots = doc.createElementNS(J2SEModularProject.PROJECT_CONFIGURATION_NAMESPACE,"test-roots");  //NOI18N
        if (srcRoot != null) {
            Element root = doc.createElementNS (J2SEModularProject.PROJECT_CONFIGURATION_NAMESPACE,"root");   //NOI18N
            root.setAttribute ("id","test.src.dir");   //NOI18N
            root.setAttribute ("pathref","test.src.dir.path");   //NOI18N
            testRoots.appendChild (root);
            ep.setProperty("test.src.dir", srcRoot); // NOI18N
            ep.setProperty("test.src.dir.path", testSrcRootPath); // NOI18N
        }
        data.appendChild (testRoots);
        h.putPrimaryConfigurationData(data, true);
        ep.setProperty(ProjectProperties.ANNOTATION_PROCESSING_ENABLED, "true"); // NOI18N
        ep.setProperty(ProjectProperties.ANNOTATION_PROCESSING_ENABLED_IN_EDITOR, "false"); // NOI18N
        ep.setProperty(ProjectProperties.ANNOTATION_PROCESSING_RUN_ALL_PROCESSORS, "true"); // NOI18N
        ep.setProperty(ProjectProperties.ANNOTATION_PROCESSING_PROCESSORS_LIST, ""); // NOI18N
        ep.setProperty(ProjectProperties.ANNOTATION_PROCESSING_SOURCE_OUTPUT, "${build.generated.sources.dir}/ap-source-output"); // NOI18N
        ep.setProperty(ProjectProperties.ANNOTATION_PROCESSING_PROCESSOR_OPTIONS, ""); // NOI18N
        ep.setProperty("dist.dir", "dist"); // NOI18N
        ep.setComment("dist.dir", new String[] {"# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_dist.dir")}, false); // NOI18N
        ep.setProperty("javac.classpath", ""); // NOI18N
        ep.setProperty(ProjectProperties.JAVAC_PROCESSORPATH, new String[] {ref(ProjectProperties.JAVAC_CLASSPATH, true)}); // NOI18N
        ep.setProperty("javac.test.processorpath", new String[] {ref(ProjectProperties.JAVAC_TEST_CLASSPATH,true)}); // NOI18N
        ep.setProperty("build.sysclasspath", "ignore"); // NOI18N
        ep.setComment("build.sysclasspath", new String[] {"# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_build.sysclasspath")}, false); // NOI18N
        ep.setProperty("run.classpath", ""); // NOI18N
        ep.setProperty("debug.classpath", new String[] { // NOI18N
            ref(ProjectProperties.RUN_CLASSPATH,true)
        });
        ep.setComment("debug.classpath", new String[] { // NOI18N
            "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_debug.transport"),
            "#debug.transport=dt_socket"
        }, false);
        ep.setProperty("jar.compress", "false"); // NOI18N

        ep.setProperty("javac.compilerargs", ""); // NOI18N
        ep.setComment("javac.compilerargs", new String[] {
            "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_javac.compilerargs"), // NOI18N
        }, false);
        ep.setProperty("javac.source", sourceLevel.toString()); // NOI18N
        ep.setProperty("javac.target", sourceLevel.toString()); // NOI18N
        ep.setProperty("javac.deprecation", "false"); // NOI18N
        ep.setProperty("javac.test.classpath", new String[] { // NOI18N
            ref(ProjectProperties.JAVAC_CLASSPATH, true)
        });
        ep.setProperty("run.test.classpath", new String[] { // NOI18N
            ref(ProjectProperties.JAVAC_TEST_CLASSPATH, true)
        });
        ep.setProperty("debug.test.classpath", new String[] { // NOI18N
            ref(ProjectProperties.RUN_TEST_CLASSPATH, true)
        });

        //Modules
        ep.setProperty(ProjectProperties.JAVAC_MODULEPATH, ""); //NOI18N
        ep.setProperty(ProjectProperties.JAVAC_PROCESSORMODULEPATH, ""); //NOI18N
        ep.setProperty(ProjectProperties.RUN_MODULEPATH, new String[] {
            ref(ProjectProperties.JAVAC_MODULEPATH, false),
            ref(ProjectProperties.BUILD_MODULES_DIR, true)
        });
        ep.setProperty(ProjectProperties.DEBUG_MODULEPATH, new String[] {
            ref(ProjectProperties.RUN_MODULEPATH, true)
        });
        ep.setProperty(ProjectProperties.JAVAC_TEST_MODULEPATH, new String[] {
                ref(ProjectProperties.JAVAC_MODULEPATH, false),
                ref(ProjectProperties.BUILD_MODULES_DIR, true)
        });
        ep.setProperty(ProjectProperties.RUN_TEST_MODULEPATH, new String[] {
            ref(ProjectProperties.JAVAC_TEST_MODULEPATH, false),
            ref(ProjectProperties.BUILD_TEST_MODULES_DIR, true)
        });
        ep.setProperty(ProjectProperties.DEBUG_TEST_MODULEPATH, new String[] {
            ref(ProjectProperties.RUN_TEST_MODULEPATH, true)
        });

        ep.setProperty("build.generated.dir", "${build.dir}/generated"); // NOI18N

        ep.setProperty("build.dir", "build"); // NOI18N
        ep.setComment("build.dir", new String[] {"# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_build.dir")}, false); // NOI18N
        ep.setProperty("build.classes.dir", "${build.dir}/classes"); // NOI18N  //TODO: For What?
        ep.setProperty(ProjectProperties.BUILD_MODULES_DIR, "${build.dir}/modules"); // NOI18N  //TODO: For What?
        ep.setProperty("build.generated.sources.dir", "${build.dir}/generated-sources"); // NOI18N
        ep.setProperty("build.test.classes.dir", "${build.dir}/test/classes"); // NOI18N
        ep.setProperty("build.test.modules.dir", "${build.dir}/test/modules"); // NOI18N
        ep.setProperty("build.test.results.dir", "${build.dir}/test/results"); // NOI18N
        ep.setProperty("build.classes.excludes", "**/*.java,**/*.form"); // NOI18N
        ep.setProperty("dist.javadoc.dir", "${dist.dir}/javadoc"); // NOI18N
        ep.setProperty("platform.active", platformId); // NOI18N

        ep.setProperty(ProjectProperties.RUN_JVM_ARGS, ""); // NOI18N
        ep.setComment(ProjectProperties.RUN_JVM_ARGS, new String[] {
            "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_run.jvmargs"), // NOI18N
            "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_run.jvmargs_2"), // NOI18N
            "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_run.jvmargs_3"), // NOI18N
        }, false);

        ep.setProperty(ProjectProperties.JAVADOC_PRIVATE, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_NO_TREE, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_USE, "true"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_NO_NAVBAR, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_NO_INDEX, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_HTML5, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_SPLIT_INDEX, "true"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_AUTHOR, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_VERSION, "false"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_WINDOW_TITLE, ""); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_ENCODING, "${"+ProjectProperties.SOURCE_ENCODING+"}"); // NOI18N
        ep.setProperty(ProjectProperties.JAVADOC_ADDITIONALPARAM, ""); // NOI18N
        Charset enc = FileEncodingQuery.getDefaultEncoding();
        ep.setProperty(ProjectProperties.SOURCE_ENCODING, enc.name());
        ep.setProperty(ProjectProperties.DIST_ARCHIVE_EXCLUDES,""); //NOI18N
        ep.setComment(ProjectProperties.DIST_ARCHIVE_EXCLUDES,
                new String[] {
                    "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_dist.archive.excludes") //NOI18N
                },
                false);
        //JLink
        ep.setProperty(ProjectProperties.DIST_JLINK_DIR, "${"+ProjectProperties.DIST_DIR+"}/jlink");
        ep.setProperty(ProjectProperties.DIST_JLINK_OUTPUT, "${"+ProjectProperties.DIST_JLINK_DIR+"}/"+antName);
        ep.setProperty(ProjectProperties.JLINK_ADDITIONALMODULES, "");
        ep.setComment(ProjectProperties.JLINK_ADDITIONALMODULES,
                new String[] {
                    "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_jlink.additionalmodules") //NOI18N
                },
                false);
        ep.setProperty(ProjectProperties.JLINK_ADDITIONALPARAM, "");
        ep.setComment(ProjectProperties.JLINK_ADDITIONALPARAM,
                new String[] {
                    "# " + NbBundle.getMessage(J2SEModularProjectGenerator.class, "COMMENT_jlink.additionalparam") //NOI18N
                },
                false);
        ep.setProperty(ProjectProperties.JLINK_LAUNCHER, "true");
        ep.setProperty(ProjectProperties.JLINK_LAUNCHER_NAME, antName);
        h.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, ep);
        ep = h.getProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH);
        ep.setProperty(ProjectProperties.COMPILE_ON_SAVE, "true"); // NOI18N
        h.putProperties(AntProjectHelper.PRIVATE_PROPERTIES_PATH, ep);
        logUsage(Action.OPEN);
        return h;
    }

    private static SpecificationVersion getSourceLevel (final JavaPlatform platform) {
        if (defaultSourceLevel != null) {
            return defaultSourceLevel;
        } else {            
            return platform.getSpecification().getVersion();
        }
    }

    private static String ref(@NonNull final String propertyName, final boolean lastEntry) {
        return String.format("${%s}%s", propertyName, lastEntry ? "" : ":");  //NOI18N
    }

    // http://wiki.netbeans.org/UsageLoggingSpecification
    static void logUsage(@NonNull Action action) {
        assert action != null;
        Logger logger = Logger.getLogger(J2SEModularProjectGenerator.METRICS_LOGGER);
        LogRecord logRecord = new LogRecord(Level.INFO, action.getGenericLogMessage());
        logRecord.setLoggerName(logger.getName());
        logRecord.setParameters(new Object[]{
            J2SEModularProject.TYPE
        });
        logger.log(logRecord);
        
        logger = Logger.getLogger(J2SEModularProjectGenerator.J2SE_MODULAR_METRICS_LOGGER);
        logRecord = new LogRecord(Level.INFO, action.getSpecificLogMessage());
        logRecord.setLoggerName(logger.getName());
        logger.log(logRecord);
    }

    //------------ Used by unit tests -------------------

    private static SpecificationVersion defaultSourceLevel;
                
    /**
     * Unit test only method. Sets the default source level for tests
     * where the default platform is not available.
     * @param version the default source level set to project when it is created
     *
     */
    public static void setDefaultSourceLevel (SpecificationVersion version) {
        defaultSourceLevel = version;
    }
}


