/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2005 Bull S.A.
 * 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$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.ee.jdbc;

import java.io.PrintWriter;
import java.sql.DriverManager;
import java.util.HashSet;
import java.util.Iterator;

import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import javax.sql.XAConnection;
import javax.transaction.xa.XAResource;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * @author Eric hardesty
 */
public class ManagedConnectionImpl implements ManagedConnection, javax.resource.spi.LocalTransaction {

    public Logger trace = null;

    protected PrintWriter out = null;

    protected DataSource factory = null;

    protected HashSet cels = null;

    protected boolean inLocalTransaction = false;

    ManagedConnectionFactoryImpl mcf = null;

    java.sql.Connection connection = null;

    PooledConnection poolCon = null;

    XAConnection xaCon = null;

    PasswordCredential pc = null;

    DriverWrapper wrapper = null;

    private XAResource xares;

    private boolean isAutoCommit = true;

    private boolean closed = false;

    private long[] sigList;

    private int maxSigs;

    private long signature;

    private long lastSig;

    /**
     * Logging (debug enabled)
     */
    private final boolean isDebugOn;

    public ManagedConnectionImpl(ManagedConnectionFactoryImpl _mcf, PasswordCredential _pc, java.sql.Connection _con,
            PooledConnection _pcon, XAConnection _xa, DriverWrapper wrp) {

        mcf = _mcf;
        pc = _pc;
        connection = _con;
        poolCon = _pcon;
        xaCon = _xa;
        wrapper = wrp;
        xares = null;
        out = mcf.pw;
        cels = new HashSet();
        maxSigs = 50;
        sigList = new long[maxSigs];
        signature = 0;
        lastSig = 0;
        trace = mcf.trace;
        isDebugOn = trace.isLoggable(BasicLevel.DEBUG);
    }

    public void signalEvent(int code, Object ch, Exception ex) {
        ConnectionEvent ce = null;
        if (ex == null) {
            ce = new ConnectionEvent(this, code);
        } else {
            ce = new ConnectionEvent(this, code, ex);
        }
        if (ch != null) {
            ce.setConnectionHandle(ch);
        }
        for (Iterator it = cels.iterator(); it.hasNext();) {
            switch (code) {
                case ConnectionEvent.CONNECTION_CLOSED:
                    ((ConnectionEventListener) it.next()).connectionClosed(ce);
                    break;
                case ConnectionEvent.LOCAL_TRANSACTION_STARTED:
                    ((ConnectionEventListener) it.next()).localTransactionStarted(ce);
                    break;
                case ConnectionEvent.LOCAL_TRANSACTION_COMMITTED:
                    ((ConnectionEventListener) it.next()).localTransactionCommitted(ce);
                    break;
                case ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK:
                    ((ConnectionEventListener) it.next()).localTransactionRolledback(ce);
                    break;
                case ConnectionEvent.CONNECTION_ERROR_OCCURRED:
                    ((ConnectionEventListener) it.next()).connectionErrorOccurred(ce);
                    break;
                default:
                    throw new IllegalArgumentException("Illegal eventType: " + code);
            }
        }
    }

    public synchronized boolean getAutoCommit() throws ResourceException {
        return isAutoCommit;
    }

    public synchronized void setAutoCommit(boolean ac) throws ResourceException {
        isAutoCommit = ac;
        try {
            if (connection != null) {
                connection.setAutoCommit(ac);
            }
        } catch (Exception e) {
            throw new ResourceException(e.getMessage());
        }
    }

    /**
     * Adds a connection event listener.
     */
    public void addConnectionEventListener(ConnectionEventListener listener) {
        cels.add(listener);
    }

    /**
     * Change the association of a connection handle
     */
    public synchronized void associateConnection(Object con) throws ResourceException {
        if (isDebugOn) {
            trace.log(BasicLevel.DEBUG, "connection:" + con);
        }
        if (con instanceof ConnectionImpl) {
            ConnectionImpl ci = (ConnectionImpl) con;
            long sig = getNewSignature(true);
            ci.setSignature(sig);
        } else {
            throw new ResourceException("The managedConnection cannot associate this connection: " + con);
        }
    }

    /**
     * Cleanup the ManagedConnection instance.
     */
    public void cleanup() throws ResourceException {
        if (inLocalTransaction) {
            throw new IllegalStateException("A local transaction is not complete");
        }
        clearSignature();
    }

    /**
     * Destroys the physical connection.
     */
    public synchronized void destroy() throws ResourceException {
        if (isDebugOn) {
            trace.log(BasicLevel.DEBUG, "destroy mc=" + this + " with connection=" + connection);
        }
        // Check State of resource
        if (inLocalTransaction) {
            throw new IllegalStateException("in local transaction");
        }
        // Also check global transaction.
        // TODO this works only if XAResource uses setAutoCommit
        // -> it works for our XAResourceImpl
        if (!isAutoCommit) {
            throw new IllegalStateException("in global transaction");
        }
        try {
            if (wrapper != null) {
                DriverManager.deregisterDriver(wrapper);
            }
        } catch (Exception ex) {
            trace.log(BasicLevel.ERROR, "Unable to deregister Driver wrapper", ex);
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (Exception e) {
                trace.log(BasicLevel.ERROR, "", e, "ManagedConnectionImpl", "destroy");
                throw new ResourceException(e);
            }
            connection = null;
            poolCon = null;
            xaCon = null;
            xares = null;
        }
    }

