#include "DateRel.h"

%%{
    machine daterel_parser;
    
    action sign { sign = *p; }
    
    action digit {
        acc *= 10;
        acc += fc - '0';
    }
    
    action number {
        if (sign == '-') {
            acc = -acc;
            sign = 0;
        }
    }

    action sec   { NSAVE(_sec); }
    action min   { NSAVE(_min); }
    action hour  { NSAVE(_hour); }
    action day   { NSAVE(_day); }
    action month { NSAVE(_month); }
    action year  { NSAVE(_year); }
    
    action week {
        _day += acc*7;
        acc = 0;
    }
    
    number = "-"? $sign digit+ $digit %number;

    simple_part = (number [Yy] %year) | (number "M" %month) | (number [Dd] %day) | (number [Ww] %week) | (number "h" %hour) | (number "m" %min) | (number "s" %sec);
    simple = (simple_part (space+ simple_part)*) %{ format |= InputFormat::simple; };
    
    iso8601_duration = (
        "P" (number "Y" %year)? (number "M" %month)? (number "D" %day)? (number "W" %week)? ("T" (number "H" %hour)? (number "M" %min)? (number "S" %sec)?)?
    ) %{ format |= InputFormat::iso8601; };

    main := simple | iso8601_duration;
}%%

namespace panda { namespace date {

%% write data;

#define NSAVE(dest) { dest += acc; acc = 0; }
        
errc DateRel::parse (string_view str, int available_formats) {
    _year = _month = _day = _hour = _min = _sec = 0;
    int         cs     = daterel_parser_start;
    int64_t     acc    = 0;
    char        sign   = 0;
    const char* p      = str.data();
    const char* pe     = p + str.length();
    const char* eof    = pe;
    int         format = 0;
    
    %% write exec;
    
    if (cs < daterel_parser_first_final && (available_formats & InputFormat::iso8601i)) {
        _year = _month = _day = _hour = _min = _sec = 0;
        // ISO8601 interval format: "iso8601_date/iso8601_relative"
        auto pos = str.find('/');
        if (pos == string::npos) return errc::parser_error;
        format = InputFormat::iso8601i;
        
        _from = Date(str.substr(0, pos), {}, Date::InputFormat::iso8601);
        if (_from->error()) return errc::parser_error;

        return parse(str.substr(pos+1), InputFormat::iso8601d);
    }
    
    if (!(format & available_formats)) {
        _year = _month = _day = _hour = _min = _sec = 0;
        return errc::parser_error;
    }
    
    return errc::ok;
}

}}