/*
 * JavaParserGenerator.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) 2003-2015 Per Cederberg. All rights reserved.
 */

package net.percederberg.grammatica.output;

import java.io.IOException;

import net.percederberg.grammatica.Grammar;
import net.percederberg.grammatica.code.CodeStyle;
import net.percederberg.grammatica.code.java.JavaFile;
import net.percederberg.grammatica.code.java.JavaPackage;
import net.percederberg.grammatica.parser.ProductionPattern;
import net.percederberg.grammatica.parser.TokenPattern;

/**
 * A Java parser generator. This class generates the source code files
 * needed for a Java parser.
 *
 * @author   Per Cederberg
 * @version  1.5
 */
public class JavaParserGenerator extends ParserGenerator {

    /**
     * The fully qualified Java package name.
     */
    private String basePackage = null;

    /**
     * The Java class name prefix.
     */
    private String baseName = null;

    /**
     * The public class and interface access flag.
     */
    private boolean publicAccess = false;

    /**
     * The Java class comment.
     */
    private String classComment = null;

    /**
     * Creates a new Java parser generator.
     *
     * @param grammar        the grammar to use
     */
    public JavaParserGenerator(Grammar grammar) {
        super(grammar);
        initialize();
    }

    /**
     * Initializes various instance variables.
     */
    private void initialize() {
        StringBuffer  buffer;
        String        str;

        // Set base name
        str = getGrammar().getFileName();
        if (str.indexOf('/') >= 0) {
            str = str.substring(str.lastIndexOf('/') + 1);
        }
        if (str.indexOf('\\') >= 0) {
            str = str.substring(str.lastIndexOf('\\') + 1);
        }
        if (str.indexOf('.') > 0) {
            str = str.substring(0, str.indexOf('.'));
        }
        if (Character.isLowerCase(str.charAt(0))) {
            str = Character.toUpperCase(str.charAt(0)) + str.substring(1);
        }
        baseName = str;

        // Create class comment
        buffer = new StringBuffer();
        str = getGrammar().getDeclaration(Grammar.AUTHOR_DECLARATION);
        if (str != null) {
            buffer.append("@author   ");
            buffer.append(str);
        }
        str = getGrammar().getDeclaration(Grammar.VERSION_DECLARATION);
        if (str != null) {
            if (buffer.length() > 0) {
                buffer.append("\n");
            }
            buffer.append("@version  ");
            buffer.append(str);
        }
        classComment = buffer.toString();
    }

    /**
     * Returns the Java package where the classes will be created.
     *
     * @return the fully qualified Java package name
     */
    public String getBasePackage() {
        return basePackage;
    }

    /**
     * Sets the Java package name where the classes will be created.
     *
     * @param pkg            the fully qualified package name
     */
    public void setBasePackage(String pkg) {
        this.basePackage = pkg;
    }

    /**
     * Returns the Java class name prefix.
     *
     * @return the Java class name prefix
     */
    public String getBaseName() {
        return baseName;
    }

    /**
     * Sets the Java class name prefix.
     *
     * @param name           the Java class name prefix
     */
    public void setBaseName(String name) {
        this.baseName = name;
    }

    /**
     * Returns the public access flag.
     *
     * @return true if the classes should have public access, or
     *         false otherwise
     */
    public boolean getPublicAccess() {
        return publicAccess;
    }

    /**
     * Sets the public access flag.
     *
     * @param flag           the new public access flag value
     */
    public void setPublicAccess(boolean flag) {
        publicAccess = flag;
    }

    /**
     * Returns the Java code style to use.
     *
     * @return the Java code style to use
     */
    public CodeStyle getCodeStyle() {
        return CodeStyle.JAVA;
    }

    /**
     * Returns the Java class comment.
     *
     * @return the Java class comment
     */
    public String getClassComment() {
        return classComment;
    }

    /**
     * Writes the Java source code files.
     *
     * @throws IOException if the files couldn't be written correctly
     */
    public void write() throws IOException {
        Grammar            grammar = getGrammar();
        JavaConstantsFile  constants = new JavaConstantsFile(this);
        JavaTokenizerFile  tokenizer = new JavaTokenizerFile(this);
        JavaAnalyzerFile   analyzer = new JavaAnalyzerFile(this);
        JavaParserFile     parser = new JavaParserFile(this, tokenizer, analyzer);
        TokenPattern       token;
        ProductionPattern  production;
        int                i;

        // Create token declarations
        for (i = 0; i < grammar.getTokenPatternCount(); i++) {
            token = grammar.getTokenPattern(i);
            constants.addToken(token);
            tokenizer.addToken(token, constants);
            analyzer.addToken(token, constants);
        }

        // Create production constants
        for (i = 0; i < grammar.getProductionPatternCount(); i++) {
            production = grammar.getProductionPattern(i);
            constants.addProduction(production);
            parser.addProductionConstant(production);
            analyzer.addProduction(production, constants);
        }

        // Create production definitions
        for (i = 0; i < grammar.getProductionPatternCount(); i++) {
            production = grammar.getProductionPattern(i);
            parser.addProduction(production, constants);
        }

        // Write source code files
        constants.writeCode();
        tokenizer.writeCode();
        parser.writeCode();
        analyzer.writeCode();
    }

    /**
     * Creates a Java file in the correct base directory. The package
     * will be set if applicable.
     *
     * @return a new Java file
     */
    public JavaFile createJavaFile() {
        if (basePackage == null) {
            return new JavaFile(getBaseDir());
        } else {
            return new JavaFile(getBaseDir(),
                                new JavaPackage(getBasePackage()));
        }
    }
}
