package com.github.dockerjava.netty.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;

import java.nio.charset.Charset;

import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.exception.BadRequestException;
import com.github.dockerjava.api.exception.ConflictException;
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.exception.InternalServerErrorException;
import com.github.dockerjava.api.exception.NotAcceptableException;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.exception.NotModifiedException;
import com.github.dockerjava.api.exception.UnauthorizedException;

/**
 * Handler that is responsible to handle an incoming {@link HttpResponse}. It evaluates the status code and triggers the appropriate
 * lifecycle methods at the passed {@link ResultCallback}.
 *
 * @author Marcus Linke
 */
public class HttpResponseHandler extends SimpleChannelInboundHandler<HttpObject> {

    private HttpResponse response;

    private ByteBuf errorBody = Unpooled.buffer();

    private HttpRequestProvider requestProvider;

    private ResultCallback<?> resultCallback;

    public HttpResponseHandler(HttpRequestProvider requestProvider, ResultCallback<?> resultCallback) {
        super(false);
        this.requestProvider = requestProvider;
        this.resultCallback = resultCallback;
    }

    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpResponse) {

            response = (HttpResponse) msg;

            resultCallback.onStart(() -> ctx.channel().close());

        } else if (msg instanceof HttpContent) {

            HttpContent content = (HttpContent) msg;

            ByteBuf byteBuf = content.content();

            switch (response.status().code()) {
                case 200:
                case 201:
                case 204:
                    ctx.fireChannelRead(byteBuf);
                    break;
                default:
                    errorBody.writeBytes(byteBuf);
            }

            if (content instanceof LastHttpContent) {
                try {

                    switch (response.status().code()) {
                        case 101:
                        case 200:
                        case 201:
                        case 204:
                            break;
                        case 301:
                        case 302:
                            if (response.headers().contains(HttpHeaderNames.LOCATION)) {
                                String location = response.headers().get(HttpHeaderNames.LOCATION);
                                HttpRequest redirected = requestProvider.getHttpRequest(location);

                                ctx.channel().writeAndFlush(redirected);
                            }
                            break;
                        case 304:
                            throw new NotModifiedException(getBodyAsMessage(errorBody));
                        case 400:
                            throw new BadRequestException(getBodyAsMessage(errorBody));
                        case 401:
                            throw new UnauthorizedException(getBodyAsMessage(errorBody));
                        case 404:
                            throw new NotFoundException(getBodyAsMessage(errorBody));
                        case 406:
                            throw new NotAcceptableException(getBodyAsMessage(errorBody));
                        case 409:
                            throw new ConflictException(getBodyAsMessage(errorBody));
                        case 500:
                            throw new InternalServerErrorException(getBodyAsMessage(errorBody));
                        default:
                            throw new DockerException(getBodyAsMessage(errorBody), response.status().code());
                    }
                } catch (Throwable e) {
                    resultCallback.onError(e);
                } finally {
                    resultCallback.onComplete();
                }
            }
        }
    }

    private String getBodyAsMessage(ByteBuf body) {
        String result = body.readBytes(body.readableBytes()).toString(Charset.forName("UTF-8"));
        body.discardReadBytes();
        body.release();
        return result;
    }
}
