/*
 *  Copyright (c) WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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.wso2.carbon.mss.internal.router;

import com.google.common.base.Preconditions;
import com.google.common.collect.Multimap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkedInput;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.stream.ChunkedFile;
import org.wso2.carbon.mss.ChunkResponder;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;

/**
 * HttpResponder responds back to the client that initiated the request. Caller can use sendJson method to respond
 * back to the client in json format.
 */
public class BasicHttpResponder extends AbstractHttpResponder {

    private final Channel channel;
    private final boolean keepAlive;
    private final AtomicBoolean responded;

    public BasicHttpResponder(Channel channel, boolean keepAlive) {
        this.channel = channel;
        this.keepAlive = keepAlive;
        responded = new AtomicBoolean(false);
    }

    @Override
    public ChunkResponder sendChunkStart(HttpResponseStatus status, @Nullable Multimap<String, String> headers) {
        Preconditions.checkArgument(responded.compareAndSet(false, true), "Response has been already sent");
        Preconditions.checkArgument((status.code() >= 200 && status.code() < 210), "Http Chunk Failure");
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);

        setCustomHeaders(response, headers);

//    response.setChunked(true);
        response.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);

        boolean responseKeepAlive = setResponseKeepAlive(response);
        channel.write(response);
        return new ChannelChunkResponder(channel, responseKeepAlive);
    }

    @Override
    public void sendContent(HttpResponseStatus status, @Nullable ByteBuf content, String contentType,
                            @Nullable Multimap<String, String> headers) {
        Preconditions.checkArgument(responded.compareAndSet(false, true), "Response has been already sent");
        HttpResponse response;
        if (content != null) {
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
            HttpHeaders responseHeaders = response.headers();
            responseHeaders.set(HttpHeaders.Names.CONTENT_TYPE, contentType);
            responseHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes());
        } else {
            response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
            HttpHeaders responseHeaders = response.headers();
            responseHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
        }
        setCustomHeaders(response, headers);

        boolean responseKeepAlive = setResponseKeepAlive(response);
        ChannelFuture future = channel.write(response);
        if (!responseKeepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void sendFile(File file, String contentType,
                         @Nullable Multimap<String, String> headers) throws IOException {
        Preconditions.checkArgument(responded.compareAndSet(false, true), "Response has been already sent");
        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

        setCustomHeaders(response, headers);
        response.headers().set(HttpHeaders.Names.CONTENT_TYPE, contentType);
        // response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());

        response.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
        channel.writeAndFlush(response);

        HttpChunkedInput httpChunkWriter = new HttpChunkedInput(new ChunkedFile(file));
        ChannelFuture sendFileFuture = channel.write(httpChunkWriter);

        final boolean responseKeepAlive = setResponseKeepAlive(response);
        sendFileFuture.addListener(future -> {
            if (!responseKeepAlive) {
                channel.close();
            }
        });
    }

    private void setCustomHeaders(HttpResponse response, @Nullable Multimap<String, String> headers) {
        // Add headers. They will override all headers set by the framework
        if (headers != null) {
            for (Map.Entry<String, Collection<String>> entry : headers.asMap().entrySet()) {
                response.headers().add(entry.getKey(), entry.getValue());
            }
        }
    }

    private boolean setResponseKeepAlive(HttpResponse response) {
        HttpHeaders headers = response.headers();
        boolean closeConn = HttpHeaders.Values.CLOSE.equalsIgnoreCase(headers.get(HttpHeaders.Names.CONNECTION));
        boolean responseKeepAlive = this.keepAlive && !closeConn;

        if (responseKeepAlive) {
            headers.set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        } else {
            headers.set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
        }

        return responseKeepAlive;
    }
}

@@ -4615,24 +4615,25 @@
 tType);%0A
+%0A
         
 // respo
@@ -4624,19 +4624,21 @@
         
-// 
+if (!
 response
@@ -4640,35 +4640,40 @@
 ponse.headers().
-set
+contains
 (HttpHeaders.Nam
@@ -4693,27 +4693,17 @@
 NGTH
-, file.length());%0A%0A
+)) %7B%0A    
     
@@ -4795,16 +4795,26 @@
 UNKED);%0A
+        %7D%0A
         

Validate conflicting HTTP headers