/*
 * VisualBasicParserFile.java
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the BSD license.
 *
 * This program 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
 * LICENSE.txt file for more details.
 *
 * Copyright (c) 2004 Adrian Moore. All rights reserved.
 * Copyright (c) 2003-2015 Per Cederberg. All rights reserved.
 */

package net.percederberg.grammatica.output;

import java.io.IOException;
import java.util.HashMap;

import net.percederberg.grammatica.code.visualbasic.VisualBasicClass;
import net.percederberg.grammatica.code.visualbasic.VisualBasicComment;
import net.percederberg.grammatica.code.visualbasic.VisualBasicConstructor;
import net.percederberg.grammatica.code.visualbasic.VisualBasicEnumeration;
import net.percederberg.grammatica.code.visualbasic.VisualBasicFile;
import net.percederberg.grammatica.code.visualbasic.VisualBasicImports;
import net.percederberg.grammatica.code.visualbasic.VisualBasicMethod;
import net.percederberg.grammatica.code.visualbasic.VisualBasicNamespace;
import net.percederberg.grammatica.parser.ProductionPattern;
import net.percederberg.grammatica.parser.ProductionPatternAlternative;
import net.percederberg.grammatica.parser.ProductionPatternElement;

/**
 * The Visual Basic parser file generator. This class encapsulates all
 * the Visual Basic (.NET) code necessary for creating a parser.
 *
 * @author   Adrian Moore
 * @author   Per Cederberg
 * @version  1.5
 * @since    1.5
 */
class VisualBasicParserFile {

    /**
     * The type comment.
     */
    private static final String TYPE_COMMENT =
        "<remarks>A token stream parser.</remarks>";

    /**
     * The production enumeration comment.
     */
    private static final String ENUM_COMMENT =
        "<summary>An enumeration with the generated production node\n" +
        "identity constants.</summary>";

    /**
     * The first constructor comment.
     */
    private static final String CONSTRUCTOR1_COMMENT =
        "<summary>Creates a new parser with a default analyzer.</summary>\n\n" +
        "<param name='input'>the input stream to read from</param>\n\n" +
        "<exception cref='ParserCreationException'>if the parser\n" +
        "couldn't be initialized correctly</exception>";

    /**
     * The second constructor comment.
     */
    private static final String CONSTRUCTOR2_COMMENT =
        "<summary>Creates a new parser.</summary>\n\n" +
        "<param name='input'>the input stream to read from</param>\n\n" +
        "<param name='analyzer'>the analyzer to parse with</param>\n\n" +
        "<exception cref='ParserCreationException'>if the parser\n" +
        "couldn't be initialized correctly</exception>";

    /**
     * The tokenizer factory method comment.
     */
    private static final String FACTORY_COMMENT =
        "<summary>Creates a new tokenizer for this parser. Can be overridden\n" +
        "by a subclass to provide a custom implementation.</summary>\n\n" +
        "<param name='input'>the input stream to read from</param>\n\n" +
        "<returns>the tokenizer created</returns>\n\n" +
        "<exception cref='ParserCreationException'>if the tokenizer\n" +
        "couldn't be initialized correctly</exception>";

    /**
     * The init method comment.
     */
    private static final String INIT_METHOD_COMMENT =
        "<summary>Initializes the parser by creating all the production\n" +
        "patterns.</summary>\n\n" +
        "<exception cref='ParserCreationException'>if the parser\n" +
        "couldn't be initialized correctly</exception>";

    /**
     * The parser generator.
     */
    private VisualBasicParserGenerator gen;

    /**
     * The file to write.
     */
    private VisualBasicFile file;

    /**
     * The class to write.
     */
    private VisualBasicClass cls;

    /**
     * The syntetic contants enumeration.
     */
    private VisualBasicEnumeration enm;

    /**
     * The class initializer method.
     */
    private VisualBasicMethod initMethod;

    /**
     * A map with the production constants in this class. This map
     * is indexed with the pattern id and contains the production
     * constant name.
     */
    private HashMap constantNames = new HashMap();

    /**
     * The first available constant id number.
     */
    private int constantId = 1;

    /**
     * Creates a new parser file.
     *
     * @param gen            the parser generator to use
     * @param tokenizer      the tokenizer file generator
     * @param analyzer       the analyzer file generator
     */
    public VisualBasicParserFile(VisualBasicParserGenerator gen,
                                 VisualBasicTokenizerFile tokenizer,
                                 VisualBasicAnalyzerFile analyzer) {

        String  name = gen.getBaseName() + "Parser";
        int     modifiers;

        this.gen = gen;
        this.file = new VisualBasicFile(gen.getBaseDir(), name);
        if (gen.getPublicAccess()) {
            modifiers = VisualBasicClass.PUBLIC;
        } else {
            modifiers = VisualBasicClass.FRIEND;
        }
        this.cls = new VisualBasicClass(modifiers,
                                        name,
                                        "RecursiveDescentParser");
        this.enm = new VisualBasicEnumeration(VisualBasicEnumeration.PRIVATE,
                                              "SynteticPatterns");
        this.initMethod = new VisualBasicMethod(VisualBasicMethod.PRIVATE,
                                                "CreatePatterns",
                                                "",
                                                "");
        initializeCode(tokenizer, analyzer);
    }

