/* *******************************************************************************
 *   Kenya                                                                       *
 *   Copyright (C) 2004 Tristan Allwood,                                         *
 *                 2004 Matthew Sackman                                          *
 *                                                                               *
 *   This program 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 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               *
 *   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. *
 *                                                                               *
 *   The authors can be contacted by email at toa02@doc.ic.ac.uk                 *
 *                                             ms02@doc.ic.ac.uk                 *
 *                                                                               *
 *********************************************************************************/

package uk.ac.imperial.doc.kenya.errors;

import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import uk.ac.imperial.doc.kenya.minijava.lexer.LexerException;
import uk.ac.imperial.doc.kenya.minijava.parser.ParserException;
import uk.ac.imperial.doc.kenya.sourceCodeInformation.interfaces.ISourceCodeError;
import uk.ac.imperial.doc.kenya.sourceCodeInformation.interfaces.ISourceCodeLocation;
import uk.ac.imperial.doc.kenya.sourceCodeInformation.util.SourceCodeLocation;


/**
 * The new and improved SourceCodeException class. DO NOT DELETE THE LINES
 * MARKER BEGIN INJECT and MARKER END INJECT They
 * are referenced by the script. [with the // immediately before the marker's]
 * See the SourceCodeException target in build.xml
 * @author toa02
 *  
 */
