package com.github.gquintana.metrics.sql;

/*
 * #%L
 * Metrics SQL
 * %%
 * Copyright (C) 2014 Open-Source
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */


import com.codahale.metrics.Timer;
import com.github.gquintana.metrics.proxy.MethodInvocation;
import com.github.gquintana.metrics.proxy.ProxyClass;
import com.github.gquintana.metrics.proxy.ProxyHandler;

import java.sql.SQLException;
import java.sql.Wrapper;

/**
 * Base class for all JDBC Proxy handlers.
 * 
 * @param <T> Proxied type
 */
public abstract class JdbcProxyHandler<T> extends ProxyHandler<T> {

    /**
     * JDBC Interface class
     */
    private final Class<T> delegateType;
    /**
     * Timer measuring this proxy lifetime
     */
    private final Timer.Context lifeTimerContext;
    /**
     * Parent factory of proxy factories
     */
    protected final JdbcProxyFactory proxyFactory;

    /**
     * Main constructor
     *
     * @param delegate Wrapped JDBC object
     * @param delegateType JDBC object interface
     * @param proxyFactory Parent factory
     * @param lifeTimerContext Proxy life timer context
     */
    protected JdbcProxyHandler(T delegate, Class<T> delegateType, JdbcProxyFactory proxyFactory, Timer.Context lifeTimerContext) {
        super(delegate);
        this.delegateType = delegateType;
        this.proxyFactory = proxyFactory;
        this.lifeTimerContext = lifeTimerContext;
    }

    private boolean isDelegateType(Class<?> iface) {
        return this.delegateType.equals(iface);
    }

    private Class getClassArg(MethodInvocation methodInvocation) {
        return (Class) methodInvocation.getArgAt(0, Class.class);
    }

    protected Object isWrapperFor(MethodInvocation methodInvocation) throws Throwable {
        final Class iface = getClassArg(methodInvocation);
        return isDelegateType(iface) ? true : methodInvocation.proceed();
    }

    protected Object close(MethodInvocation methodInvocation) throws Throwable {
        stopTimer(lifeTimerContext);
        return methodInvocation.proceed();
    }

    protected static void stopTimer(Timer.Context timerContext) {
        if (timerContext != null) {
            timerContext.stop();
        }
    }

    protected Object unwrap(MethodInvocation<T> methodInvocation) throws SQLException {
        final Class iface = getClassArg(methodInvocation);
        final Wrapper delegateWrapper = (Wrapper) delegate;
        Object result;
        if (isDelegateType(iface)) {
            result = delegateWrapper.isWrapperFor(iface) ? delegateWrapper.unwrap(iface) : iface.cast(delegateWrapper);
        } else {
            result = delegateWrapper.unwrap(iface);
        }
        return result;
    }

    public ProxyClass getProxyClass() {
        return new ProxyClass(delegate.getClass().getClassLoader(), delegateType);
    }

    protected MetricHelper getTimerStarter() {
        return proxyFactory.getMetricHelper();
    }
}