package io.cantor.http;

import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

import io.netty.buffer.ByteBufOutputStream;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;

public class HandlerResponse {

    private static final byte[] EMPTY_BODY = "".getBytes();

    private AtomicBoolean ok = new AtomicBoolean(true);

    private FullHttpResponse delegator;
    private HttpStatus status;
    private HttpHeaders headers;
    private Consumer<Throwable> catcher;
    private Runnable completer;
    private TriConsumer<FullHttpResponse, Runnable, Consumer<Throwable>> writer;
    private AffinityScheduler scheduler;
    private String httpVer;

    HandlerResponse(HandlerRequest request,
                    AffinityScheduler scheduler,
                    TriConsumer<FullHttpResponse, Runnable, Consumer<Throwable>> writer) {
        this.httpVer = request.version();
        this.scheduler = scheduler;
        this.writer = writer;
    }

    private void checkAndInit() {
        if (ok.compareAndSet(true, false)) {
            delegator = new DefaultFullHttpResponse(HttpVersion.valueOf(httpVer),
                                                    HttpResponseStatus.OK);
            status = new HttpStatus(delegator);
            headers = new HttpHeaders(delegator.headers());
        }
    }
    public HandlerResponse header(String name, String value) {
        headers().set(name, value);
        return this;
    }

    public HandlerResponse statusCode(int code) {
        status().code(code);
        return this;
    }

    public HttpStatus status() {
        checkAndInit();
        return status;
    }

    public HttpHeaders headers() {
        checkAndInit();
        return headers;
    }

    public HandlerResponse write(byte[] bytes) {
        checkAndInit();
        delegator.content().writeBytes(bytes);
        return this;
    }

    public OutputStream stream() {
        checkAndInit();
        return new ByteBufOutputStream(delegator.content());
    }

    public void catcher(Consumer<Throwable> catcher) {
        this.catcher = catcher;
    }

    public void completer(Runnable completer) {
        this.completer = completer;
        complete();
    }

    public void complete() {
        checkAndInit();
        if (!headers().contains(HttpHeaderNames.CONTENT_LENGTH.toString())) {
            int length = delegator().content().writerIndex();
            headers().set(HttpHeaderNames.CONTENT_LENGTH.toString(), length);
        }

        scheduler.io(() -> {
            writer.accept(delegator, completer, catcher);
        });
    }

    public void ok() {
        complete(HttpResponseStatus.OK.code(), EMPTY_BODY);
    }

    public void ok(byte[] body) {
        complete(HttpResponseStatus.OK.code(), body);
    }

    public void badRequest() {
        complete(HttpResponseStatus.BAD_REQUEST.code(),
                 HttpResponseStatus.BAD_REQUEST.toString().getBytes());
    }

    public void badRequest(byte[] msg) {
        complete(HttpResponseStatus.BAD_REQUEST.code(), msg);
    }

    public void notFound() {
        complete(HttpResponseStatus.NOT_FOUND.code(),
                 HttpResponseStatus.NOT_FOUND.toString().getBytes());
    }

    public void notFound(byte[] msg) {
        complete(HttpResponseStatus.NOT_FOUND.code(), msg);
    }

    public void internalServerError() {
        complete(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
                 HttpResponseStatus.INTERNAL_SERVER_ERROR.toString().getBytes());
    }

    public void internalServerError(byte[] msg) {
        complete(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), msg);
    }

    public void forbidden() {
        complete(HttpResponseStatus.FORBIDDEN.code(),
                 HttpResponseStatus.FORBIDDEN.toString().getBytes());
    }

    public void forbidden(byte[] msg) {
        complete(HttpResponseStatus.FORBIDDEN.code(), msg);
    }

    public void unauthorized() {
        complete(HttpResponseStatus.UNAUTHORIZED.code(),
                 HttpResponseStatus.UNAUTHORIZED.toString().getBytes());
    }

    public void unauthorized(byte[] msg) {
        complete(HttpResponseStatus.UNAUTHORIZED.code(), msg);
    }

    private void complete(int code, byte[] body) {
        status().code(code);
        write(body);
        complete();
    }

    private FullHttpResponse delegator() {
        checkAndInit();
        return delegator;
    }

}