/**
 *  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.commons.collections.map.MultiValueMap;
import org.apache.http.*;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.nio.ContentDecoder;
import org.apache.http.nio.NHttpClientConnection;
import org.apache.synapse.transport.http.conn.LoggingNHttpClientConnection;
import org.apache.synapse.transport.passthru.config.TargetConfiguration;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * This class represents a response coming from the target server.
 */
public class TargetResponse {
    // private Log log = LogFactory.getLog(TargetResponse.class);
    /** To pipe the incoming data through */
    private Pipe pipe = null;
    /** Headers of the response */
    private Map<String, String> headers = new HashMap<String, String>();
    /** Excess headers of the response */ 
    private Map excessHeaders = new MultiValueMap();
    /** The status of the response */
    private int status = HttpStatus.SC_OK;
    /** Http status line */
    private String statusLine = "OK";
    /** The Http response */
    private HttpResponse response = null;
    /** Configuration of the sender */
    private TargetConfiguration targetConfiguration;
    /** Protocol version */
    private ProtocolVersion version = HttpVersion.HTTP_1_1;
    /** This utility class is used for determining weather we need to close the connection
     * after submitting the response */
    private ConnectionReuseStrategy connStrategy = new DefaultConnectionReuseStrategy();
    /** The connection */
    private NHttpClientConnection connection;
    /** Weather this response has a body */
    private boolean expectResponseBody = true;
    /** Whether to shutdown connection after response completion*/
    private boolean forceShutdownConnectionOnComplete = false;

    public TargetResponse(TargetConfiguration targetConfiguration,
                          HttpResponse response,
                          NHttpClientConnection conn,
                          boolean expectResponseBody,
                          boolean forceShutdownConnectionOnComplete) {
        this.targetConfiguration = targetConfiguration;
        this.response = response;
        this.connection = conn;

        this.version = response.getProtocolVersion();

        this.status = response.getStatusLine().getStatusCode();
        this.statusLine = response.getStatusLine().getReasonPhrase();

        Header[] headers = response.getAllHeaders();
        if (headers != null) {
            for (Header header : headers) {
            	if(this.headers.containsKey(header.getName())) {
            		addExcessHeader(header);
            	} else {
            		this.headers.put(header.getName(), header.getValue());
            	}
             }        
        }   

        this.expectResponseBody = expectResponseBody;
        this.forceShutdownConnectionOnComplete = forceShutdownConnectionOnComplete;
    }    

    /**
     * Starts the response
     * @param conn the client connection
     */
    public void start(NHttpClientConnection conn) {
        TargetContext.updateState(conn, ProtocolState.RESPONSE_HEAD);
        
        if (expectResponseBody) {
            pipe
                = new Pipe(conn, targetConfiguration.getBufferFactory().getBuffer(), "target", targetConfiguration);

            TargetContext.get(conn).setReader(pipe);

            BasicHttpEntity entity = new BasicHttpEntity();
            if (response.getStatusLine().getProtocolVersion().greaterEquals(HttpVersion.HTTP_1_1)) {
                entity.setChunked(true);
            }
            response.setEntity(entity);
        } else {            
            if (!connStrategy.keepAlive(response, conn.getContext()) || forceShutdownConnectionOnComplete) {
                try {
                    // this is a connection we should not re-use
                    TargetContext.updateState(conn, ProtocolState.CLOSING);
                    targetConfiguration.getConnections().shutdownConnection(conn);
                                       
                } catch (Exception ignore) {

                }
            } else {
                targetConfiguration.getConnections().releaseConnection(conn);
            }
        }
    }

    /**
     * Read the data from the wire and read in to the pipe so that other end of
     * the pipe can write.
     * @param conn the target connection
     * @param decoder content decoder
     * @throws java.io.IOException if an error occurs
     * @return number of bites read
     */
    public int read(NHttpClientConnection conn, ContentDecoder decoder) throws IOException {
    	
    	int bytes=0;
    	
    	if(pipe != null){
    		bytes = pipe.produce(decoder);
    	}

        // Update connection state
        if (decoder.isCompleted()) {
            conn.getContext().setAttribute(PassThroughConstants.RES_FROM_BACKEND_READ_END_TIME,System.currentTimeMillis());
            conn.getContext().setAttribute(PassThroughConstants.RES_ARRIVAL_TIME,System.currentTimeMillis());
            TargetContext.updateState(conn, ProtocolState.RESPONSE_DONE);

            targetConfiguration.getMetrics().notifyReceivedMessageSize(
                    conn.getMetrics().getReceivedBytesCount());

            if (!this.connStrategy.keepAlive(response, conn.getContext())  || forceShutdownConnectionOnComplete) {
                TargetContext.updateState(conn, ProtocolState.CLOSED);

                targetConfiguration.getConnections().shutdownConnection(conn);
            } else {
                if (conn instanceof LoggingNHttpClientConnection) {
                    ((LoggingNHttpClientConnection) conn).setReleaseConn(true);
                } 
            }
        }
        return bytes;
    }

    public String getHeader(String name) {
        return headers.get(name);
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public Map getExcessHeaders() {
    	return this.excessHeaders;
    }
    
    public void addExcessHeader(Header h) {
    	this.excessHeaders.put(h.getName(), h.getValue());
    }

    public Pipe getPipe() {
        return pipe;
    }

    public int getStatus() {
        return status;
    }

    public String getStatusLine() {
        return statusLine;
    }

    public boolean isExpectResponseBody() {
        return expectResponseBody;
    }

    public NHttpClientConnection getConnection() {
        return connection;
    }

    public ProtocolVersion getVersion() {
        return version;
    }
}
