#pragma once
#include "MessageParser.h"

#define PARSER_DEFINITIONS_ONLY
#include "MessageParser.cc"

namespace panda { namespace protocol { namespace http {

#define RETURN_IF_PARSE_ERROR do if (cs == message_parser_error) {  \
    if (!error) set_error(errc::lexical_error);                     \
    return pos;                                                     \
} while (0)

#define RETURN_IF_INCOMPLETE do if (cs < message_parser_first_final) {  \
    if (marked) {                                                       \
        if (mark != -1) {                                               \
            acc = buffer.substr(mark, len - mark);                      \
            mark = -1;                                                  \
        }                                                               \
        else acc += buffer;                                             \
    }                                                                   \
    return pos;                                                         \
} while (0)

#define RETURN_IF_MAX_BODY_SIZE(current_size) do if (current_size > max_body_size) {    \
    set_error(max_body_size ? errc::body_too_large : errc::unexpected_body);            \
    return pos;                                                                         \
} while (0)

template <class F1, class F2>
size_t MessageParser::parse (const string& buffer, F1&& after_headers_cb, F2&& no_body_cb) {
    auto   len = buffer.length();
    size_t pos = 0;
    //printf("parse: %s\n", buffer.c_str());

    while (pos != len) switch (state) {
        case State::headers: {
            //printf("headers\n");
            pos = machine_exec(buffer, pos);
            RETURN_IF_PARSE_ERROR;

            headers_so_far += pos;
            if (headers_so_far > max_headers_size) {
                set_error(errc::headers_too_large);
                return pos;
            }
            
            RETURN_IF_INCOMPLETE;
            
            headers_finished = true;
            if (!after_headers_cb()) return pos;

            if (message->chunked) {
                state = State::chunk;
                cs = message_parser_en_first_chunk;
            }
            else if (has_content_length) {
                if (content_length > 0) {
                    state = State::body;
                    RETURN_IF_MAX_BODY_SIZE(content_length);
                } else {
                    state = State::done;
                    return pos;
                }
            }
            else if (!no_body_cb()) return pos;
            
            continue;
        }
        case State::body: {
            //printf("body\n");
            auto have = len - pos;
            size_t consumed;

            /* 1. determine how much it can be consumed */
            if (content_length) {
                auto left = content_length - body_so_far;
                if (have >= left) { consumed = left; state = State::done; }
                else              { consumed = have;                      }
            } else {
                consumed = have;
            }

            /* 2. try to consume the available bytes */
            string piece = buffer.substr(pos, consumed);
            body_so_far += have;
            if (compressor) {
                auto append_err = compressor->uncompress(piece, message->body);
                if (append_err) { set_error(append_err); return pos; }
            }
            else {
                if (!content_length) { RETURN_IF_MAX_BODY_SIZE(body_so_far); }
                message->body.parts.push_back(piece);
            }

            return pos + consumed;
        }
        case State::chunk: {
            //printf("chunk. rest: %s\n", buffer.substr(pos).c_str());
            pos = machine_exec(buffer, pos);
            RETURN_IF_PARSE_ERROR;
            RETURN_IF_INCOMPLETE;

            if (!chunk_length) { // final chunk
                state = State::chunk_trailer;
                cs = message_parser_en_chunk_trailer;
                continue;
            }
            //printf("chunk len = %llu\n", chunk_length);

            body_so_far += chunk_length;
            RETURN_IF_MAX_BODY_SIZE(body_so_far);

            chunk_so_far = 0;
            state = State::chunk_body;
            continue;
        }
        case State::chunk_body: {
            //printf("chunk body\n");
            auto left = chunk_length - chunk_so_far;
            auto have = len - pos;

            bool final = false;
            size_t consumed;
            if (have >= left) {
                consumed = left;
                final = true;
            } else {
                consumed = have;
            }
            chunk_so_far += consumed;

            auto piece = buffer.substr(pos, consumed);
            if (compressor) {
                auto append_err = compressor->uncompress(piece, message->body);
                if (append_err) { set_error(append_err); return pos; }
            }
            else {
                message->body.parts.push_back(piece);
            }

            if (final) {
                pos += left;
                state = State::chunk;
                cs = message_parser_en_chunk;
                continue;
            } else {
                return len;
            }

        }
        case State::chunk_trailer: {
            //printf("chjunk trailer\n");
            pos = machine_exec(buffer, pos);
            RETURN_IF_PARSE_ERROR;
            RETURN_IF_INCOMPLETE;
            state = State::done;
            return pos;
        }
        default: abort();
    }
    
    return len;
}

}}}
