/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 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 Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor.transformer.xslt;

import org.orbeon.errorified.Exceptions;
import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.xml.dom4j.ExtendedLocationData;
import org.orbeon.oxf.xml.dom4j.LocationData;
import org.xml.sax.SAXException;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMLocator;
import java.util.ArrayList;
import java.util.List;

public class StringErrorListener implements ErrorListener {

    private Logger logger;
    private boolean hasErrors;
    private List<LocationData> errorLocationData;
    private StringBuilder messages = new StringBuilder();

    public StringErrorListener(Logger logger) {
        this.logger = logger;
    }

    public void warning(TransformerException exception)
            throws TransformerException {

        String message = "Warning: ";
        if (exception.getLocator() != null)
            message += getLocationMessage(exception) + "\n  ";
        message += getExpandedMessage(exception);

        logger.warn(message);

        if (messages.length() > 0)
            messages.append("\n");
        messages.append(message);
    }

    public void error(TransformerException exception) throws TransformerException {
        addError(exception);

        final String locationMessage = getLocationMessage(exception);
        final String message = "Error"
                + (locationMessage.length() > 0 ? " " + locationMessage + ":\n" : ": ")
                + getExpandedMessage(exception);

        logger.error(message);

        if (messages.length() > 0)
            messages.append("\n");
        messages.append(message);
    }

    /**
     * Receive notification of a non-recoverable error.
     *
     * The application must assume that the transformation cannot continue after the Transformer has invoked this
     * method, and should continue (if at all) only to collect addition error messages. In fact, Transformers are free
     * to stop reporting events once this method has been invoked.
     *
     * @param exception The error information encapsulated in a transformer exception.
     * @throws javax.xml.transform.TransformerException if the application chooses to discontinue the transformation.
     * @see javax.xml.transform.TransformerException
     */

    public void fatalError(TransformerException exception) throws TransformerException {
        error(exception);
        throw exception;
    }

    private void addError(TransformerException exception) {
        hasErrors = true;

        if (errorLocationData == null)
            errorLocationData = new ArrayList<LocationData>();

        final LocationData locationData = getTransformerExceptionLocationData(exception, null);
        if (locationData != null)
            errorLocationData.add(locationData);
    }

    public boolean hasErrors() {
        return hasErrors;
    }

    public String getMessages() {
        return messages.toString();
    }

    public List<LocationData> getErrors() {
        return errorLocationData;
    }

    /**
     * Try to get the best possible location data for a TransformerException.
     *
     * @param transformerException  TransformerException to process
     * @param defaultSystemId       System Id to use if none is found in the TransformerException.
     * @return ExtendedLocationData
     */
    public static ExtendedLocationData getTransformerExceptionLocationData(TransformerException transformerException, String defaultSystemId) {
        final SourceLocator locator = transformerException.getLocator();
        if (locator == null && defaultSystemId == null) {
            return null;
        } else if (locator == null) {
            return new ExtendedLocationData(defaultSystemId, -1, -1, null);
        } else {
            String description;
            if (locator instanceof DOMLocator) {
                description = ((DOMLocator) locator).getOriginatingNode().getNodeName();
            } else if (locator.getClass().getName().equals("net.sf.saxon.instruct.InstructionDetails")
                    || locator.getClass().getName().equals("org.orbeon.saxon.instruct.InstructionDetails")) {
                try {
                    description = locator.getClass().getMethod("getInstructionName", new Class[]{}).invoke(locator).toString();
                } catch (Exception e) {
                    // Let's not consider this a real issue, just clear the description
                    description = null;
                }
            } else {
                description = null;
            }
            return new ExtendedLocationData((locator.getSystemId() != null) ? locator.getSystemId() : defaultSystemId, locator.getLineNumber(), locator.getColumnNumber(), description);
        }
    }

    /**
     * Get a string identifying the location of an error.
     */
    private static String getLocationMessage(TransformerException te) {
        final ExtendedLocationData extendedLocationData = getTransformerExceptionLocationData(te, null);
        if (extendedLocationData != null) {
            return "at " + extendedLocationData.toString();
        } else  if (te.getException() instanceof OXFException) {
            final Throwable t = Exceptions.getRootThrowable(te.getException());
            // TODO: check this, should maybe look for root validation data?
            if (t instanceof ValidationException)
                return ((ValidationException) t).firstLocationData().toString();
            else
                return ((OXFException) te.getException()).getMessage();
        } else {
            return "";
        }
    }

    /**
     * Get a string containing the message for this exception and all contained exceptions
     */
    private static String getExpandedMessage(TransformerException err) {
        String message = "";
        Throwable e = err;
        while (true) {
            if (e == null) {
                break;
            }
            String next = e.getMessage();
            if (next == null) next = "";
            if (!next.equals("TRaX Transform Exception") && !message.endsWith(next)) {
                if (!message.equals("")) {
                    message += ": ";
                }
                message += Exceptions.getRootThrowable(e).getMessage();
            }
            if (e instanceof TransformerException) {
                e = ((TransformerException) e).getException();
            } else if (e instanceof SAXException) {
                e = ((SAXException) e).getException();
            } else {
                break;
            }
        }

        return message;
    }
}
