/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2010 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library 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 any later version.
 *
 * This library 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: ConnectionImpl.java 20173 2010-08-17 08:12:04Z benoitf $
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.ee.jdbc;

import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.resource.internal.IJDBCConnection;
import org.ow2.jonas.resource.internal.SQLManager;
import org.ow2.jonas.resource.internal.cm.ManagedConnectionInfo;

/**
 * The class <b>ConnectionImpl</b> provides the Connection implementation for
 * encapsulating JDBC {@link Connection}s
 * @author Eric hardesty
*/
public class ConnectionImpl implements InvocationHandler {

    public Logger trace = null;

    ManagedConnectionImpl mc = null;

    Connection connection = null;

    PrintWriter pw = null;

    long key = 0;

    String user = "";

    private SQLManager conman = null;

    private ManagedConnectionInfo mci = null;

    /**
     * Traces are enabled ?
     */
    private final boolean isDebugging;

    protected ConnectionImpl(final ManagedConnectionImpl _mc, final Connection _con, final long _key, final PrintWriter _pw) {
        mc = _mc;
        connection = _con;
        key = _key;
        pw = _pw;
        trace = mc.trace;
        isDebugging = trace.isLoggable(BasicLevel.DEBUG);
        if (connection == null) {
            trace.log(BasicLevel.ERROR, "Init ConnectionImpl with a null Connection");
        }
    }

    /**
     * @return true if connection is physically closed
     */
    public boolean isPhysicallyClosed() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        return (connection.isClosed());
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.ee.jdbc.IJDBCConnection#setJonasInfo(org.ow2.jonas.resource.cm.ManagedConnectionInfo, org.ow2.jonas.resource.SQLManager)
     */
    public void setJonasInfo(final ManagedConnectionInfo _mci, final SQLManager _conman) {
        mci = _mci;
        conman = _conman;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.ee.jdbc.IJDBCConnection#setUser()
     */
    public void setUser() {
        try {
            user = mc.getMetaData().getUserName();
        } catch (Exception ex) {
        }
    }




    /**
     * Processes a method invocation on a proxy instance and returns the result.
     * This method will be invoked on an invocation handler when a method is
     * invoked on a proxy instance that it is associated with.
     * @param proxy the proxy instance that the method was invoked on
     * @param method the <code>Method</code> instance corresponding to the
     *        interface method invoked on the proxy instance.
     * @param args an array of objects containing the values of the arguments
     *        passed in the method invocation on the proxy instance, or
     *        <code>null</code> if interface method takes no arguments.
     * @return the value to return from the method invocation on the proxy
     *         instance.
     * @throws Throwable the exception to throw from the method invocation on
     *         the proxy instance. The exception's type must be assignable
     *         either to any of the exception types declared in the
     *         <code>throws</code> clause of the interface method or to the
     *         unchecked exception types <code>java.lang.RuntimeException</code>
     *         or <code>java.lang.Error</code>. If a checked exception is
     *         thrown by this method that is not assignable to any of the
     *         exception types declared in the <code>throws</code> clause of
     *         the interface method, then an UndeclaredThrowableException
     *         containing the exception that was thrown by this method will be
     *         thrown by the method invocation on the proxy instance.
     */
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        // Methods on the Object.class are not send on the connection impl
        if (method.getDeclaringClass().getName().equals("java.lang.Object")) {
            return handleObjectMethods(method, args);
        }

        // Methods that are part of the IJDBCConnection interface
        if ("setJonasInfo".equals(method.getName())) {
            setJonasInfo((ManagedConnectionInfo) args[0], (SQLManager) args[1]);
            return null;
        } else if ("isClosed".equals(method.getName())) {
            return isClosed();
        } else if ("getKey".equals(method.getName())) {
            return getKey();
        } else if ("isPhysicallyClosed".equals(method.getName())) {
            return Boolean.valueOf(isPhysicallyClosed());
        } else if ("close".equals(method.getName())) {
            close(proxy);
            return null;
        } else if ("setSignature".equals(method.getName())) {
            setSignature((Long) args[0]);
            return null;
        } else if ("setUser".equals(method.getName())) {
            setUser();
            return null;
        } else if ("prepareStatement".equals(method.getName()) && conman != null && conman.getMaxPstmtPoolSize() >= 0) {
            if (isDebugging) {
               trace.log(BasicLevel.DEBUG, method.getName());
            }
            try {
                checkContext();
            } catch (Exception e) {
                trace.log(BasicLevel.ERROR, "checkContext error");
                throw new SQLException("JOnAS JDBC: " + e.getMessage());
            }
            if (args.length == 1) {
                return conman.getPStatement(mci, connection, user, (String) args[0]);
            } else if (args.length == 3) {
                return conman.getPStatement(mci, connection, user, (String) args[0], (Integer) args[1], (Integer) args[2]);
            } else if (args.length == 4) {
                return conman.getPStatement(mci, connection, user, (String) args[0], (Integer) args[1], (Integer) args[2], (Integer) args[3]);
            } else if (args.length == 2) {
                String sql = (String) args[0];
                Object val = args[1];
                if (val.getClass().isArray()) {
                    if (int.class.equals(val.getClass().getComponentType())) {
                        return conman.getPStatement(mci, connection, user, sql, toArrayInt((Integer[]) val));
                    } else if (String.class.equals(val.getClass().getComponentType())) {
                        return conman.getPStatement(mci, connection, user, sql, (String[]) val);
                    }
                } else if (val instanceof Integer) {
                    return conman.getPStatement(mci, connection, user, sql, (Integer) val);
                }
            }
            throw new IllegalStateException("Invalid call with arguments '" + args + "' for the method '" + method.getName() + "'.");
        } else {
            if (isDebugging) {
               trace.log(BasicLevel.DEBUG, method.getName());
            }
            try {
                checkContext();
            } catch (Exception e) {
                trace.log(BasicLevel.ERROR, "checkContext error");
                throw new SQLException("JOnAS JDBC: " + e.getMessage());
            }

            // Then delegate to the physicalConnection object
            try {
                return method.invoke(connection, args);
            } catch (InvocationTargetException e) {
                // Check if it is an SQLException
                Throwable targetException = e.getTargetException();
                // Rethrow Exception
                throw targetException;
            }

        }
    }

