package com.fp.common.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.sql.BatchUpdateException;
import java.sql.SQLException;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.persistence.PersistenceException;

import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.GenericJDBCException;

import com.fp.common.logger.APPLogger;

/**
 * Clase encargada del manejo de excepciones
 * 
 * @author Jorge Vaca
 * @version 2.1
 */
public class ExceptionHandler {

    /**
     * Referencia a la excepcion presentanda.
     */
    private final Throwable exception;

    /**
     * Referencia a la causa de la excepcion.
     */
    private Throwable cause;

    /**
     * Codigo de lenguage
     */
    private String language = "es";

    /**
     * Indica que es un mensaje aplicativo.
     */
    private boolean appexception = false;

    /**
     * Crea una Instancia de ExceptionHandler
     * 
     * @param pException Excepcion por Manejar
     */
    public ExceptionHandler(Throwable pException, String pLanguage) {
        APPLogger.getLogger().warn(pException, pException);
        this.exception = pException;
        this.cause = this.exception.getCause();
        if (pException instanceof APPException) {
            this.appexception = true;
        }
        while ((this.cause != null) && (this.cause.getCause() != null)) {
            if ((pException instanceof InvocationTargetException) || (pException instanceof PersistenceException)
                    || (this.cause instanceof PersistenceException) || (this.cause instanceof InvocationTargetException)
                    || (pException instanceof RuntimeException)) {
                if (this.cause instanceof PersistenceException) {
                    this.cause = this.cause.getCause();
                }
                if ((this.cause != null) && (this.cause.getCause() != null)) {
                    this.cause = this.cause.getCause();
                }
                if ((this.cause != null) && (this.cause instanceof ConstraintViolationException)) {
                    this.cause = this.cause.getCause();
                }
                if ((this.cause != null) && (this.cause instanceof GenericJDBCException)) {
                    this.cause = this.cause.getCause();
                }
            } else {
                break;
            }
            if ((this.cause == null) || (this.cause.getCause() == null)) {
                break;
            }
        }
        if (pLanguage != null) {
            this.language = pLanguage.toLowerCase();
        }
    }

    /**
     * Metodo que entrega un mensaje tecnico.
     * 
     * @return String
     */
    public String getTechnicalMessage() {
        if (!(this.exception instanceof APPException)) {
            return "";
        }
        String message = this.exception.getMessage();
        StackTraceElement[] stack = this.exception.getStackTrace();
        if (this.cause != null) {
            message = this.cause.getMessage();
            stack = this.cause.getStackTrace();
        }
        for (StackTraceElement element : stack) {
            if (element.getClassName().indexOf("com.fp") > -1) {
                message += " " + element.getClassName() + " : " + element.getLineNumber();
                break;
            }
        }
        return message;
    }

    /**
     * Metodo que entrega el StackTrace de la Excepcion.
     * 
     * @return
     */
    public String getStackTrace() {
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            StackTraceElement[] ele;
            if (this.cause != null) {
                // pw.println(this.cause.getMessage());
                ele = this.cause.getStackTrace();
                // this.cause.printStackTrace(pw);
            } else {
                // pw.println(this.exception.getMessage());
                ele = this.exception.getStackTrace();
                // this.exception.printStackTrace(pw);
            }
            String msg = this.getTechnicalMessage();
            if (msg.length() > 200) {
                msg = msg.substring(0, 200);
            }
            pw.println(msg);
            boolean first = true;
            for (StackTraceElement element : ele) {
                if (first) {
                    pw.println(element.getClassName() + "." + element.getMethodName() + ":" + element.getLineNumber());
                    first = false;
                    continue;
                }
                if (element.getClassName().indexOf("com.fp") > -1) {
                    pw.println(" " + element.getClassName() + "." + element.getMethodName() + ":" + element.getLineNumber());
                }
            }

            String data = sw.toString();
            pw.close();
            return data;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Metodo que entrega el mensaje de usuario.
     * 
     * @return String
     */
    public String getUserMessage() {
        String message = this.exception.getMessage();
        Locale locale = new Locale(this.language, Locale.getDefault().getCountry(), this.getVariant());
        boolean exist = false;
        if (this.exception instanceof APPException) {
            message = ((APPException) this.exception).getUserMessage(this.language);
            exist = true;
        }
        if (this.cause instanceof APPException) {
            message = ((APPException) this.cause).getUserMessage(this.language);
            exist = true;
        }
        if (!exist) {
            ExceptionManager man = this.getExceptionManager();
            if (man == null) {
                try {
                    ResourceBundle bdl = ResourceBundle.getBundle("userMessages", locale);
                    message = bdl.getString(this.exception.getClass().getName());
                } catch (MissingResourceException e) {
                    message = this.exception.getMessage();
                }
            } else {
                man.setLocale(locale);
                message = man.getUserMessage(this.cause != null ? this.cause : this.exception);
            }
        }
        return message;
    }

    /**
     * Metodo que entrega la clase que se encarga del manejo de la excepcion.
     * 
     * @return ExceptionManager
     */
    private ExceptionManager getExceptionManager() {
        try {
            ExceptionsProperties param = ExceptionsProperties.getInstance();
            String className = "";
            if (this.cause == null) {
                className = param.getValue(this.exception.getClass().getName());
            } else {
                className = param.getValue(this.cause.getClass().getName());
            }
            return (ExceptionManager) Class.forName(className).newInstance();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Metodo que entrega el codigo de la excepcion.
     * 
     * @return String
     */
    public String getCode() {
        String code = this.exception.getClass().getName();
        if (this.cause != null) {
            code = this.cause.getClass().getName();
            if ((this.cause instanceof BatchUpdateException) || (this.cause instanceof SQLException)) {
                Integer i = ((SQLException) this.cause).getErrorCode();
                code = i.toString();
                if (code.compareTo("0") == 0) {
                    // Si no puede obtener una coneccion de base devuelve el codigo de error 0
                    code = "99999";
                }
            }
        } else if (this.exception instanceof SQLException) {
            Integer i = ((SQLException) this.exception).getErrorCode();
            code = i.toString();
            if (code.compareTo("0") == 0) {
                // Si no puede obtener una coneccion de base devuelve el codigo de error 0
                code = "99999";
            }
        }
        if (this.exception instanceof APPException) {
            code = ((APPException) this.exception).getCode();
        }
        if (this.cause instanceof APPException) {
            code = ((APPException) this.cause).getCode();
        }
        return code;
    }

    /**
     * Metodo que entrega la variante del idioma.
     * 
     * @return String
     */
    private String getVariant() {
        String code = "";
        for (int i = 0; i < this.getCode().length(); i++) {
            if (Character.isDigit(this.getCode().charAt(i))) {
                break;
            } else {
                code += this.getCode().charAt(i);
            }
        }
        return code;
    }

    /**
     * Entrega el valor de: ExceptionHandler.java
     * 
     * @return boolean
     */
    public boolean isAppexception() {
        return this.appexception;
    }

}
