%{
#include <vector>
#include <utility>
#include <string>
#include <cstring>
#include <cstdlib>
#include <stdexcept>
#include <memory>
#include <functional>
#include <user/deviceAttachmentSpec.h>
#include <deviceManager/deviceManager.h>

#ifndef yylex
/* We use different prefixes for the lexer and parser.
 * * Our lexer's prefix is deviceAttachmentPipeSpecl.
 * * Our parser's prefix is deviceAttachmentPipeSpecp.
 *
 * Yacc and Bison don't have a way to handle the scenario where the lexer has
 * a different prefix from the parser that they generate. They assume that the
 * lexer must have the same prefix as the parser they generate. So we just use
 * this #define below to override yacc/bison's presumed prefix for the lexer.
 */
#error "Yacc should have defined yylex as a preprocessor token, and we need to override it to tell yacc the name of our lex function."
#endif
#undef yylex
#define yylex deviceAttachmentPipeSpecllex

#ifdef yytext
#undef yytext
#endif
#define yytext deviceAttachmentPipeSpecltext

// Declare the symbols that our lexer will export.
int yylex(void);
extern char* yytext; // Declare yytext to access the current token text
void yyerror(const char *message)
{
    throw std::runtime_error(
        std::string("deviceAttachmentPipeSpec parser error: ")
            + std::string(message)
            + " at token: " + std::string(yytext));
}

%}

%union {
    char* str;
    char chr;
    smo::device::DeviceAttachmentSpec* sensorSpec;
    smo::device::InteroceptorDevAttachmentSpec* interoceptorSpec;
    smo::device::ExtrospectorDevAttachmentSpec* extrospectorSpec;
    std::vector<std::pair<std::string,std::string>>* paramVector;
    std::pair<std::string,std::string>* param;
    smo::device::DapSegment* DapSpecSegment;
    std::vector<smo::device::DapSegment>* DapSegmentVector;
}

%token <str> STRING
%token PIPE DOUBLE_PIPE LPAREN RPAREN
%token <chr> KEYWORD_SPECTYPE_ACTUATOR
%token <chr> KEYWORD_SPECTYPE_EXTROSPECTOR KEYWORD_SPECTYPE_INTEROSPECTOR
%token EQUALS    // Add new token for '='

%type <paramVector> params opt_params
%type <param> param
%type <sensorSpec> spec_body
%type <interoceptorSpec> interoceptor_spec
%type <extrospectorSpec> extrospector_spec
%type <DapSpecSegment> specifier_segment
%type <DapSegmentVector> specifier_segments

%%

file: /* NOTHING */
    | sensor_specs
    ;

sensor_specs:
    sensor_spec
    | sensor_specs DOUBLE_PIPE sensor_spec
    ;

sensor_spec:
    interoceptor_spec
    | extrospector_spec
    ;

interoceptor_spec:
    KEYWORD_SPECTYPE_INTEROSPECTOR PIPE spec_body {
        auto spec = std::make_shared<smo::device::InteroceptorDevAttachmentSpec>(
            *static_cast<smo::device::InteroceptorDevAttachmentSpec *>($3));

        spec->sensorType = $1;
        smo::device::DeviceManager::getInstance().s.rsrc.commandLineDASpecs.push_back(*spec);

        delete $3;
    }
    ;

extrospector_spec:
    KEYWORD_SPECTYPE_EXTROSPECTOR PIPE spec_body {
        auto spec = std::make_shared<smo::device::ExtrospectorDevAttachmentSpec>(
            *static_cast<smo::device::ExtrospectorDevAttachmentSpec *>($3));

        spec->sensorType = $1;
        smo::device::DeviceManager::getInstance().s.rsrc.commandLineDASpecs.push_back(*spec);

        delete $3;
    }
    ;

spec_body:
    STRING PIPE specifier_segments PIPE STRING {
        auto segments = std::unique_ptr<std::vector<smo::device::DapSegment>>($3);

        if (segments->size() < 3 || segments->size() > 5)
        {
            throw std::runtime_error(
                "DAP spec for device '" + std::string($1) + "' must declare "
                "qualeIfaceApi, stimBuffApi and provider (optionally preceded "
                "by up to one postrin and one negtrin segment); got "
                + std::to_string(segments->size()) + " pre-devSelector segments.");
        }

        const size_t nIntrins = segments->size() - 3;

        $$ = new smo::device::DeviceAttachmentSpec();
        $$->deviceIdentifier = std::string($1);
        $$->sensorType = '\0';  // This will be set by the parent rule

        for (size_t i = 0; i < nIntrins; ++i)
        {
            auto& seg = (*segments)[i];
            if (seg.name == "postrin")
            {
                if (!$$->postrin.empty())
                {
                    throw std::runtime_error(
                        "DAP spec for device '" + $$->deviceIdentifier
                        + "' declares more than one postrin segment.");
                }
                $$->postrin = seg.name;
                $$->postrinParams = std::move(seg.params);
            }
            else if (seg.name == "negtrin")
            {
                if (!$$->negtrin.empty())
                {
                    throw std::runtime_error(
                        "DAP spec for device '" + $$->deviceIdentifier
                        + "' declares more than one negtrin segment.");
                }
                $$->negtrin = seg.name;
                $$->negtrinParams = std::move(seg.params);
            }
            else
            {
                throw std::runtime_error(
                    "DAP spec segment before qualeIfaceApi must be 'postrin' "
                    "or 'negtrin' (got '" + seg.name + "') for device '"
                    + $$->deviceIdentifier + "'.");
            }
        }

        auto& qSeg = (*segments)[nIntrins];
        auto& sSeg = (*segments)[nIntrins + 1];
        auto& pSeg = (*segments)[nIntrins + 2];

        $$->qualeIfaceApi = std::move(qSeg.name);
        $$->qualeIfaceApiParams = std::move(qSeg.params);
        $$->stimBuffApi = std::move(sSeg.name);
        $$->stimBuffApiParams = std::move(sSeg.params);
        $$->provider = std::move(pSeg.name);
        $$->providerParams = std::move(pSeg.params);
        $$->deviceSelector = std::string($5);
    }
    ;

specifier_segments:
    specifier_segment {
        $$ = new std::vector<smo::device::DapSegment>();
        $$->push_back(std::move(*$1));
        delete $1;
    }
    | specifier_segments PIPE specifier_segment {
        $$ = $1;
        $$->push_back(std::move(*$3));
        delete $3;
    }
    ;

specifier_segment:
    STRING LPAREN opt_params RPAREN {
        $$ = new smo::device::DapSegment();
        $$->name = std::string($1);
        $$->params = std::move(*$3);
        delete $3;
    }
    ;

opt_params:
    params
    | /* empty */ { $$ = new std::vector<std::pair<std::string,std::string>>(); }
    ;

params:
    param {
        $$ = new std::vector<std::pair<std::string,std::string>>();
        $$->push_back(*$1);
        delete $1;
    }
    | params PIPE param {
        $$ = $1;
        $$->push_back(*$3);
        delete $3;
    }
    ;

param:
    STRING {
        $$ = new std::pair<std::string,std::string>($1, "");
    }
    | STRING EQUALS {
        $$ = new std::pair<std::string,std::string>($1, "");
    }
    | STRING EQUALS STRING {
        $$ = new std::pair<std::string,std::string>($1, $3);
    }
    ;

%%
