/*
Copyright (C) 2000-2010  Ministere de la culture et de la communication (France), AJLSM
See LICENCE file
*/
package fr.gouv.culture.sdx.exception;

import fr.gouv.culture.sdx.framework.Framework;
import fr.gouv.culture.sdx.utils.Utilities;
import fr.gouv.culture.sdx.utils.constants.Node;
import fr.gouv.culture.sdx.utils.logging.LoggingUtils;
import org.apache.avalon.framework.CascadingException;
import org.apache.avalon.framework.logger.Logger;
import org.apache.excalibur.xml.sax.XMLizable;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * The superclass of all exceptions thrown by the SDX Framework and the applications contained within.
 */
public class SDXException extends CascadingException implements XMLizable, SDXExceptionCode {

    /** The current locale (system's locale by default). */
    protected Locale locale = Locale.getDefault();

    /** The base name for the property file. */
    private final static String propertiesBaseName = "fr.gouv.culture.sdx.exception.errors";

    /** The error code. */
    private int errorCode = -1;

    /** The error key for the properties files. */
    private String errorKey;
    
    private String originalMessage = null;

    /** The error arguments. */
    private String[] arguments;

    /** The super.getLog() to use. */
    private Logger logger = null;

    /** The original exception. */
    private Throwable originalError = null;

    /**Our default locale for resource loading if the error_locale.properties file doesn't exist for the current system locale*/
    private Locale defaultLocale = Locale.ENGLISH;

    /**
     * Creates an SDX exception with an single message
     *
     * @param s	The message
     */
    public SDXException(String s) {
        super(s);
        this.originalMessage = s;
    }

    /**
     * Creates an SDX exception with a single and the original exception
     *
     * @param s	The error code (must be defined in class SDXExceptionCode).
     * @param e The original exception thrown (may be null).
     */
    public SDXException(String s, Throwable throwable) {
        super(s, throwable);
        this.originalError = super.getCause();
        this.originalMessage = s;
    }
    
    /**
     * Creates an SDX exception with an error code and arguments
     *
     * @param code      The error code (must be defined in class SDXExceptionCode).
     * @param args      The message arguments (may be null or empty).
     */
    public SDXException(int code, String[] args) {
        super("");
        this.errorCode = code;
        this.errorKey = "error." + this.errorCode;
    	if (args != null)
            this.arguments = args;
        else
            this.arguments = new String[0];
        // we dont want any nulls sent to the message formatting methods
        for (int i = 0; i < this.arguments.length; i++) {
            if (this.arguments[i] == null)
                this.arguments[i] = "missing exception detail";
        }
    }
    
    /**
     * Creates an SDX exception with all parameters.
     * The exception is logged as an error.
     *
     * @param l         The super.getLog() to use (may be null).
     * @param code      The error code (must be defined in class SDXExceptionCode).
     * @param args      The message arguments (may be null or empty).
     * @param e         The original exception thrown (may be null).
     */
    public SDXException(Logger l, int code, String[] args, Throwable e) {
        super("", e);
        this.logger = l;
        this.errorCode = code;
        this.errorKey = "error." + this.errorCode;
        this.originalError = super.getCause();
        if (args != null)
            this.arguments = args;
        else
            this.arguments = new String[0];
        //we dont want any nulls sent to the message formatting methods
        for (int i = 0; i < this.arguments.length; i++) {
            if (this.arguments[i] == null)
                this.arguments[i] = "missing exception detail";
        }
        log();
    }

    /** Logs an error message. */
    private void log() {
        String message = getMessage();
        if (!Utilities.checkString(message))
            message = "noMessageProvided";

        if (originalError != null && originalError.fillInStackTrace() != null)
            LoggingUtils.logError(logger, message, originalError.fillInStackTrace());
        else
            LoggingUtils.logError(logger, message, this);
        //a null super.getLog() allows  us to wrap our exceptions within other types of exceptions without double logging (example an Avalon ConfigurationException)
    }

    /** Returns an XML description of this error.TODOJavadoc:once we have a schema for the structure of this representation we should link to it here */
    public void toSAX(ContentHandler hdl) throws SAXException {
        toSAX(hdl, this.locale);
    }

