/**
 *  Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  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.
 */

package org.apache.synapse.transport.passthru;

import org.apache.axis2.context.MessageContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpRequest;
import org.apache.http.nio.NHttpConnection;
import org.apache.log4j.MDC;
import org.apache.synapse.transport.passthru.config.TargetConfiguration;
import org.apache.synapse.transport.passthru.util.ControlledByteBuffer;

/**
 * When a connection is created, an object of this class is stored in the Connection Context.
 * It is used as a holder for information required during the life-cycle of this connection.
 */
public class TargetContext {
    private TargetConfiguration targetConfiguration = null;

    public static final String CONNECTION_INFORMATION = "CONNECTION_INFORMATION";
    /** The request for this connection */
    private TargetRequest request;
    /** The response for this connection */
    private TargetResponse response;
    /** State of the connection */
    private ProtocolState state;
    /** The request message context */
    private MessageContext requestMsgCtx;
    /** The current reader */
    private Pipe reader;
    /** The current writer */
    private Pipe writer;
    /** logger for correlation.log */
    private static final Log correlationLog = LogFactory.getLog(PassThroughConstants.CORRELATION_LOGGER);
    /** Time that state got updated*/
    private long lastStateUpdatedTime;

    public TargetContext(TargetConfiguration targetConfiguration) {
        this.targetConfiguration = targetConfiguration;
    }

    public ProtocolState getState() {
        return state;
    }

    public void setState(ProtocolState state) {
        this.state = state;
    }

    public TargetConfiguration getTargetConfiguration() {
        return targetConfiguration;
    }

    public TargetRequest getRequest() {
        return request;
    }

    public void setRequest(TargetRequest request) {
        this.request = request;
    }

    public TargetResponse getResponse() {
        return response;
    }

    public void setResponse(TargetResponse response) {
        this.response = response;
    }

    public MessageContext getRequestMsgCtx() {
        return requestMsgCtx;
    }

    public void setRequestMsgCtx(MessageContext requestMsgCtx) {
        this.requestMsgCtx = requestMsgCtx;
    }

    public Pipe getReader() {
        return reader;
    }

    public Pipe getWriter() {
        return writer;
    }

    public void setReader(Pipe reader) {
        this.reader = reader;
    }

    public void setWriter(Pipe writer) {
        this.writer = writer;
    }

    /**
     * Reset the resources associated with this context
     */
    public void reset() {
       reset(false);
    }

    /**
     * Reset the resources associated with this context
     *
     * @param isError whether an error is causing this shutdown of the connection.
     *                It is very important to set this flag correctly.
     *                When an error causing the shutdown of the connections we should not
     *                release associated writer buffer to the pool as it might lead into
     *                situations like same buffer is getting released to both source and target
     *                buffer factories
     */
    public void reset(boolean isError) {
        request = null;
        response = null;

        if (writer != null) {
            if (!isError) {      // If there is an error we do not release the buffer to the factory
                ControlledByteBuffer buffer = writer.getBuffer();
                targetConfiguration.getBufferFactory().release(buffer);
            }
        }

        reader = null;
        writer = null;       
    }

    public static void create(NHttpConnection conn, ProtocolState state, 
                              TargetConfiguration configuration) {
        TargetContext targetContext = new TargetContext(configuration);
        conn.getContext().setAttribute(CONNECTION_INFORMATION, targetContext);
        targetContext.setState(state);
        if (targetContext.getTargetConfiguration().isCorrelationLoggingEnabled()) {
            targetContext.updateLastStateUpdatedTime();
        }
    }

    public static void updateState(NHttpConnection conn, ProtocolState state) {

        TargetContext targetContext = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);
        if (targetContext != null) {
            targetContext.setState(state);
            if (targetContext.getTargetConfiguration().isCorrelationLoggingEnabled() && isCorrelationIdAvailable(conn)) {
                long lastStateUpdateTime = targetContext.getLastStateUpdatedTime();
                String url = "", method = "";
                if (targetContext.getRequest() != null) {
                    url = targetContext.getRequest().getUrl().toString();
                    method = targetContext.getRequest().getMethod();
                } else {
                    HttpRequest httpRequest = conn.getHttpRequest();
                    if (httpRequest != null) {
                        url = httpRequest.getRequestLine().getUri();
                        method = httpRequest.getRequestLine().getMethod();
                    }
                }
                if ((method.length() != 0) && (url.length() != 0)) {
                    MDC.put(PassThroughConstants.CORRELATION_MDC_PROPERTY,
                            conn.getContext().getAttribute(PassThroughConstants.CORRELATION_ID).toString());
                    correlationLog.info((targetContext.updateLastStateUpdatedTime() - lastStateUpdateTime)
                            + "|HTTP State Transition|"
                            + conn.getContext().getAttribute("http.connection") + "|"
                            + method + "|" + url + "|"
                            + state.name());
                    MDC.remove(PassThroughConstants.CORRELATION_MDC_PROPERTY);
                }
            }
        } else {
            throw new IllegalStateException("Connection information should be present");
        }
    }

    public static boolean assertState(NHttpConnection conn, ProtocolState state) {
        TargetContext info = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);

        return info != null && info.getState() == state;

    }

    public static ProtocolState getState(NHttpConnection conn) {
        TargetContext info = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);

        return info != null ? info.getState() : null;
    }

    public static void setRequest(NHttpConnection conn, TargetRequest request) {
        TargetContext info = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);

        if (info != null) {
            info.setRequest(request);
        } else {
            throw new IllegalStateException("Connection information should be present");
        }
    }

    public static void setResponse(NHttpConnection conn, TargetResponse response) {
        TargetContext info = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);

        if (info != null) {
            info.setResponse(response);
        } else {
            throw new IllegalStateException("Connection information should be present");
        }
    }

    public static TargetRequest getRequest(NHttpConnection conn) {
        TargetContext info = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);

        return info != null ? info.getRequest() : null;
    }

    public static TargetResponse getResponse(NHttpConnection conn) {
        TargetContext info = (TargetContext)
                conn.getContext().getAttribute(CONNECTION_INFORMATION);

        return info != null ? info.getResponse() : null;
    }

    public static TargetContext get(NHttpConnection conn) {
        return (TargetContext) conn.getContext().getAttribute(CONNECTION_INFORMATION);
    }

    public long getLastStateUpdatedTime() {
        return lastStateUpdatedTime;
    }

    public long updateLastStateUpdatedTime() {
        this.lastStateUpdatedTime = System.currentTimeMillis();
        return this.lastStateUpdatedTime;
    }

    public static Boolean isCorrelationIdAvailable(NHttpConnection connection) {
        if (connection.getContext().getAttribute(PassThroughConstants.CORRELATION_ID) != null) {
            return true;
        }
        return false;
    }
}
