////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2020 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

package com.github.checkstyle.site;

import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

import com.github.checkstyle.Main;
import com.github.checkstyle.data.CheckstyleRecord;
import com.github.checkstyle.data.CliPaths;
import com.github.checkstyle.data.DiffReport;
import com.github.checkstyle.data.MergedConfigurationModule;
import com.github.checkstyle.data.Statistics;

/**
 * Generates site report using thymeleaf template engine. Instead of single
 * template 3 smaller ones are used with purpose of avoiding creation of extra
 * large Context instance.
 *
 * @author attatrol
 */
public final class SiteGenerator {

    /**
     * Name for the site file.
     */
    public static final Path SITEPATH = Paths.get("index.html");

    /**
     * Pattern for a common file name beginning.
     */
    private static final Pattern COMMON_FILENAME_BEGINNING = Pattern.compile("src/main/java/.*");

    /**
     * Common filename beginning length.
     */
    private static final int COMMON_FILENAME_BEGINNING_LENGTH = 14;

    /**
     * Private ctor, please use generate method.
     */
    private SiteGenerator() {
    }

    /**
     * Generates site report using thymeleaf template engine.
     *
     * @param diffReport
     *        container with parsed data.
     * @param diffConfiguration
     *        merged configurations from both reports.
     * @param paths
     *        CLI paths.
     * @throws IOException
     *         on failure to write site to disc.
     */
    public static void generate(DiffReport diffReport, MergedConfigurationModule diffConfiguration,
            CliPaths paths) throws IOException {
        // setup thymeleaf engine
        final TemplateEngine tplEngine = getTemplateEngine();
        // setup xreference generator
        final XrefGenerator xrefGenerator = new XrefGenerator(paths.getRefFilesPath(),
                paths.getOutputPath().resolve(Main.XREF_FILEPATH), paths.getOutputPath());
        // html generation
        final Path sitepath = paths.getOutputPath().resolve(SITEPATH);
        final FileWriter writer = new FileWriter(sitepath.toString());
        try {
            // write statistics
            generateHeader(tplEngine, writer, diffReport.getStatistics(), diffConfiguration);
            // write parsed content
            generateBody(tplEngine, writer, diffReport, paths, xrefGenerator);
            // write html footer
            tplEngine.process("footer", new Context(), writer);
        }
        finally {
            writer.close();
        }
    }

    /**
     * Creates thymeleaf template engine.
     *
     * @return template engine.
     */
    private static TemplateEngine getTemplateEngine() {
        final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setTemplateMode("HTML");
        templateResolver.setPrefix("/");
        templateResolver.setSuffix(".template");
        final TemplateEngine tplEngine = new TemplateEngine();
        tplEngine.setTemplateResolver(templateResolver);
        return tplEngine;
    }

    /**
     * Creates beginning part of resulting site.
     *
     * @param tplEngine
     *        thymeleaf template engine.
     * @param writer
     *        file writer.
     * @param statistics
     *        container for statistics.
     * @param diffConfiguration
     *        merged configurations from both reports.
     */
    private static void generateHeader(TemplateEngine tplEngine, FileWriter writer,
            Statistics statistics, MergedConfigurationModule diffConfiguration) {
        final Context context = new Context();
        context.setVariable("statistics", statistics);
        context.setVariable("config", diffConfiguration);
        tplEngine.process("header", context, writer);
    }

    /**
     * Creates main part of resulting site.
     *
     * @param tplEngine
     *        thymeleaf template engine.
     * @param writer
     *        file writer.
     * @param diffReport
     *        difference between two checkstyle reports.
     * @param paths
     *        CLI paths.
     * @param xrefGenerator
     *        xReference generator.
     */
    private static void generateBody(TemplateEngine tplEngine, FileWriter writer,
            DiffReport diffReport, CliPaths paths, XrefGenerator xrefGenerator) {
        final AnchorCounter anchorCounter = new AnchorCounter();

        final Path refFilesPath = paths.getRefFilesPath();
        for (Map.Entry<String, List<CheckstyleRecord>> entry : diffReport.getRecords().entrySet()) {
            final List<CheckstyleRecord> records = entry.getValue();
            String filename = entry.getKey();

            xrefGenerator.reset();

            for (CheckstyleRecord record : records) {
                final String xreference = xrefGenerator.generateXref(record.getXref(),
                            paths.isShortFilePaths());
                record.setXref(xreference);
            }

            if (refFilesPath != null) {
                try {
                    filename = refFilesPath.relativize(Paths.get(filename)).toString();
                }
                catch (IllegalArgumentException ignore) {
                    // use original file name
                }
            }
            generateContent(tplEngine, writer, records, shortenFilename(filename),
                    anchorCounter);
        }
    }

    /**
     * Appends to the site a table with parsed data for a single file entry.
     *
     * @param tplEngine
     *        thymeleaf template engine.
     * @param writer
     *        file writer.
     * @param records
     *        checkstyle records for a single file.
     * @param filename
     *        current file name from checkstyle reports.
     * @param anchorCounter
     *        anchor links provider.
     */
    private static void generateContent(TemplateEngine tplEngine, FileWriter writer,
            List<CheckstyleRecord> records, String filename,
            AnchorCounter anchorCounter) {
        final Context context = new Context();
        context.setVariable("filename", filename);
        context.setVariable("records", records);
        context.setVariable("anchor", anchorCounter);
        tplEngine.process("content", context, writer);
    }

    /**
     * Removes "src/main/java/" from filename beginning.
     *
     * @param filename
     *        file name.
     * @return shortened
     *         file name.
     */
    private static String shortenFilename(String filename) {
        final String shortenedFilename;
        if (COMMON_FILENAME_BEGINNING.matcher(filename).matches()) {
            shortenedFilename = filename.substring(COMMON_FILENAME_BEGINNING_LENGTH,
                    filename.length());
        }
        else {
            shortenedFilename = filename;
        }
        return shortenedFilename;
    }

}