/*
 * [The "BSD license"]
 *  Copyright (c) 2010 Terence Parr
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.antlr.tool;

import org.antlr.Tool;
import org.antlr.codegen.CodeGenerator;
import org.antlr.misc.Utils;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroup;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/** Given a grammar file, show the dependencies on .tokens etc...
 *  Using ST, emit a simple "make compatible" list of dependencies.
 *  For example, combined grammar T.g (no token import) generates:
 *
 *		TParser.java : T.g
 * 		T.tokens : T.g
 * 		T__g : T.g
 *
 *  For tree grammar TP with import of T.tokens:
 *
 * 		TP.g : T.tokens
 * 		TP.java : TP.g
 *
 *  If "-lib libdir" is used on command-line with -depend, then include the
 *  path like
 *
 * 		TP.g : libdir/T.tokens
 *
 *  Pay attention to -o as well:
 *
 * 		outputdir/TParser.java : T.g
 *
 *  So this output shows what the grammar depends on *and* what it generates.
 *
 *  Operate on one grammar file at a time.  If given a list of .g on the
 *  command-line with -depend, just emit the dependencies.  The grammars
 *  may depend on each other, but the order doesn't matter.  Build tools,
 *  reading in this output, will know how to organize it.
 *
 *  This is a wee bit slow probably because the code generator has to load
 *  all of its template files in order to figure out the file extension
 *  for the generated recognizer.
 *
 *  This code was obvious until I removed redundant "./" on front of files
 *  and had to escape spaces in filenames :(
 */
public class BuildDependencyGenerator {
    protected String grammarFileName;
    protected String tokenVocab;
    protected Tool tool;
    protected Grammar grammar;
    protected CodeGenerator generator;
    protected STGroup templates;

    public BuildDependencyGenerator(Tool tool, String grammarFileName)
            throws IOException {
        this.tool = tool;
        this.grammarFileName = grammarFileName;
        grammar = tool.getRootGrammar(grammarFileName);
        String language = (String) grammar.getOption("language");
        generator = new CodeGenerator(tool, grammar, language);
        generator.loadTemplates(language);
    }

    /** From T.g return a list of File objects that
     *  name files ANTLR will emit from T.g.
     */
    public List<File> getGeneratedFileList() {
        List<File> files = new ArrayList<File>();
        File outputDir = tool.getOutputDirectory(grammarFileName);
        if (outputDir.getName().equals(".")) {
            outputDir = outputDir.getParentFile();
        } else if (outputDir.getName().indexOf(' ') >= 0) { // has spaces?
            String escSpaces = Utils.replace(outputDir.toString(),
                    " ",
                    "\\ ");
            outputDir = new File(escSpaces);
        }
        // add generated recognizer; e.g., TParser.java
        String recognizer =
                generator.getRecognizerFileName(grammar.name, grammar.type);
        files.add(new File(outputDir, recognizer));
        // add output vocab file; e.g., T.tokens. This is always generated to
        // the base output directory, which will be just . if there is no -o option
        //
        files.add(new File(tool.getOutputDirectory(), generator.getVocabFileName()));
        // are we generating a .h file?
        ST headerExtST = null;
        ST extST = generator.getTemplates().getInstanceOf("codeFileExtension");
        if (generator.getTemplates().isDefined("headerFile")) {
            headerExtST = generator.getTemplates().getInstanceOf("headerFileExtension");
            String suffix = Grammar.grammarTypeToFileNameSuffix[grammar.type];
            String fileName = grammar.name + suffix + headerExtST.render();
            files.add(new File(outputDir, fileName));
        }
        if (grammar.type == Grammar.COMBINED) {
            // add autogenerated lexer; e.g., TLexer.java TLexer.h TLexer.tokens
            // don't add T__.g (just a temp file)
            
            String suffix = Grammar.grammarTypeToFileNameSuffix[Grammar.LEXER];
            String lexer = grammar.name + suffix + extST.render();
            files.add(new File(outputDir, lexer));

            // TLexer.h
            if (headerExtST != null) {
                String header = grammar.name + suffix + headerExtST.render();
                files.add(new File(outputDir, header));
            }
        // for combined, don't generate TLexer.tokens
        }

        // handle generated files for imported grammars
        List<Grammar> imports =
                grammar.composite.getDelegates(grammar.composite.getRootGrammar());
        for (Grammar g : imports) {
            outputDir = tool.getOutputDirectory(g.getFileName());
            String fname = groomQualifiedFileName(outputDir.toString(), g.getRecognizerName() + extST.render());
            files.add(new File(fname));
        }

        if (files.isEmpty()) {
            return null;
        }
        return files;
    }

    /**
     * Return a list of File objects that name files ANTLR will read
     * to process T.g; This can be .tokens files if the grammar uses the tokenVocab option
     * as well as any imported grammar files.
     */
    public List<File> getDependenciesFileList() {
        // Find all the things other than imported grammars
        List<File> files = getNonImportDependenciesFileList();

        // Handle imported grammars
        List<Grammar> imports =
                grammar.composite.getDelegates(grammar.composite.getRootGrammar());
        for (Grammar g : imports) {
            String libdir = tool.getLibraryDirectory();
            String fileName = groomQualifiedFileName(libdir, g.fileName);
            files.add(new File(fileName));
        }

        if (files.isEmpty()) {
            return null;
        }
        return files;
    }

    /**
     * Return a list of File objects that name files ANTLR will read
     * to process T.g; This can only be .tokens files and only
     * if they use the tokenVocab option.
     *
     * @return List of dependencies other than imported grammars
     */
    public List<File> getNonImportDependenciesFileList() {
        List<File> files = new ArrayList<File>();

        // handle token vocabulary loads
        tokenVocab = (String) grammar.getOption("tokenVocab");
        if (tokenVocab != null) {

            File vocabFile = tool.getImportedVocabFile(tokenVocab);
            files.add(vocabFile);
        }

        return files;
    }

    public ST getDependencies() {
        loadDependencyTemplates();
        ST dependenciesST = templates.getInstanceOf("dependencies");
        dependenciesST.add("in", getDependenciesFileList());
        dependenciesST.add("out", getGeneratedFileList());
        dependenciesST.add("grammarFileName", grammar.fileName);
        return dependenciesST;
    }

    public void loadDependencyTemplates() {
        if (templates != null) return;
        String fileName = "org/antlr/tool/templates/depend.stg";
        templates = new ToolSTGroupFile(fileName);
    }

    public String getTokenVocab() {
        return tokenVocab;
    }

    public CodeGenerator getGenerator() {
        return generator;
    }    

    public String groomQualifiedFileName(String outputDir, String fileName) {
        if (outputDir.equals(".")) {
            return fileName;
        } else if (outputDir.indexOf(' ') >= 0) { // has spaces?
            String escSpaces = Utils.replace(outputDir.toString(),
                    " ",
                    "\\ ");
            return escSpaces + File.separator + fileName;
        } else {
            return outputDir + File.separator + fileName;
        }
    }
}