    private void toSAX(ContentHandler hdl, Locale locale) throws SAXException {
        // TODOImplement: Implement multilinguism.
        String sdxNsUri = Framework.SDXNamespaceURI;
        String sdxNsPrefix = Framework.SDXNamespacePrefix;

        //Creation of local variables which are later passed into startElement() and endElement() methods-rbp14/03/02
        String localName = Node.Name.EXCEPTION;
        String qName = sdxNsPrefix + ":" + localName;
        AttributesImpl atts = new AttributesImpl();
        String lang = locale.getLanguage();
        if (!"".equals(lang) && !"".equals(locale.getCountry())) lang += "-" + locale.getCountry();
        atts.addAttribute(Framework.XMLNamespaceURI, Node.Name.LANG, Node.Name.XML_LANG, Node.Type.CDATA, lang);
        atts.addAttribute("", Node.Name.CODE, Node.Name.CODE, Node.Type.CDATA, Integer.toString(this.errorCode));

        //startElement() method is called for "exception" and local variables are passed-rbp14/03/02
        hdl.startElement(sdxNsUri, localName, qName, atts);


        String childTwoLocalName = Node.Name.MESSAGE;
        String childTwoQName = sdxNsPrefix + ":" + childTwoLocalName;
        AttributesImpl childTwoAtts = new AttributesImpl();

        //startElement() method is called for "message" and local variables are passed-rbp14/03/02
        hdl.startElement(sdxNsUri, childTwoLocalName, childTwoQName, childTwoAtts);

        //Creation of an characterArray to input pass into characters() method-rbp14/03/02
        char[] messArr = this.getMessage(locale).toCharArray();
        hdl.characters(messArr, 0, messArr.length);

        //endElement() method is called for "message" and local variables are passed-rbp14/03/02
        hdl.endElement(sdxNsUri, childTwoLocalName, childTwoQName);

        if (originalError != null) {
            String childThreeLocalName = Node.Name.ORIGINAL_EXCEPTION;
            String childThreeQName = sdxNsPrefix + ":" + childThreeLocalName;
            AttributesImpl childThreeAtts = new AttributesImpl();



            //startElement() method is called for "originalError" and local variables are passed-rbp14/03/02
            hdl.startElement(sdxNsUri, childThreeLocalName, childThreeQName, childThreeAtts);

            try {
                //in java1.4 the  below code will not be necessary
                ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                PrintStream ps = new PrintStream(byteOut);
                originalError.printStackTrace(ps);
//              char[] origExceptArr = (originalError.getMessage()).toCharArray();
                //Creation of an characterArray to input pass into characters() method-rbp14/03/02
                byteOut.flush();
                char[] origExceptArr = (byteOut.toString()).toCharArray();
                byteOut.close();
                hdl.characters(origExceptArr, 0, origExceptArr.length);
            } catch (IOException e) {
                throw new SAXException(e.getMessage(), e);
            } finally {
                //endElement() method is called for "originalError" and local variables are passed-rbp14/03/02
                hdl.endElement(sdxNsUri, childThreeLocalName, childThreeQName);
            }

        }
        //endElement() method is called for "exception" and local variables are passed-rbp14/03/02
        hdl.endElement(sdxNsUri, localName, qName);

    }

    /**
     * Returns an error message.
     *
     * <p>
     * The error message is in the default locale of the Java virtual machine, if available, if not
     * in the SDX default (english)
     * <p>TODONOW:currently not true, the default locale (english) still needs to be implemented-rbp
     */
    public String getMessage() {
        return getMessage(locale);
    }

    /**
     * Returns the error message for a given locale.
     *
     * @param   l      The locale.
     */
    public String getMessage(Locale l) {
    	if(Utilities.checkString(this.originalMessage) && !Utilities.checkString(this.errorKey))
    		return this.originalMessage;	// pas de code d'erreur mais un message original, on le sert
        if (l != null) locale = l;
        ResourceBundle rb = null;
        try {
            rb = ResourceBundle.getBundle(propertiesBaseName, locale);
        } catch (MissingResourceException e) {
            rb = ResourceBundle.getBundle(propertiesBaseName, defaultLocale);
        }
        return MessageFormat.format(rb.getString(errorKey), arguments);
    }


}
