// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF 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.

#include "http/http_parser.h"

namespace doris {

std::ostream& operator<<(std::ostream& os, const HttpChunkParseCtx& ctx) {
    os << "HttpChunkParseCtx(" 
        << "state=" << ctx.state
        << "size=" << ctx.size
        << "length=" << ctx.length
        << ")";
    return os;
}

#define CR ((uint8_t)'\r')
#define LF ((uint8_t)'\n')

HttpParser::ParseState HttpParser::http_parse_chunked(
        const uint8_t** buf,
        const int64_t buf_len,
        HttpChunkParseCtx* ctx) {
    enum ChunkParseState {
        SW_CHUNK_START = 0,
        SW_CHUNK_SIZE,
        SW_CHUNK_EXTENSION,
        SW_CHUNK_EXTENSION_ALMOST_DONE,
        SW_CHUNK_DATA,
        SW_AFTER_DATA,
        SW_AFTER_DATA_ALMOST_DONE,
        SW_LAST_CHUNK_EXTENSION,
        SW_LAST_CHUNK_EXTENSION_ALMOST_DONE,
        SW_TRAILER,
        SW_TRAILER_ALMOST_DONE,
        SW_TRAILER_HEADER,
        SW_TRAILER_HEADER_ALMOST_DONE
    } state;

    // from last state
    state = (ChunkParseState)ctx->state;
    ParseState rc = PARSE_AGAIN;
    if (state == SW_CHUNK_DATA && ctx->size == 0) {
        state = SW_AFTER_DATA;
    }

    const uint8_t* pos = *buf;
    const uint8_t* end = *buf + buf_len;
    for (; pos < end; pos++) {
        uint8_t ch = *pos;
        uint8_t c = ch;
        bool break_loop = false;
        switch (state) {
        case SW_CHUNK_START:
            if (ch >= '0' && ch <= '9') {
                state = SW_CHUNK_SIZE;
                ctx->size = ch - '0';
                break;
            }
            c = ch | 0x20;
            if (c >= 'a' && c <= 'f') {
                state = SW_CHUNK_SIZE;
                ctx->size = c - 'a' + 10;
                break;
            }
            // Invalid state
            return PARSE_ERROR;
        case SW_CHUNK_SIZE:
            if (ch >= '0' && ch <= '9') {
                ctx->size = ctx->size * 16 + (ch - '0');
                break;
            }
            c = ch | 0x20;
            if (c >= 'a' && c <= 'f') {
                ctx->size = ctx->size * 16 + (c - 'a' + 10);
                break;
            }
            // handle last check
            if (ctx->size == 0) {
                switch (ch) {
                case CR:
                    state = SW_LAST_CHUNK_EXTENSION_ALMOST_DONE;
                    break;
                case LF:
                    state = SW_TRAILER;
                    break;
                case ';':
                case ' ':
                case '\t':
                    state = SW_LAST_CHUNK_EXTENSION;
                    break;
                default:
                    // Invalid state
                    return PARSE_ERROR;
                }
                break;
            }
            // Check if size over
            switch (ch) {
            case CR:
                state = SW_CHUNK_EXTENSION_ALMOST_DONE;
                break;
            case LF:
                state = SW_CHUNK_DATA;
                break;
            case ';':
            case ' ':
            case '\t':
                state = SW_CHUNK_EXTENSION;
                break;
            default:
                return PARSE_ERROR;
            }
            break;
        case SW_CHUNK_EXTENSION:
            switch (ch) {
            case CR:
                state = SW_CHUNK_EXTENSION_ALMOST_DONE;
                break;
            case LF:
                state = SW_CHUNK_DATA;
                break;
            default:
                // just swallow this character
                break;
            }
            break;
        case SW_CHUNK_EXTENSION_ALMOST_DONE:
            if (ch == LF) {
                state = SW_CHUNK_DATA;
                break;
            }
            return PARSE_ERROR;
        case SW_CHUNK_DATA:
            rc = PARSE_OK;
            break_loop = true;
            break;
        case SW_AFTER_DATA:
            switch (ch) {
            case CR:
                state = SW_AFTER_DATA_ALMOST_DONE;
                break;
            case LF:
                // Next chunk
                state = SW_CHUNK_START;
                break;
            default:
                // just swallow
                break;
            }
            break;
        case SW_AFTER_DATA_ALMOST_DONE:
            if (ch == LF) {
                state = SW_CHUNK_START;
                break;
            }
            return PARSE_ERROR;
        case SW_LAST_CHUNK_EXTENSION:
            switch (ch) {
            case CR:
                state = SW_LAST_CHUNK_EXTENSION_ALMOST_DONE;
                break;
            case LF:
                state = SW_TRAILER;
                break;
            default:
                // Just swallow
                break;
            }
            break;
        case SW_LAST_CHUNK_EXTENSION_ALMOST_DONE:
            if (ch == LF) {
                state = SW_TRAILER;
                break;
            }
            return PARSE_ERROR;
        case SW_TRAILER:
            switch (ch) {
            case CR:
                state = SW_TRAILER_ALMOST_DONE;
                break;
            case LF:
                // Over
                ctx->state = 0;
                *buf = pos + 1;
                return PARSE_DONE;
            default:
                state = SW_TRAILER_HEADER;
            }
            break;
        case SW_TRAILER_ALMOST_DONE:
            if (ch == LF) {
                ctx->state = 0;
                *buf = pos + 1;
                return PARSE_DONE;
            }
            return PARSE_ERROR;
        case SW_TRAILER_HEADER:
            switch (ch) {
            case CR:
                state = SW_TRAILER_HEADER_ALMOST_DONE;
                break;
            case LF:
                state = SW_TRAILER;
                break;
            default:
                break;
            }
            break;
        case SW_TRAILER_HEADER_ALMOST_DONE:
            if (ch == LF) {
                state = SW_TRAILER;
                break;
            }
            return PARSE_ERROR;
        }
        if (break_loop) {
            break;
        }
    }

    ctx->state = state;
    *buf = pos;

    // Set length have
    switch (state) {
    case SW_CHUNK_START:
        ctx->length = 3 /* "0" LF LF */;
        break;
    case SW_CHUNK_SIZE:
        ctx->length = 1 /* LF */
            + (ctx->size ? ctx->size + 4 /* LF "0" LF LF */
               : 1 /* LF */);
        break;
    case SW_CHUNK_EXTENSION:
    case SW_CHUNK_EXTENSION_ALMOST_DONE:
        ctx->length = 1 /* LF */ + ctx->size + 4 /* LF "0" LF LF */;
        break;
    case SW_CHUNK_DATA:
        ctx->length = ctx->size + 4 /* LF "0" LF LF */;
        break;
    case SW_AFTER_DATA:
    case SW_AFTER_DATA_ALMOST_DONE:
        ctx->length = 4 /* LF "0" LF LF */;
        break;
    case SW_LAST_CHUNK_EXTENSION:
    case SW_LAST_CHUNK_EXTENSION_ALMOST_DONE:
        ctx->length = 2 /* LF LF */;
        break;
    case SW_TRAILER:
    case SW_TRAILER_ALMOST_DONE:
        ctx->length = 1 /* LF */;
        break;
    case SW_TRAILER_HEADER:
    case SW_TRAILER_HEADER_ALMOST_DONE:
        ctx->length = 2 /* LF LF */;
        break;
    }

    return rc;
}

}