    /**
     * Create a connection handle
     */
    public Object getConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
        if (isDebugOn) {
            trace.log(BasicLevel.DEBUG, "subject:" + subject + " connectionRequest:" + cxRequestInfo);
        }

        long sig = getNewSignature(true);

        try {
            return new ConnectionImpl(this, connection, sig, out);
        } catch (Exception e) {
            trace.log(BasicLevel.ERROR, "", e, "ManagedConnectionImpl", "getConnection");
            throw new ResourceException(e.getMessage());
        }
    }

    /**
     * Returns an javax.resource.spi.LocalTransaction instance.
     */
    public LocalTransaction getLocalTransaction() throws ResourceException {
        return (LocalTransaction) this;
    }

    /**
     * Gets the log writer for this ManagedConnection instance.
     */
    public PrintWriter getLogWriter() {
        return out;
    }

    /**
     * Gets the metadata information for this connection's underlying EIS
     * resource manager instance.
     */
    public ManagedConnectionMetaData getMetaData() throws ResourceException {
        return new MetaDataImpl(this);
    }

    /**
     * Check if the mc is in local transaction
     */
    public XAResource getXAResource() throws ResourceException {
        if (isDebugOn) {
            trace.log(BasicLevel.DEBUG, "");
        }
        if (inLocalTransaction) {
            throw new ResourceException(
                    "The managedConnection is already in a local transaction and an XA resource cannot be obtained");
        }
        if (xares == null) {
            if (xaCon != null) {
                try {
                    xares = xaCon.getXAResource();
                } catch (Exception ex) {
                    throw new ResourceException("Unable to obtain XAResource: ", ex);
                }
            } else {
                if (isDebugOn) {
                    trace.log(BasicLevel.DEBUG, "JDBC driver doesn't support XA, simulate it");
                }
            }
            xares = new XAResourceImpl(this, xares);
        }
        return xares;
    }

    /**
     * Removes an already registered connection event listener from the
     * ManagedConnection instance
     */
    public void removeConnectionEventListener(ConnectionEventListener listener) {
        cels.remove(listener);
    }

    /**
     * Sets the log writer for this ManagedConnection instance.
     */
    public void setLogWriter(PrintWriter _out) {
        out = _out;
    }

    public void addSignature(long sig) {
        int off = -1;
        for (int i = 0; i < maxSigs; i++)
            if (sigList[i] == 0) {
                off = i;
                break;
            }

        if (off > -1)
            sigList[off] = sig;
        else {
            maxSigs += 20;
            long[] tmp = new long[maxSigs];
            System.arraycopy(sigList, 0, tmp, 0, maxSigs - 20);
            sigList = tmp;
            sigList[maxSigs - 20] = sig;
        }
    }

    public void clearSignature() {
        for (int i = 0; i < maxSigs; i++)
            sigList[i] = 0;
        signature = 0;
    }

    public void clearSignature(long sig, boolean clear) {
        for (int i = 0; i < maxSigs; i++) {
            if (sig == sigList[i]) {
                sigList[i] = 0;
                break;
            }
        }
        if (clear) {
            signature = 0;
        }
    }

    public void setSignature(long sig) {
        if (signature == 0) {
            signature = sig;
        }
    }

    /**
     * Gets the signature value passed in if found in the signature hash table
     * @param sig value to find in signature table
     * @return long signature value or 0 if not found
     */
    public long getSignature(long sig) {
        // find signature
        long retSig = 0;
        if (sig > 0) {
            for (int i = 0; i < maxSigs; i++)
                if (sig == sigList[i]) {
                    retSig = sig;
                    break;
                }
        }
        return (retSig);
    }

    /**
     * Gets the current signature value
     * @return long current signature value
     */
    public long getSignature() {
        return (signature);
    }

    /**
     * Gets the next signature value
     * @return long next signature value
     */
    public long getNewSignature(boolean setSig) {
        long sig = System.currentTimeMillis();
        if (sig <= lastSig && lastSig != 0) {
            sig = lastSig++;
        }
        if (isDebugOn) {
            trace.log(BasicLevel.DEBUG, "Sig is " + sig + " last Sig was " + lastSig);
        }
        lastSig = sig;
        addSignature(sig);
        if (setSig) {
            signature = sig;
        }
        return sig;
    }

    public void close(ConnectionImpl ch) throws ResourceException {
        clearSignature(ch.key, true);
        signalEvent(ConnectionEvent.CONNECTION_CLOSED, ch, null);
    }

    // ---------------------------------------------------------------
    // Implementation of Interface LocalTransaction
    // ---------------------------------------------------------------

    /**
     * start a new local transaction.
     */
    public void begin() throws ResourceException {
        if (inLocalTransaction) {
            throw new ResourceException("The managedConnection is already in a LocalTransaction");
        }
        try {
            inLocalTransaction = true;
            connection.setAutoCommit(false);
        } catch (Exception ex) {
        }
    }

    /**
     * commit the local transaction.
     */
    public void commit() throws ResourceException {
        try {
            connection.commit();
            connection.setAutoCommit(true);
            inLocalTransaction = false;
        } catch (Exception ex) {
        }
    }

    /**
     * rollback the local transaction.
     */
    public void rollback() throws ResourceException {
        try {
            connection.rollback();
            connection.setAutoCommit(true);
            inLocalTransaction = false;
        } catch (Exception ex) {
        }
    }

}
