/*
 * ParseException.cs
 *
 * This work is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This work 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 GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * As a special exception, the copyright holders of this library give
 * you permission to link this library with independent modules to
 * produce an executable, regardless of the license terms of these
 * independent modules, and to copy and distribute the resulting
 * executable under terms of your choice, provided that you also meet,
 * for each linked independent module, the terms and conditions of the
 * license of that module. An independent module is a module which is
 * not derived from or based on this library. If you modify this
 * library, you may extend this exception to your version of the
 * library, but you are not obligated to do so. If you do not wish to
 * do so, delete this exception statement from your version.
 *
 * Copyright (c) 2003 Per Cederberg. All rights reserved.
 */

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

namespace PerCederberg.Grammatica.Parser {

    /**
     * A parse exception.
     *
     * @author   Per Cederberg, <per at percederberg dot net>
     * @version  1.1
     */
    public class ParseException : 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 I/O error type is used for stream I/O errors. 
             */
            IO,
    
            /*
             * The unexpected end of file error type is used when end
             * of file is encountered instead of a valid token.
             */
            UNEXPECTED_EOF,

            /*
             * The unexpected character error type is used when a
             * character is read that isn't handled by one of the
             * token patterns.
             */
            UNEXPECTED_CHAR,

            /*
             * The unexpected token error type is used when another
             * token than the expected one is encountered.
             */
            UNEXPECTED_TOKEN,
    
            /*
             * The invalid token error type is used when a token
             * pattern with an error message is matched. The
             * additional information provided should contain the
             * error message.
             */
            INVALID_TOKEN,

            /*
             * The analysis error type is used when an error is
             * encountered in the analysis. The additional information
             * provided should contain the error message.
             */
            ANALYSIS
        }

        /**
         * The error type.
         */
        private ErrorType type;
    
        /**
         * The additional information string.
         */
        private string info;

        /**
         * The additional details information. This variable is only
         * used for unexpected token errors.
         */
        private ArrayList details;

        /**
         * The line number.
         */
        private int line;
    
        /**
         * The column number.
         */
        private int column;

        /**
         * Creates a new parse exception.
         * 
         * @param type           the parse error type
         * @param info           the additional information
         * @param line           the line number, or -1 for unknown
         * @param column         the column number, or -1 for unknown
         */
        public ParseException(ErrorType type, 
                              string info, 
                              int line, 
                              int column) 
            : this(type, info, null, line, column) {
        }

        /**
         * Creates a new parse exception. This constructor is only
         * used to supply the detailed information array, which is
         * only used for expected token errors. The list then contains
         * descriptions of the expected tokens.
         * 
         * @param type           the parse error type
         * @param info           the additional information
         * @param details        the additional detailed information
         * @param line           the line number, or -1 for unknown
         * @param column         the column number, or -1 for unknown
         */
        public ParseException(ErrorType type, 
                              string info,
                              ArrayList details,
                              int line, 
                              int column) {

            this.type = type;
            this.info = info;
            this.details = details;
            this.line = line;
            this.column = column;
        }

        /**
         * The message property. This property contains the detailed
         * exception error message.
         */
        public override string Message {
            get{
                return GetMessage(); 
            }   
        }

        /**
         * Returns the error type. 
         * 
         * @return the error type
         */
        public ErrorType GetErrorType() {
            return type;
        }

        /**
         * Returns the additional error information.
         * 
         * @return the additional error information
         */
        public string GetInfo() {
            return info;
        }

        /**
         * Returns the additional detailed error information. 
         * 
         * @return the additional detailed error information
         */
        public ArrayList GetDetails() {
            return new ArrayList(details);
        }

        /**
         * Returns the line number where the error occured.
         * 
         * @return the line number of the error, or 
         *         -1 if unknown
         */
        public int GetLine() {
            return line;
        }
    
        /**
         * Returns the column number where the error occured.
         * 
         * @return the column number of the error, or 
         *         -1 if unknown
         */
        public int GetColumn() {
            return column;
        }
    
        /**
         * Returns a default error message.
         * 
         * @return a default error message
         */
        public string GetMessage() {
            StringBuilder  buffer = new StringBuilder();

            // Add error description
            buffer.Append(GetErrorMessage());

            // Add line and column
            if (line > 0 && column > 0) {
                buffer.Append(", on line: ");
                buffer.Append(line);
                buffer.Append(" column: ");
                buffer.Append(column);
            }

            return buffer.ToString();
        }

        /**
         * Returns the error message. This message will contain all the 
         * information available, except for the line and column number 
         * information.
         * 
         * @return the error message
         */
        public string GetErrorMessage() {
            StringBuilder  buffer = new StringBuilder();
    
            // Add type and info
            switch (type) {
            case ErrorType.IO:
                buffer.Append("I/O error: ");
                buffer.Append(info);
                break;
            case ErrorType.UNEXPECTED_EOF:
                buffer.Append("unexpected end of file");
                break;
            case ErrorType.UNEXPECTED_CHAR:
                buffer.Append("unexpected character '");
                buffer.Append(info);
                buffer.Append("'");
                break;
            case ErrorType.UNEXPECTED_TOKEN:
                buffer.Append("unexpected token ");
                buffer.Append(info);
                if (details != null) {
                    buffer.Append(", expected ");
                    if (details.Count > 1) {
                        buffer.Append("one of ");
                    }
                    buffer.Append(GetMessageDetails());
                }
                break;
            case ErrorType.INVALID_TOKEN:
                buffer.Append(info);
                break;
            case ErrorType.ANALYSIS:
                buffer.Append(info);
                break;
            default:
                buffer.Append("internal error");
                if (info != null) {
                    buffer.Append(": ");
                    buffer.Append(info);
                }
                break;
            }

            return buffer.ToString();
        }
    
        /**
         * Returns a string containing all the detailed information in
         * a list. The elements are separated with a comma.
         * 
         * @return the detailed information string
         */
        private string GetMessageDetails() {
            StringBuilder  buffer = new StringBuilder();
        
            for (int i = 0; i < details.Count; i++) {
                if (i > 0) {
                    buffer.Append(", ");
                    if (i + 1 == details.Count) {
                        buffer.Append("or ");
                    }
                }
                buffer.Append(details[i]);
            }

            return buffer.ToString();
        }
    }
}