	public static int[] toArrayInt(final Integer[] ints) {
		int[] values = new int[ints.length];
		int i=0;
		for (Integer integer : ints) {
			values[i++] = integer;
		}
		return values;
	}


    /**
     * Manages all methods of java.lang.Object class.
     * @param method the <code>Method</code> instance corresponding to the
     *        interface method invoked on the proxy instance. The declaring
     *        class of the <code>Method</code> object will be the interface
     *        that the method was declared in, which may be a superinterface of
     *        the proxy interface that the proxy class inherits the method
     *        through.
     * @param args an array of objects containing the values of the arguments
     *        passed in the method invocation on the proxy instance
     * @return the value of the called method.
     */
    protected Object handleObjectMethods(final Method method, final Object[] args) {
        String methodName = method.getName();

        if (methodName.equals("equals")) {
            // equals with a proxy
            if (Proxy.isProxyClass(args[0].getClass())) {
                return this.equals(Proxy.getInvocationHandler(args[0]));
            }
            return Boolean.valueOf(this.equals(args[0]));
        } else if (methodName.equals("toString")) {
            return this.toString();
        } else if (methodName.equals("hashCode")) {
            return Integer.valueOf(this.hashCode());
        } else {
            throw new IllegalStateException("Method '" + methodName + "' is not present on Object.class.");
        }
    }

    public void close(final Object proxy) throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        try {
            if (key != 0) {
                mc.close((IJDBCConnection) proxy);
                key = 0;
            }
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "error");
            e.printStackTrace();
            throw new SQLException("JOnAS JDBC: " + e.getMessage());
        }
    }

    public boolean isClosed() throws SQLException {
        if (isDebugging) {
            trace.log(BasicLevel.DEBUG, "");
        }
        return (key == 0);
    }

    private void checkContext() throws Exception {
        if (key == 0) {
            trace.log(BasicLevel.ERROR, "Connection is closed");
            throw new Exception("Connection is closed");
        }
        if (key != mc.getSignature()) {
            if (mc.getSignature() == 0) {
                mc.setSignature(key);
            } else {
                trace.log(BasicLevel.ERROR, "not current active Connection ");
                throw new Exception("Connection w/sig(" + key + ") is not current active Connection ("
                        + mc.getSignature() + ")");
            }
        }
    }

    public void setSignature(final long sig) {
        key = sig;
    }

   public long getKey() {
       return key;
   }
}
