/*
 *  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.apache.isis.tool.mavenplugin;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.io.Files;

import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import org.apache.isis.applib.services.jaxb.JaxbService;
import org.apache.isis.core.commons.factory.InstanceUtil;
import org.apache.isis.core.runtime.system.session.IsisSessionFactory;

@Mojo(
        name = "xsd",
        defaultPhase = LifecyclePhase.GENERATE_RESOURCES,
        requiresProject = true,
        requiresDependencyResolution = ResolutionScope.COMPILE,
        requiresDependencyCollection = ResolutionScope.COMPILE
)
public class IsisMojoXsd extends IsisMojoAbstract {

    protected IsisMojoXsd() {
        super();
    }

    /**
     * List of JAXB-annotated classes to generate schema for.
     */
    @Parameter(required = true, readonly = false, property = "jaxbClasses")
    private List<String> jaxbClasses;

    /**
     * Directory to generate XSD schemas; defaults to <code>target/generated-resources/isis-xsd</code>
     */
    @Parameter(required = false, readonly = false, property = "output", defaultValue = "generated-resources/isis-xsd")
    private String output;

    /**
     * Whether a separate directory is created for each JAXB-class, or whether to generate all together (so that they
     * are created as a single bundle).
     */
    @Parameter(required = false, readonly = false, property = "separate", defaultValue = "false")
    private boolean separate;

    /**
     * Whether to also generate the isis common schema(s).
     */
    @Parameter(required = false, readonly = false, property = "commonSchemas", defaultValue = "false")
    private boolean commonSchemas;

    @Override
    protected void doExecute(
            final ContextForMojo context, final IsisSessionFactory isisSessionFactory)
            throws MojoFailureException, IOException {

        final JaxbService jaxbService = isisSessionFactory.getServicesInjector().lookupService(JaxbService.class);

        final MavenProject mavenProject = context.getMavenProject();
        final File outputDir = determineOutputDir(mavenProject);

        for (String jaxbClass : jaxbClasses) {
            writeSchemas(jaxbService, jaxbClass, outputDir);
        }
    }

    private File determineOutputDir(final MavenProject mavenProject) {
        final String targetDir = mavenProject.getBuild().getDirectory();
        final String outputDirStr = targetDir + File.separator + output;
        return new File(outputDirStr);
    }

    /**
     * to ensure that any schemas with no namespace are given unique file names when written out
     */
    private int unnamed;
    private void writeSchemas(
            final JaxbService jaxbService,
            final String dtoClassName,
            final File outputDir) throws MojoFailureException {

        final Object instance = InstanceUtil.createInstance(dtoClassName);
        final Map<String, String> schemaByNamespace =
                jaxbService.toXsd(
                        instance,
                        commonSchemas ? JaxbService.IsisSchemas.INCLUDE: JaxbService.IsisSchemas.IGNORE);

        final File schemaDir = separate? new File(outputDir, dtoClassName): outputDir;

        unnamed = 0;
        for (Map.Entry<String, String> entry : schemaByNamespace.entrySet()) {
            final String namespaceUri = entry.getKey();
            final String schemaText = entry.getValue();

            final String xsdName = xsdDirNameFor(namespaceUri);
            final File schemaFile = new File(schemaDir, xsdName);
            try {
                Files.createParentDirs(schemaFile);
            } catch (IOException e) {
                throw new MojoFailureException( String.format("Failed to create dir: '%s'", schemaFile.getParent()));
            }
            try {
                Files.write(schemaText, schemaFile, Charsets.UTF_8);
            } catch (IOException e) {
                throw new MojoFailureException("Failed to write out " + schemaFile);
            }
            if(!xsdName.endsWith(".xsd")) {
                final File schemaFileWithXsdSuffix = new File(schemaDir, xsdName + ".xsd");
                try {
                    Files.copy(schemaFile, schemaFileWithXsdSuffix);
                } catch (IOException e) {
                    throw new MojoFailureException("Failed to copy to " + schemaFileWithXsdSuffix);
                }
            }
        }
    }

    String xsdDirNameFor(final String namespaceUri) {
        final String sanitized = sanitize(namespaceUri);
        final String sanitizedElseUnnamed = Strings.isNullOrEmpty(sanitized) ? ("unnamed-" + (unnamed++)) : sanitized;
        return sanitizedElseUnnamed;
    }

    private static String sanitize(final String namespaceUri) {
        String sanitized = namespaceUri;
        sanitized = removePrefix(sanitized, "http://");
        sanitized = removePrefix(sanitized, "https://");
        sanitized = sanitized.replaceAll(":", "_");
        return  sanitized.replaceAll("[\\\\\\/]", File.separator + File.separator);
    }

    private static String removePrefix(final String str, final String prefix) {
        return str.startsWith(prefix)? str.substring(prefix.length()): str;
    }

}