#include "MessageParser.h"

%%{
    machine message_parser;
    include rules "Rules.rl";
    
    action mark {
        mark   = fpc - ps;
        marked = true;
    }

    action unmark {
        marked = false;
    }

    action done {
        fbreak;
    }
    
    action header_name {
        if (!headers_finished) {
            string value;
            SAVE(value);
            message->headers.add(value, {});
        }
        else {} // trailing header after chunks, currently we just ignore them
    }
    
    action header_value {
        if (!headers_finished) {
            string& value = message->headers.fields.back().value;
            SAVE(value);
            if (value && value.back() <= 0x20) value.offset(0, value.find_last_not_of(" \t") + 1);
        }
        else {} // trailing header after chunks, currently we just ignore them
    }
    
    action content_length_start {
        if (has_content_length) {
            cs = message_parser_error;
            set_error(errc::multiple_content_length);
            fbreak;
        }
        has_content_length = true;
    }

    action transfer_encoding_err {
        cs = message_parser_error;
        set_error(errc::unsupported_transfer_encoding);
        fbreak;
    }

    action unknown_content_encoding {
        if (uncompress_content) {
            cs = message_parser_error;
            set_error(errc::unsupported_compression);
            fbreak;
        }
    }

    action content_gziped {
        if (uncompress_content) {
            if (message->compression.type == Compression::IDENTITY) { message->compression.type = Compression::GZIP; }
            else {
                cs = message_parser_error;
                set_error(errc::unsupported_compression);
                fbreak;
            }
        }
    }

    action content_deflated {
        if (uncompress_content) {
            if (message->compression.type == Compression::IDENTITY) { message->compression.type = Compression::DEFLATE; }
            else {
                cs = message_parser_error;
                set_error(errc::unsupported_compression);
                fbreak;
            }
        }
    }

    action content_brotlied {
        if (uncompress_content) {
            if (message->compression.type == Compression::IDENTITY) { message->compression.type = Compression::BROTLI; }
            else {
                cs = message_parser_error;
                set_error(errc::unsupported_compression);
                fbreak;
            }
        }
    }

    action attach_compressor {
        if (message->compression.type != Compression::IDENTITY) {
            auto it = compression::instantiate(message->compression.type);
            if (it) {
                it->prepare_uncompress(max_body_size);
                compressor = std::move(it);
            } else {
                cs = message_parser_error;
                set_error(errc::unsupported_compression);
                fbreak;
            }
        }
    }

    action request_target {
        string target;
        SAVE(target);
        request->uri = new URI(target, URI::Flags::allow_extended_chars);
        if (target.length() >= 2 && target[0] == '/' && target[1] == '/') { // treat protocol-relative url as path
            proto_relative_uri = true;
        }
    }

    action push_compression {
        if (compr) {
            request->allow_compression(static_cast<Compression::Type>(compr));
            compr = 0;
        }
    }
    
    http_version  = "HTTP/1." (("0" %{message->http_version = 10;}) | ("1" %{message->http_version = 11;}));
    
    ################################## ACCEPT-ENCODING ################################
    encoding_gzip     = /gzip/     %{ compr = Compression::GZIP;     };
    encoding_deflate  = /deflate/  %{ compr = Compression::DEFLATE;  };
    encoding_br       = /br/       %{ compr = Compression::BROTLI;  };
    encoding_identity = /identity/;
    encoding_compress = /compress/;
    encoding_any      = "*" %{compr = Compression::GZIP | Compression::DEFLATE; };
    some_enconding    = encoding_identity | encoding_deflate | encoding_gzip | encoding_compress | encoding_br | encoding_any;
    q_not             = "0" ("." ("0"){,3})? %{ compr = 0; };
    q_any             = ("1" ("." ("0"){,3})?) | ("0" ("." digit{1,3})? );
    q_value           = q_any | q_not;
    encoding_value    = some_enconding (OWS ";" OWS "q=" q_value )? %push_compression;
    accept_encoding   = /Accept-Encoding/i ":" OWS encoding_value ( OWS "," OWS encoding_value )*;

    ################################## HEADERS ########################################
    field_name        = token >mark %header_name %unmark;
    field_vchar       = VCHAR | WSP | obs_text;
    field_value       = field_vchar* >mark %header_value %unmark;
    header_field      = field_name ":" OWS <: field_value;
    content_length    = /Content-Length/i ":" OWS digit+ >content_length_start ${ADD_DIGIT(content_length)} OWS;
    te_chunked        = /chunked/i %{message->chunked = true;                     };
    te_value          = te_chunked OWS;
    transfer_encoding = /Transfer-Encoding/i ":" OWS <: (te_value| (field_vchar+ - te_value) %transfer_encoding_err);
    ce_identity       = /identity/;
    ce_gzip           = /gzip/     %content_gziped;
    ce_deflate        = /deflate/  %content_deflated;
    ce_br             = /br/       %content_brotlied;
    ce_compression    = ce_identity | ce_gzip | ce_deflate | ce_br;
    ce_value          = (ce_compression (OWS "," OWS ce_compression)*) OWS;
    content_encoding  = /Content-Encoding/i ":" OWS <: (ce_value | (field_vchar+ - ce_value) %unknown_content_encoding) %attach_compressor;
    header            = content_length | transfer_encoding | content_encoding | header_field;

    ################################## CHUNKS ########################################
    chunk_size      = xdigit+ >{chunk_length = 0;} ${ADD_XDIGIT(chunk_length)};
    chunk_ext_name  = token;
    chunk_ext_val   = token | quoted_string;
    chunk_extension = ( ";" chunk_ext_name ("=" chunk_ext_val)? )+;
    _first_chunk    = chunk_size chunk_extension? CRLF;
    first_chunk    := _first_chunk @done;
    chunk          := CRLF _first_chunk @done;
    chunk_trailer  := (header_field CRLF)* CRLF @done;
    
    ################################## REQUEST ########################################
    method = "OPTIONS" %{request->method_raw(Request::Method::Options); }
           | "GET"     %{request->method_raw(Request::Method::Get);     }
           | "HEAD"    %{request->method_raw(Request::Method::Head);    }
           | "POST"    %{request->method_raw(Request::Method::Post);    }
           | "PUT"     %{request->method_raw(Request::Method::Put);     }
           | "DELETE"  %{request->method_raw(Request::Method::Delete);  }
           | "TRACE"   %{request->method_raw(Request::Method::Trace);   }
           | "CONNECT" %{request->method_raw(Request::Method::Connect); }
           ;
    request_target  = VCHAR+ >mark %request_target %unmark;
    request_line    = method SP request_target SP http_version :> CRLF;
    request_header  = accept_encoding | header;
    request_headers = (request_header CRLF)* CRLF;
    request        := request_line request_headers @done;
    
    ################################## RESPONSE ########################################
    status_code      = ([1-9] digit{2}) ${ADD_DIGIT(response->code)};
    reason_phrase    = (VCHAR | WSP | obs_text)* >mark %{SAVE(response->message)} %unmark;
    status_line      = http_version SP status_code SP reason_phrase :> CRLF;
    response_header  = header;
    response_headers = (response_header CRLF)* CRLF;
    response        := status_line response_headers @done;
}%%

namespace panda { namespace protocol { namespace http {

%% write data;

#ifdef PARSER_DEFINITIONS_ONLY
#undef PARSER_DEFINITIONS_ONLY
#else

#define ADD_DIGIT(dest) \
    dest *= 10;         \
    dest += *p - '0';
    
#define ADD_XDIGIT(dest) \
    char fc = *p | 0x20; \
    dest *= 16;          \
    dest += fc >= 'a' ? (fc - 'a' + 10) : (fc - '0');

#define SAVE(dest)                                              \
    if (mark != -1) dest = buffer.substr(mark, p - ps - mark);  \
    else {                                                      \
        dest = std::move(acc);                                  \
        dest.append(ps, p - ps);                                \
    }

size_t MessageParser::machine_exec (const string& buffer, size_t off) {
    const char* ps = buffer.data();
    const char* p  = ps + off;
    const char* pe = ps + buffer.size();
    %% write exec;
    return p - ps;
}

#endif

}}}