/*
 * ParserCreationException.cs
 *
 * 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.
 */

using System;
using System.Collections;
using System.Text;

namespace PerCederberg.Grammatica.Runtime {

    /**
     * A parser creation exception. This exception is used for signalling
     * an error in the token or production patterns, making it impossible
     * to create a working parser or tokenizer.
     *
     * @author   Per Cederberg
     * @version  1.5
     */
    public class ParserCreationException : Exception {

        /**
         * The error type enumeration.
         */
        public enum ErrorType {

            /**
             * The internal error type is only used to signal an
             * error that is a result of a bug in the parser or
             * tokenizer code.
             */
            INTERNAL,

            /**
             * The invalid parser error type is used when the parser
             * as such is invalid. This error is typically caused by
             * using a parser without any patterns.
             */
            INVALID_PARSER,

            /**
             * The invalid token error type is used when a token
             * pattern is erroneous. This error is typically caused
             * by an invalid pattern type or an erroneous regular
             * expression.
             */
            INVALID_TOKEN,

            /**
             * The invalid production error type is used when a
             * production pattern is erroneous. This error is
             * typically caused by referencing undeclared productions,
             * or violating some other production pattern constraint.
             */
            INVALID_PRODUCTION,

            /**
             * The infinite loop error type is used when an infinite
             * loop has been detected in the grammar. One of the
             * productions in the loop will be reported.
             */
            INFINITE_LOOP,

            /**
             * The inherent ambiguity error type is used when the set
             * of production patterns (i.e. the grammar) contains
             * ambiguities that cannot be resolved.
             */
            INHERENT_AMBIGUITY
        }

        /**
         * The error type.
         */
        private ErrorType type;

        /**
         * The token or production pattern name. This variable is only
         * set for some error types.
         */
        private string name;

        /**
         * The additional error information string. This variable is only
         * set for some error types.
         */
        private string info;

        /**
         * The error details list. This variable is only set for some
         * error types.
         */
        private ArrayList details;

        /**
         * Creates a new parser creation exception.
         *
         * @param type           the parse error type
         * @param info           the additional error information
         */
        public ParserCreationException(ErrorType type,
                                       String info)
            : this(type, null, info) {
        }

        /**
         * Creates a new parser creation exception.
         *
         * @param type           the parse error type
         * @param name           the token or production pattern name
         * @param info           the additional error information
         */
        public ParserCreationException(ErrorType type,
                                       String name,
                                       String info)
            : this(type, name, info, null) {
        }

        /**
         * Creates a new parser creation exception.
         *
         * @param type           the parse error type
         * @param name           the token or production pattern name
         * @param info           the additional error information
         * @param details        the error details list
         */
        public ParserCreationException(ErrorType type,
                                       String name,
                                       String info,
                                       ArrayList details) {

            this.type = type;
            this.name = name;
            this.info = info;
            this.details = details;
        }

        /**
         * The error type property (read-only).
         *
         * @since 1.5
         */
        public ErrorType Type {
            get {
                return type;
            }
        }

        /**
         * Returns the error type.
         *
         * @return the error type
         *
         * @see #Type
         *
         * @deprecated Use the Type property instead.
         */
        public ErrorType GetErrorType() {
            return Type;
        }

        /**
         * The token or production name property (read-only).
         *
         * @since 1.5
         */
        public string Name {
            get {
                return name;
            }
        }

        /**
         * Returns the token or production name.
         *
         * @return the token or production name
         *
         * @see #Name
         *
         * @deprecated Use the Name property instead.
         */
        public string GetName() {
            return Name;
        }

        /**
         * The additional error information property (read-only).
         *
         * @since 1.5
         */
        public string Info {
            get {
                return info;
            }
        }

        /**
         * Returns the additional error information.
         *
         * @return the additional error information
         *
         * @see #Info
         *
         * @deprecated Use the Info property instead.
         */
        public string GetInfo() {
            return Info;
        }

        /**
         * The detailed error information property (read-only).
         *
         * @since 1.5
         */
        public string Details {
            get {
                StringBuilder  buffer = new StringBuilder();

                if (details == null) {
                    return null;
                }
                for (int i = 0; i < details.Count; i++) {
                    if (i > 0) {
                        buffer.Append(", ");
                        if (i + 1 == details.Count) {
                            buffer.Append("and ");
                        }
                    }
                    buffer.Append(details[i]);
                }

                return buffer.ToString();
            }
        }

        /**
         * Returns the detailed error information as a string
         *
         * @return the detailed error information
         *
         * @see #Details
         *
         * @deprecated Use the Details property instead.
         */
        public string GetDetails() {
            return Details;
        }

        /**
         * The message property (read-only). This property contains
         * the detailed exception error message.
         */
        public override string Message {
            get{
                StringBuilder  buffer = new StringBuilder();

                switch (type) {
                case ErrorType.INVALID_PARSER:
                    buffer.Append("parser is invalid, as ");
                    buffer.Append(info);
                    break;
                case ErrorType.INVALID_TOKEN:
                    buffer.Append("token '");
                    buffer.Append(name);
                    buffer.Append("' is invalid, as ");
                    buffer.Append(info);
                    break;
                case ErrorType.INVALID_PRODUCTION:
                    buffer.Append("production '");
                    buffer.Append(name);
                    buffer.Append("' is invalid, as ");
                    buffer.Append(info);
                    break;
                case ErrorType.INFINITE_LOOP:
                    buffer.Append("infinite loop found in production pattern '");
                    buffer.Append(name);
                    buffer.Append("'");
                    break;
                case ErrorType.INHERENT_AMBIGUITY:
                    buffer.Append("inherent ambiguity in production '");
                    buffer.Append(name);
                    buffer.Append("'");
                    if (info != null) {
                        buffer.Append(" ");
                        buffer.Append(info);
                    }
                    if (details != null) {
                        buffer.Append(" starting with ");
                        if (details.Count > 1) {
                            buffer.Append("tokens ");
                        } else {
                            buffer.Append("token ");
                        }
                        buffer.Append(Details);
                    }
                    break;
                default:
                    buffer.Append("internal error");
                    break;
                }
                return buffer.ToString();
            }
        }

        /**
         * Returns the error message. This message will contain all the
         * information available.
         *
         * @return the error message
         *
         * @see #Message
         *
         * @deprecated Use the Message property instead.
         */
        public string GetMessage() {
            return Message;
        }
    }
}
