/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

package stdx.net.http

import std.io.InputStream
import std.collection.ArrayList

/**
 * A builder of HttpResponse, which is used by the server to return responses.
 */
public class HttpResponseBuilder {
    var _version: Protocol = HTTP1_1
    var _status: ?UInt16 = None
    var _headers: ?HttpHeaders = None
    var _body: InputStream = HttpEmptyBody.INSTANCE
    var _trailers: ?HttpHeaders = None
    var _request: ?HttpRequest = None

    func reset(): Unit {
        _version = HTTP1_1
        _status = None
        match (_headers) {
            case Some(value) => value.reset()
            case None => _headers = HttpHeaders()
        }
        _body = HttpEmptyBody.INSTANCE
        _trailers = None
        _request = None
    }

    public init() {}

    /**
     * Sets the HTTP-version of this response.
     *
     * @param version the protocol version of the response.
     * @return HttpResponseBuilder whose protocol version has been set.
     */
    public func version(version: Protocol): HttpResponseBuilder {
        this._version = version
        return this
    }

    /**
     * Sets the status-code of this response.
     *
     * @param status the status of the response.
     * @return HttpResponseBuilder whose status has been set.
     *
     * @throws HttpException, if there status not within the specification range.
     *  RFC 9110 says: All valid status codes are within the range of 100 to 599
     */
    public func status(status: UInt16): HttpResponseBuilder {
        if (status < 100 || status > 599) {
            throw HttpException("Valid status codes are within the range of 100 to 599.")
        }
        this._status = status
        return this
    }

    /**
     * Adds a specifies key-value pair to this response headers
     * Name should consist of tokens, value should consist of vchar (visible US-ASCII octet), SP or HTAB.
     *
     * @param key the field name
     * @param value the field value
     * @return HttpResponseBuilder whose header has been set.
     *
     * @throws HttpException, if field name or value invalid.
     */
    public func header(name: String, value: String): HttpResponseBuilder {
        headers.add(name, value)
        return this
    }

    /**
     * Adds the specifies headers to this response headers
     *
     * @param headers the headers to be added into the response.
     * @return HttpResponseBuilder whose header has been set.
     */
    public func addHeaders(headers: HttpHeaders): HttpResponseBuilder {
        this.headers.addAll(headers)
        return this
    }

    /**
     * Sets the specifies headers to this response headers.
     *
     * @param headers the headers to be set to the response.
     * @return HttpResponseBuilder whose header has been set.
     */
    public func setHeaders(headers: HttpHeaders): HttpResponseBuilder {
        _headers = headers
        return this
    }

    /**
     * Sets the specified message body for this response.
     * If the body exists before, it will be overwritten.
     *
     * @param body the response's body.
     * @return HttpResponseBuilder whose body has been set.
     */
    public func body(body: Array<UInt8>): HttpResponseBuilder {
        _body = HttpRawBody(body)
        return this
    }

    /**
     * Sets the specified message body for this response.
     * If the body exists before, it will be overwritten.
     *
     * @param body the response's body.
     * @return HttpResponseBuilder whose body has been set.
     */
    public func body(body: InputStream): HttpResponseBuilder {
        _body = body
        return this
    }

    /**
     * Sets the specified message body for this response.
     * If the body exists before, it will be overwritten.
     *
     * @param body the response's body.
     * @return HttpResponseBuilder whose body has been set.
     */
    public func body(body: String): HttpResponseBuilder {
        _body = HttpRawBody(body)
        return this
    }

    /**
     * Adds a specifies key-value pair to this response trailers
     * Name should consist of tokens, value should consist of vchar (visible US-ASCII octet), SP or HTAB.
     *
     * @param key the field name
     * @param value the field value
     * @return HttpResponseBuilder whose trailer has been set.
     *
     * @throws HttpException, if field name or value invalid.
     */
    public func trailer(name: String, value: String): HttpResponseBuilder {
        trailers.add(name, value)
        return this
    }

    /**
     * Adds the specifies trailers to this response trailers
     *
     * @param trailers the trailers to be added into the response.
     * @return HttpResponseBuilder whose trailers has been set.
     */
    public func addTrailers(trailers: HttpHeaders): HttpResponseBuilder {
        this.trailers.addAll(trailers)
        return this
    }

    /**
     * Sets the specifies trailers to this response trailers.
     *
     * @param trailers the trailers to be set to the response.
     * @return HttpResponseBuilder whose trailers has been set.
     */
    public func setTrailers(trailers: HttpHeaders): HttpResponseBuilder {
        _trailers = trailers
        return this
    }

    /**
     * Sets the specifies trailers to the response trailers.
     *
     * @param request the request related to the response.
     * @return HttpResponseBuilder whose request has been set.
     */
    public func request(request: HttpRequest): HttpResponseBuilder {
        this._request = request
        return this
    }