public class SourceCodeException extends RuntimeException implements
        ISourceCodeError {

    private static final long serialVersionUID = 3449404;

    public static final String NEWLINE = System.getProperty("line.separator");
    
    public static final int JAVAC_MESSAGE = 0;

    public static final int HUMAN_MESSAGE = 1;

    public static final int LINE_NUM = 0;

    public static final int CHAR_NUM = 1;

    public static final int TOKEN_LEN = 2;

    /**
     * Wraps up a ParserException into a SourceCodeException.
     * 
     * @param pe
     * @return
     */
    public static SourceCodeException wrapParserException(ParserException pe) {
        int ln = pe.getToken().getLine();
        int pos = pe.getToken().getPos();

        Pattern p = Pattern.compile("\\[(\\d+),(\\d+)\\]");

        Matcher m = p.matcher(pe.getMessage());
        if (m.find()) {
            ln = Integer.parseInt(m.group(1));
            pos = Integer.parseInt(m.group(2));
        } else {
            throw new KenyaInternalError("Didn't match!" + pe.getMessage());
        }

        int len = pe.getToken().getText().trim().length();
        if (len == 0) {
            len = 1;
        }
        String entire = m.group(0);

        String mess = pe.getMessage().substring(entire.length());

        // This is horribly tied to the grammar, but what can I do?
        mess = mess.replaceAll(":", " one of:");
        mess = mess.replaceAll("'", "");
        mess = mess.replaceAll(" and", " &&");
        mess = mess.replaceAll(" or", " ||");
        mess = mess.replaceAll(" xor", " ^");
        mess = mess.replaceAll(" not", " !");
        mess = mess.replaceAll(" identifier", " <identifier>");
        mess = mess.replaceAll(" stringliteral", " <string>");
        mess = mess.replaceAll(" charliteral", " <character>");
        mess = mess.replaceAll(" bracket pair", " [ ]");
        mess = mess.replaceAll(" intnumber", " <integer>");
        mess = mess.replaceAll(" dpnumber", " <double>");

        String[] mess2 = pe.getToken().getText().trim().length() > 0 ? new String[] {
                mess,
                "You have invalid syntax with this text: "
                        + pe.getToken().getText().trim() }
                : new String[] { mess, "You have invalid syntax" };
        return new SourceCodeException(mess2, new int[] { ln, pos, len }, null,
                -1);
    }

    /**
     * Wraps up a LexerException itno a SourceCodeException.
     * 
     * @param le
     * @return
     */
    public static SourceCodeException wrapLexerException(LexerException le) {
        int ln;
        int pos;
        Pattern p = Pattern.compile("\\[(\\d+),(\\d+)\\]");

        Matcher m = p.matcher(le.getMessage());
        if (m.find()) {
            ln = Integer.parseInt(m.group(1));
            pos = Integer.parseInt(m.group(2));
        } else {
            throw new KenyaInternalError("m didn't match! " + le.getMessage());
        }

        String entire = m.group(0);

        String mess = le.getMessage().substring(entire.length());
        String[] mess2 = { mess, "You have invalid syntax" };
        return new SourceCodeException(mess2, new int[] { ln, pos, 1 }, null,
                -1);
    }

    public static SourceCodeException noValidMainFunction() {

        String[] mess = {
                "Exception in thread \"main\" java.lang.NoSuchMethodError: main",
                "You need either a method void main(); or void main(String[] args)" };

        return new SourceCodeException(mess, new int[] { 0, 0, 1 }, null, -2);
    }

    public static SourceCodeException badClassName(ISourceCodeLocation pos,
            String name) {

        String[] mess = {
                "<none>",
                "The class name "
                        + name
                        + " is so commonly used in java that using it here will create a lot of confusion" };

        return new SourceCodeException(mess, new int[] { pos.getLineNumber(),
                pos.getColumnNumber(), pos.getTokenLength() }, null, -3);
    }

    public static SourceCodeException badClassBuiltinName(
            ISourceCodeLocation pos, String name) {

        String[] mess = {
                "<none>",
                "The class name "
                        + name
                        + " is is a reserved class name from one of the built-in methods you are using" };

        return new SourceCodeException(mess, new int[] { pos.getLineNumber(),
                pos.getColumnNumber(), pos.getTokenLength() }, null, -4);
    }

    public static SourceCodeException badEnumName(ISourceCodeLocation pos,
            String name) {

        String[] mess = {
                "<none>",
                "The enum name "
                        + name
                        + " is so commonly used in java that using it here will create a lot of confusion" };

        return new SourceCodeException(mess, new int[] { pos.getLineNumber(),
                pos.getColumnNumber(), pos.getTokenLength() }, null, -5);
    }

    public static SourceCodeException badEnumBuiltinName(
            ISourceCodeLocation pos, String name) {

        String[] mess = {
                "<none>",
                "The enum name "
                        + name
                        + " is a reserved class name from one of the built-in methods you are using" };

        return new SourceCodeException(mess, new int[] { pos.getLineNumber(),
                pos.getColumnNumber(), pos.getTokenLength() }, null, -6);
    }

    //MARKER BEGIN INJECT

    //MARKER END INJECT

    private int[] _errPos;

    private int[][] _linkPos;

    private String[] _messages;

    private final StringBuffer humanBuffer;

    private final StringBuffer javacBuffer;

    int _errID;

    private final Pattern linePattern = Pattern.compile("^(.+)[\n\r]*$",
            Pattern.MULTILINE);

    /**
     * Constructs a new SourceCodeException with the given message, at the
     * specified error Location, and with any additional "linked" lines which
     * may be relevent.
     * 
     * @param messages
     *            String[2] array of the messages. messages[JAVAC_MESSAGE] and
     *            messages[HUMAN_MESSAGE] are what u'd expect.
     * @param humanMessage
     *            String of the human Style message.
     * @param errPos
     *            The co-ordinates of the error ( errPos[LINE_NUM] is the line
     *            number, errPos[CHAR_NUM] is the character position ).
     * @param linkPos
     *            An array of co-ordinates of places that may be relevent
     *            linkPos[x][LINE_NUM] and linkPos[x][CHAR_NUM] as above.
     */
    public SourceCodeException(String[] messages, int[] errPos, int[][] linkPos,
            int errID) {
        super();

        _messages = messages;
        _errPos = errPos;
        _linkPos = linkPos;
        _errID = errID;

        StringWriter sWriter = new StringWriter();
        PrintWriter pwriter = new PrintWriter(sWriter);
        Matcher m = linePattern.matcher(_messages[HUMAN_MESSAGE]);
        boolean first = true;
        while (m.find()) {
            if (first)
                first = false;
            else
                pwriter.println();
            pwriter.print(m.group());
        }
        humanBuffer = sWriter.getBuffer();

        sWriter = new StringWriter();
        pwriter = new PrintWriter(sWriter);
        m = linePattern.matcher(_messages[JAVAC_MESSAGE]);
        first = true;
        while (m.find()) {
            if (first)
                first = false;
            else
                pwriter.println();
            pwriter.print(m.group(1));
        }
        javacBuffer = sWriter.getBuffer();
    }

    /**
     * Overriden for the custom message. The message given is in the form:
     * <code>
     * [lineNo,charNo]
     * ( [lineNo,charNo] [lineNo,charNo] ... )
     * Javac Style error message
     * Human Style error message
     * { errID }
     * </code>
     * 
     * @see java.lang.Throwable#getMessage()
     */
    public String getMessage() {
        StringBuffer sb = new StringBuffer();

        sb.append(getErrorLocation());
        sb.append(getLinkLocations());
        sb.append(NEWLINE);
        sb.append(javacBuffer);
        sb.append(NEWLINE);
        sb.append(humanBuffer);
        sb.append(NEWLINE);
        sb.append("{ ");
        sb.append(_errID);
        sb.append(" }");
        return sb.toString();
    }

    /**
     * @return String javac style error message for this error.
     */
    public StringReader getJavacStyleMessage() {
        return new StringReader(javacBuffer.toString());
    }

    /**
     * @return String human style error message for this error.
     */
    public StringReader getHumanStyleMessage() {
        return new StringReader(humanBuffer.toString());

    }

    /**
     * @return String representing where this error is in the format
     *         [lineNo,charNo]
     */
    public String getErrorLocation() {
        StringBuffer sb = new StringBuffer();

        sb.append("[");
        sb.append(_errPos[LINE_NUM]);
        sb.append(",");
        sb.append(_errPos[CHAR_NUM]);
        sb.append("]");

        return sb.toString();
    }

    /**
     * @return A string representing the "linked errors" to this error in the
     *         format: "" if there are none. ( [lineNo,charNo] [lineNo,charNo]
     *         ... )
     */
    public String getLinkLocations() {
        if (_linkPos == null || _linkPos.length == 0) {
            return "";
        }

        StringBuffer sb = new StringBuffer();
        sb.append("(");
        for (int i = 0; i < _linkPos.length; i++) {
            sb.append(" ");
            sb.append("[");
            sb.append(_linkPos[i][LINE_NUM]);
            sb.append(",");
            sb.append(_linkPos[i][CHAR_NUM]);
            sb.append("]");
        }
        sb.append(" )");

        return sb.toString();
    }

    /**
     * @see uk.ac.imperial.doc.kenya.sourceCodeInformation.interfaces.ISourceCodeInformation#getLocation()
     */
    public ISourceCodeLocation getLocation() {
        return new SourceCodeLocation(_errPos[LINE_NUM], _errPos[CHAR_NUM],
                _errPos[TOKEN_LEN]);
    }

    /**
     * @see uk.ac.imperial.doc.kenya.sourceCodeInformation.interfaces.ISourceCodeInformation#getLinkedInformation()
     */
    public ISourceCodeLocation[] getLinkedInformation() {

        if (_linkPos == null) {
            return new ISourceCodeLocation[0];
        }

        ISourceCodeLocation[] out = new ISourceCodeLocation[_linkPos.length];
        for (int i = 0; i < _linkPos.length; i++) {
            out[i] = new SourceCodeLocation(_linkPos[i][LINE_NUM],
                    _linkPos[i][CHAR_NUM], _linkPos[i][TOKEN_LEN]);
        }

        return out;
    }

    /**
     * @see uk.ac.imperial.doc.kenya.sourceCodeInformation.interfaces.ISourceCodeInformation#getErrorNumber()
     */
    public int getInformationNumber() {
        return _errID;
    }

    public int getPriority() {
        return 0;
    }

}