/*
 * ValidationElement.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.ant;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

import net.percederberg.grammatica.Grammar;
import net.percederberg.grammatica.GrammarException;
import net.percederberg.grammatica.TreePrinter;
import net.percederberg.grammatica.parser.Analyzer;
import net.percederberg.grammatica.parser.Node;
import net.percederberg.grammatica.parser.ParseException;
import net.percederberg.grammatica.parser.Parser;
import net.percederberg.grammatica.parser.ParserCreationException;
import net.percederberg.grammatica.parser.ParserLogException;
import net.percederberg.grammatica.parser.Token;
import net.percederberg.grammatica.parser.Tokenizer;

/**
 * A grammar validation element. This element validates or tests the
 * grammar in various ways.
 *
 * @author   Per Cederberg
 * @version  1.4
 * @since    1.4
 */
public class ValidationElement implements ProcessingElement {

    /**
     * The validation type.
     */
    private String type = null;

    /**
     * The input test file.
     */
    private File file = null;

    /**
     * The quiet output flag.
     */
    private boolean quiet = false;

    /**
     * Creates a new validation element.
     */
    public ValidationElement() {
        // Nothing to do here
    }

    /**
     * Sets the validation type. The type must be one of "debug",
     * "tokenize", "parse", or "profile".
     *
     * @param type           the validation type
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * Sets the input test file. The test file is not needed for the
     * debug validation type.
     *
     * @param file           the input test file
     */
    public void setInputfile(File file) {
        this.file = file;
    }

    /**
     * Sets the quiet output flag.
     *
     * @param quiet          the quiet output flag
     */
    public void setQuiet(boolean quiet) {
        this.quiet = quiet;
    }

    /**
     * Validates all attributes in the element.
     *
     * @throws RuntimeException if some attribute was missing or had an
     *             invalid value
     */
    public void validate() throws RuntimeException {
        if (type == null) {
            throw new RuntimeException(
                "missing 'type' attribute in <validate>");
        }
        if (!type.equals("debug")
         && !type.equals("tokenize")
         && !type.equals("parse")
         && !type.equals("profile")) {

            throw new RuntimeException(
                "value of 'type' attribute in <validate> must be one " +
                "of 'debug', 'tokenize', 'parse', or 'profile'");
        }
        if (file == null && !type.equals("debug")) {
            throw new RuntimeException(
                "missing 'inputfile' attribute in <validate>");
        }
    }

    /**
     * Proceses the specified grammar.
     *
     * @param grammar        the grammar to process
     *
     * @throws RuntimeException if the grammar couldn't be processed
     *             correctly
     */
    public void process(Grammar grammar) throws RuntimeException {
        if (type.equals("debug")) {
            debug(grammar);
        } else if (type.equals("tokenize")) {
            tokenize(grammar);
        } else if (type.equals("parse")) {
            parse(grammar);
        } else if (type.equals("profile")) {
            profile(grammar);
        } else {
            throw new RuntimeException("unknown <validation> type: " + type);
        }
    }

    /**
     * Debugs a grammar by printing the internal representation.
     *
     * @param grammar        the grammar to use
     *
     * @throws RuntimeException if a parser couldn't be created
     */
    private void debug(Grammar grammar) throws RuntimeException {
        Tokenizer  tokenizer = null;
        Parser     parser = null;

        // Create tokenizer and parser
        try {
            tokenizer = grammar.createTokenizer(null);
            parser = grammar.createParser(tokenizer);
        } catch (GrammarException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        }

        // Print tokenizer and parser
        if (!quiet) {
            System.out.println("Contents of " + grammar.getFileName() + ":");
            System.out.println();
            System.out.println("Token Declarations:");
            System.out.println("-------------------");
            System.out.print(tokenizer);
            System.out.println("Production Declarations:");
            System.out.println("------------------------");
            System.out.print(parser);
        }
    }

    /**
     * Tokenizes the input file with the token patterns from the
     * grammar.
     *
     * @param grammar        the grammar to use
     *
     * @throws RuntimeException if the input file couldn't be tokenized
     *             correctly
     */
    private void tokenize(Grammar grammar) throws RuntimeException {
        Tokenizer  tokenizer;
        Token      token;

        try {
            tokenizer = grammar.createTokenizer(new FileReader(file));
            if (!quiet) {
                System.out.println("Tokens from " + file + ":");
            }
            while ((token = tokenizer.next()) != null) {
                if (!quiet) {
                    System.out.println(token);
                }
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e.getMessage());
        } catch (GrammarException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        } catch (ParseException e) {
            throw new RuntimeException("in file " + file + ": " +
                                       e.getMessage());
        }
    }

    /**
     * Parses the input file with the grammar.
     *
     * @param grammar        the grammar to use
     *
     * @throws RuntimeException if the input file couldn't be parsed
     *             correctly
     */
    private void parse(Grammar grammar) throws RuntimeException {
        Tokenizer  tokenizer;
        Analyzer   analyzer;
        Parser     parser;

        try {
            tokenizer = grammar.createTokenizer(new FileReader(file));
            if (quiet) {
                analyzer = null;
            } else {
                analyzer = new TreePrinter(System.out);
            }
            parser = grammar.createParser(tokenizer, analyzer);
            if (!quiet) {
                System.out.println("Parse tree from " + file + ":");
            }
            parser.parse();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e.getMessage());
        } catch (GrammarException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        } catch (ParserCreationException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        } catch (ParserLogException e) {
            throw new RuntimeException("in file " + file + ": " +
                                       e.getMessage());
        }
    }

    /**
     * Parses the input file with the grammar and prints profiling
     * information.
     *
     * @param grammar        the grammar to use
     *
     * @throws RuntimeException if the input file couldn't be profiled
     *             correctly
     */
    private void profile(Grammar grammar) throws RuntimeException {
        Tokenizer  tokenizer;
        Parser     parser;
        Node       node;
        long       time;
        int        counter;

        // Profile tokenizer
        try {
            tokenizer = grammar.createTokenizer(new FileReader(file));
            System.out.println("Tokenizing " + file);
            time = System.currentTimeMillis();
            counter = 0;
            while (tokenizer.next() != null) {
                counter++;
            }
            time = System.currentTimeMillis() - time;
            System.out.println("  Time elapsed:  " + time + " millisec");
            System.out.println("  Tokens found:  " + counter);
            System.out.println("  Average speed: " + (counter / time) +
                               " tokens/millisec");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e.getMessage());
        } catch (GrammarException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        } catch (ParseException e) {
            throw new RuntimeException("in file " + file + ": " +
                                       e.getMessage());
        }

        // Profile parser
        try {
            tokenizer = grammar.createTokenizer(new FileReader(file));
            parser = grammar.createParser(tokenizer);
            System.out.println("Parsing " + file);
            time = System.currentTimeMillis();
            node = parser.parse();
            time = System.currentTimeMillis() - time;
            counter = 1 + node.getDescendantCount();
            System.out.println("  Time elapsed:  " + time + " millisec");
            System.out.println("  Nodes found:   " + counter);
            System.out.println("  Average speed: " + (counter / time) +
                               " nodes/millisec");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e.getMessage());
        } catch (GrammarException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        } catch (ParserCreationException e) {
            throw new RuntimeException("in grammar " + grammar.getFileName() +
                                       ": " + e.getMessage());
        } catch (ParserLogException e) {
            throw new RuntimeException("in file " + file + ": " +
                                       e.getMessage());
        }
    }
}