    /**
     * Generates and returns the configured HttpResponse.
     *
     * @return HttpResponse whose arguments are set by the builder.
     */
    public func build(): HttpResponse {
        let bodySize: ?Int64 = sizeOf(_body)
        return HttpResponse(
            _version: _version,
            _status: _status ?? HttpStatusCode.STATUS_OK,
            _headers: _headers,
            _body: _body,
            _trailers: _trailers,
            _bodySize: bodySize,
            _request: _request
        )
    }

    prop headers: HttpHeaders {
        get() {
            return match (_headers) {
                case Some(h) => h
                case None =>
                    let h = HttpHeaders()
                    _headers = h
                    return h
            }
        }
    }

    prop trailers: HttpHeaders {
        get() {
            return match (_trailers) {
                case Some(t) => t
                case None =>
                    let t = HttpHeaders()
                    _trailers = t
                    return t
            }
        }
    }
}

/*HTTP response, which is used by the client to read this response from the server.*/

public class HttpResponse <: ToString {
    HttpResponse(
        var _version!: Protocol,
        var _status!: UInt16,
        var _headers!: ?HttpHeaders,
        var _body!: InputStream,
        var _trailers!: ?HttpHeaders,
        var _bodySize!: ?Int64,
        var _request!: ?HttpRequest,
        var pushResponses!: ?ArrayList<Object> = None,
        // upgrade to websocket
        var connNode!: ?BodyProviderConn = None
    ) {}

    /**
     * Gets the Http-version of this response.
     * Default value of version is HTTP1_1.
     */
    public prop version: Protocol {
        get() {
            _version
        }
    }

    /**
     * Gets the status-code of this response.
     * Default value of status is 200.
     */
    public prop status: UInt16 {
        get() {
            _status
        }
    }

    /**
     * Gets the headers of this response, which stores the key-value pair of the response header.
     * Default provide an empty HttpHeaders.
     */
    public prop headers: HttpHeaders {
        get() {
            return match (_headers) {
                case Some(h) => h
                case None =>
                    let h = HttpHeaders()
                    _headers = h
                    return h
            }
        }
    }

    /**
     * Gets the body of this response.
     * Default value of body is an InputStream with no data.
     */
    public prop body: InputStream {
        get() {
            _body
        }
    }

    /**
     * Gets the trailers of this response, which save the trailer key-value pair in the same format as the header field.
     * Default provide an empty HttpHeaders.
     */
    public prop trailers: HttpHeaders {
        get() {
            return match (_trailers) {
                case Some(t) => t
                case None =>
                    let t = HttpHeaders()
                    _trailers = t
                    return t
            }
        }
    }

    /**
     * Gets the body size of this response.
     *
     * @return Some(0), means the body is empty.
     * @return Some(v), means the body size is known, input value of body is Array<UInt8> or String
     *                  or InputStream whose length is valid.
     * @return None, means the body size is unknown, that is, the body is self-implemented InputStream
     *                  whose length is invalid.
     */
    public prop bodySize: Option<Int64> {
        get() {
            _bodySize
        }
    }

    /**
     * Gets the request corresponding to this response.
     * Default value of request is None.
     */
    public prop request: Option<HttpRequest> {
        get() {
            _request
        }
    }

    /**
     * If headers contains "Connection: close", this value is set false.
     * It indicates that whether the connection will be closed after reading Body.
     */
    public prop isPersistent: Bool {
        get() {
            if (let Some(h) <- _headers) {
                return !(h.get("connection") |> splitValuesByComma |> "close".caseInsensitiveMatchOne)
            }
            return true
        }
    }

    public func close(): Unit {
        match (_body) {
            case closable: Resource => closable.close()
            case _ => ()
        }
    }

    /**
     * Prints request start line, headers, body size, trailer.
     *
     * @return String that can represent the response.
     */
    public override func toString(): String {
        var str = StringBuilder()
        str.append("${_version} ${_status} ${phrase}\r\n")
        str.append(_headers?.toString() ?? "\r\n")
        match (bodySize) {
            case Some(0) => ()
            case Some(s) => str.append("body size: ${s}\r\n")
            case None => str.append("unknown body size\r\n")
        }
        str.append(_trailers?.toString() ?? "")
        return str.toString()
    }

    prop phrase: String {
        get() {
            return STATUS_TEXT.get(status) ?? ""
        }
    }
}

public class HttpResponseWriter {
    public HttpResponseWriter(let ctx: HttpContext) {}

    /**
     * Send buf to client.
     *
     * @param buf the buf server wants to send to client.
     */
    public func write(buf: Array<Byte>): Unit {
        synchronized(ctx.writerMtx) {
            if (ctx.responded) {
                return
            }
            if (ctx.upgraded) {
                throw HttpException("The connection is upgraded and response cannot be written.")
            }
            ctx.httpConn.writeResponseByWriter(ctx, buf)
        }
    }
}