    /**
     * Initializes the source code objects.
     *
     * @param tokenizer      the tokenizer file generator
     * @param analyzer       the analyzer file generator
     */
    private void initializeCode(VisualBasicTokenizerFile tokenizer,
                                VisualBasicAnalyzerFile analyzer) {
        VisualBasicConstructor  constr;
        VisualBasicMethod       method;
        VisualBasicNamespace    n;
        String                  str;

        // Add using
        file.addImports(new VisualBasicImports("System.IO"));
        file.addImports(new VisualBasicImports("PerCederberg.Grammatica.Runtime"));

        // Add namespace
        if (gen.getNamespace() == null) {
            file.addClass(cls);
        } else {
            n = new VisualBasicNamespace(gen.getNamespace());
            n.addClass(cls);
            file.addNamespace(n);
        }

        // Add file comment
        str = file.toString() + "\n\n" + gen.getFileComment();
        file.addComment(new VisualBasicComment(VisualBasicComment.SINGLELINE,
                                               str));

        // Add type comment
        cls.addComment(new VisualBasicComment(TYPE_COMMENT));

        // Add enumeration
        cls.addEnumeration(enm);
        enm.addComment(new VisualBasicComment(ENUM_COMMENT));

        // Add constructor
        constr = new VisualBasicConstructor("ByVal input As TextReader");
        cls.addConstructor(constr);
        constr.addComment(new VisualBasicComment(CONSTRUCTOR1_COMMENT));
        constr.addCode("MyBase.New(input)");
        constr.addCode("CreatePatterns()");

        // Add constructor
        constr = new VisualBasicConstructor("ByVal input As TextReader, " +
                                            "ByVal analyzer As " +
                                            analyzer.getClassName());
        cls.addConstructor(constr);
        constr.addComment(new VisualBasicComment(CONSTRUCTOR2_COMMENT));
        constr.addCode("MyBase.New(input, analyzer)");
        constr.addCode("CreatePatterns()");

        // Add tokenizer factory method
        method = new VisualBasicMethod(VisualBasicMethod.PROTECTED + VisualBasicMethod.OVERRIDES,
                                      "NewTokenizer",
                                      "ByVal input As TextReader",
                                      "Tokenizer");
        method.addComment(new VisualBasicComment(FACTORY_COMMENT));
        method.addCode("Return New " + tokenizer.getClassName() + "(input);");
        cls.addMethod(method);

        // Add init method
        cls.addMethod(initMethod);
        initMethod.addComment(new VisualBasicComment(INIT_METHOD_COMMENT));
        initMethod.addCode("Dim pattern As ProductionPattern");
        initMethod.addCode("Dim alt As ProductionPatternAlternative");
    }

    /**
     * Adds a production constant definition to this file.
     *
     * @param pattern        the production pattern
     */
    public void addProductionConstant(ProductionPattern pattern) {
        String   constant;

        if (pattern.isSynthetic()) {
            constant = "SUBPRODUCTION_" + constantId;
            enm.addConstant(constant, String.valueOf(constantId + 3000));
            constantNames.put(new Integer(pattern.getId()), constant);
            constantId++;
        }
    }

    /**
     * Adds a production pattern definition to this file.
     *
     * @param pattern        the production pattern
     * @param constants      the constants file generator
     */
    public void addProduction(ProductionPattern pattern,
                              VisualBasicConstantsFile constants) {
        StringBuffer  code;
        String        str;

        // Create new pattern
        code = new StringBuffer();
        code.append("pattern = New ProductionPattern(CInt(");
        code.append(getConstant(constants, pattern.getId()));
        code.append("), \"");
        if (pattern.isSynthetic()) {
            str = (String) constantNames.get(new Integer(pattern.getId()));
            code.append(gen.getCodeStyle().getMixedCase(str, true));
        } else {
            code.append(pattern.getName());
        }
        code.append("\")");
        initMethod.addCode("");
        initMethod.addCode(code.toString());

        // Set syntetic flag
        if (pattern.isSynthetic()) {
            initMethod.addCode("pattern.Synthetic = True");
        }

        // Create pattern rules
        for (int i = 0; i < pattern.getAlternativeCount(); i++) {
            addProductionAlternative(pattern.getAlternative(i),
                                     constants);
        }

        // Add pattern to parser
        initMethod.addCode("AddPattern(pattern)");
    }

    /**
     * Adds a production pattern alternative definition to the init
     * method.
     *
     * @param alt            the production pattern alternative
     * @param constants      the constants file generator
     */
    private void addProductionAlternative(ProductionPatternAlternative alt,
                                          VisualBasicConstantsFile constants) {

        ProductionPatternElement  elem;
        StringBuffer              code;

        initMethod.addCode("alt = New ProductionPatternAlternative()");
        for (int i = 0; i < alt.getElementCount(); i++) {
            elem = alt.getElement(i);
            code = new StringBuffer();
            code.append("alt.");
            if (elem.isToken()) {
                code.append("AddToken(");
            } else {
                code.append("AddProduction(");
            }
            code.append("CInt(");
            code.append(getConstant(constants, elem.getId()));
            code.append("), ");
            code.append(elem.getMinCount());
            code.append(", ");
            if (elem.getMaxCount() == Integer.MAX_VALUE) {
                code.append("-1");
            } else {
                code.append(elem.getMaxCount());
            }
            code.append(")");
            initMethod.addCode(code.toString());
        }
        initMethod.addCode("pattern.AddAlternative(alt)");
    }

    /**
     * Returns the constant name for a specified pattern or token id.
     *
     * @param constants      the constants file
     * @param id             the pattern id
     *
     * @return the constant name to use
     */
    private String getConstant(VisualBasicConstantsFile constants, int id) {
        Integer  value = new Integer(id);

        if (constantNames.containsKey(value)) {
            return "SynteticPatterns." + constantNames.get(value);
        } else {
            return constants.getConstant(id);
        }
    }

    /**
     * Writes the file source code.
     *
     * @throws IOException if the output file couldn't be created
     *             correctly
     */
    public void writeCode() throws IOException {
        file.writeCode(gen.getCodeStyle());
    }
}
