%{

#include <map>
#include <algorithm>

#include "AppHdr.h"
#include "l_defs.h"
#include "libutil.h"
#include "mapdef.h"
#include "maps.h"
#include "stuff.h"

#define YYERROR_VERBOSE 1

int yylex();

extern int yylineno;

void yyerror(const char *e)
{
    if (strstr(e, lc_desfile.c_str()) == e)
        fprintf(stderr, "%s\n", e);
    else
        fprintf(stderr, "%s:%d: %s\n", lc_desfile.c_str(), yylineno, e);
    // Bail bail bail.
    end(1);
}

level_range set_range(const char *s, int start, int end)
{
    try
    {
        lc_range.set(s, start, end);
    }
    catch (const std::string &err)
    {
        yyerror(err.c_str());
    }
    return (lc_range);
}

%}

%union
{
    int i;
    const char *text;
    raw_range range;
}

/* Two harmless shift/reduce conflicts */
%expect 2

%token <i>      DEFAULT_DEPTH SHUFFLE SUBST TAGS KFEAT KITEM KMONS KMASK KPROP
%token <i>      NAME DEPTH ORIENT PLACE CHANCE WEIGHT MONS ITEM MARKER COLOUR
%token <i>      PRELUDE MAIN VALIDATE VETO NSUBST WELCOME LFLAGS BFLAGS
%token <i>      LFLOORCOL LROCKCOL LFLOORTILE LROCKTILE FTILE RTILE

%token <i>      COMMA COLON PERC INTEGER CHARACTER

%token <text>   STRING MAP_LINE MONSTER_NAME ITEM_INFO
%token <text>   LUA_LINE

%%

file            : definitions { }
                ;

definitions     : /* empty */               {}
                | definitions definition    {}
                ;

definition      : def       {}
                | level     {}
                ;

error_seq       : CHARACTER
                {
                    yyerror("Unexpected character sequence.");
                }
                ;

def             : defdepth  {}
                | global_lua  {}
                ;

defdepth        : DEFAULT_DEPTH STRING
                {
                    dgn_reset_default_depth();
                    std::string err = dgn_set_default_depth($2);
                    if (!err.empty())
                        yyerror(make_stringf("Bad default-depth: %s (%s)",
                                $2, err.c_str()).c_str());
                }
                ;

level           : name map_specs
                {
                    lc_map.set_file(lc_desfile);

                    if (lc_run_global_prelude && !lc_global_prelude.empty())
                    {
                        lc_global_prelude.set_file(lc_desfile);
                        lc_run_global_prelude = false;
                        if (lc_global_prelude.load_call(dlua, NULL))
                            yyerror( lc_global_prelude.orig_error().c_str() );
                    }

                    std::string err = lc_map.validate_map_def();
                    if (!err.empty())
                        yyerror(err.c_str());
                    if (!lc_map.has_depth() && !lc_default_depths.empty())
                        lc_map.add_depths(lc_default_depths.begin(),
                                          lc_default_depths.end());
                    add_parsed_map(lc_map);
                }
                ;

map_specs       : /* nothing */      { }
                | map_specs map_spec { }
                ;

map_spec        : metaline { }
                | map_def  { }
                ;

name            : NAME STRING
                {
                    lc_map.init();
                    lc_map.name = $2;

                    map_load_info_t::const_iterator i =
                        lc_loaded_maps.find($2);

                    if (i != lc_loaded_maps.end())
                    {
                        yyerror(
                            make_stringf(
                                "Map named '%s' already loaded at %s:%d",
                                $2,
                                i->second.filename.c_str(),
                                i->second.lineno).c_str() );
                    }

                    lc_map.place_loaded_from =
                        map_file_place(lc_desfile, yylineno);
                    lc_loaded_maps[$2] = lc_map.place_loaded_from;
                }
                ;

metaline        : place
                | depth
                | chance
                | weight
                | orientation
                | welcome
                | mons
                | items
                | marker
                | subst
                | nsubst
                | colour
                | lfloorcol
                | lrockcol
                | lfloortile
                | lrocktile
                | ftile
                | rtile
                | shuffle
                | tags
                | lflags
                | bflags
                | kfeat
                | kitem
                | kmons
                | kmask
                | kprop
                | main_lua
                | prelude_lua
                | validate_lua
                | veto_lua
                | error_seq {}
                ;

global_lua      : MAIN global_lua_lines { }

global_lua_lines  : /* empty */ { }
                | global_lua_lines global_lua_line { }
                ;

global_lua_line   : LUA_LINE
                {
                    lc_global_prelude.add(yylineno, $1);
                }

main_lua        : MAIN main_lua_lines { }

main_lua_lines  : /* empty */ { }
                | main_lua_lines main_lua_line { }
                ;

main_lua_line   : LUA_LINE
                {
                    lc_map.main.add(yylineno, $1);
                }

validate_lua    : VALIDATE validate_lua_lines { }

validate_lua_lines  : /* empty */ { }
                | validate_lua_lines validate_lua_line { }
                ;

validate_lua_line   : LUA_LINE
                {
                    lc_map.validate.add(yylineno, $1);
                }

veto_lua        : VETO veto_lua_lines { }

veto_lua_lines  : /* empty */ { }
                | veto_lua_lines veto_lua_line { }
                ;

veto_lua_line   : LUA_LINE
                {
                    lc_map.veto.add(yylineno, $1);
                }

prelude_lua     : PRELUDE prelude_lua_lines { }

prelude_lua_lines : /* empty */ { }
                | prelude_lua_lines prelude_lua_line { }
                ;

prelude_lua_line : LUA_LINE
                {
                    lc_map.prelude.add(yylineno, $1);
                }

kfeat           : KFEAT { }
                | KFEAT STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kfeat(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kmons           : KMONS { }
                | KMONS STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kmons(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kitem           : KITEM { }
                | KITEM STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kitem(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kmask           : KMASK { }
                | KMASK STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kmask(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kprop           : KPROP { }
                | KPROP STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kprop(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

shuffle         : SHUFFLE shuffle_specifiers {}
                ;

shuffle_specifiers : shuffle_spec
                   | shuffle_specifiers COMMA shuffle_spec
                   ;

shuffle_spec    : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("shuffle(\"%s\")",
                            quote_lua_string($1).c_str()));
                }

tags            : TAGS tagstrings {}
                ;

tagstrings      : /* empty */
                | tagstrings tagstring
                ;

tagstring       : STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("tags(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

lflags          : LFLAGS STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lflags(\"%s\")",
                                     quote_lua_string($2).c_str()));
                }
                ;

bflags          : BFLAGS STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("bflags(\"%s\")",
                                     quote_lua_string($2).c_str()));
                }
                ;

marker          : MARKER STRING
                {
                    std::string key, arg;
                    int sep(0);

                    const std::string err =
                      mapdef_split_key_item($2, &key, &sep, &arg);

                    if (!err.empty())
                      yyerror(err.c_str());

                    // Special treatment for Lua markers.
                    if (arg.find("lua:") == 0)
                    {
                       arg = arg.substr(4);
                       lc_map.main.add(
                         yylineno,
                         make_stringf("lua_marker(\"%s\", function () "
                                      "  return %s "
                                      "end)",
                                      quote_lua_string(key).c_str(),
                                      arg.c_str()));
                    }
                    else
                    {
                       lc_map.main.add(
                         yylineno,
                         make_stringf("marker(\"%s\")",
                                      quote_lua_string($2).c_str()));
                    }
                }
                ;

colour          : COLOUR colour_specifiers { }
                ;

lfloorcol       : LFLOORCOL { }
                | LFLOORCOL STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lfloorcol(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

lrockcol        : LROCKCOL { }
                | LROCKCOL STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lrockcol(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

lfloortile      : LFLOORTILE { }
                | LFLOORTILE STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lfloortile(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

lrocktile       : LROCKTILE { }
                | LROCKTILE STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lrocktile(\"%s\")",
                            quote_lua_string($2).c_str()));
                }


ftile           : FTILE ftile_specifiers
                ;

ftile_specifiers: ftile_specifier
                | ftile_specifiers COMMA ftile_specifier
                ;

ftile_specifier : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("ftile(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

rtile           : RTILE rtile_specifiers
                ;

rtile_specifiers: rtile_specifier
                | rtile_specifiers COMMA rtile_specifier 
                ;

rtile_specifier : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("rtile(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

colour_specifiers : colour_specifier  { }
                  | colour_specifiers COMMA colour_specifier { }
                  ;

colour_specifier  : ITEM_INFO
                  {
                      lc_map.main.add(
                          yylineno,
                          make_stringf("colour(\"%s\")",
                              quote_lua_string($1).c_str()));
                  }
                  ;

nsubst          : NSUBST nsubst_specifiers { }
                ;

nsubst_specifiers : nsubst_spec  { }
                  | nsubst_specifiers COMMA nsubst_spec { }
                  ;

nsubst_spec     : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("nsubst(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

subst           : SUBST subst_specifiers { }
                ;

subst_specifiers : subst_spec
                 | subst_spec COMMA subst_specifiers
                 ;

subst_spec      : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("subst(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

items           : ITEM {}
                | ITEM item_specifiers {}
                ;

item_specifiers : item_specifiers COMMA item_specifier
                | item_specifier
                ;

item_specifier  : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("item(\"%s\")",
                            quote_lua_string($1).c_str()));
                }

mons            : MONS {}
                | MONS mnames {}
                ;

mnames          : mname COMMA mnames
                | mname
                ;

mname           : MONSTER_NAME
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("mons(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

place           : PLACE STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("place(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

depth           : DEPTH {}
                | DEPTH STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("depth(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

chance          : CHANCE INTEGER COLON INTEGER PERC
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("chance(%d, %d)", $2, $4 * 100));
                }
                |
                CHANCE INTEGER COLON INTEGER
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("chance(%d, %d)", $2, $4));
                }
                |
                CHANCE INTEGER PERC
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("chance(0, %d)", $2 * 100));
                }
                |
                CHANCE INTEGER
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("chance(0, %d)", $2));
                }
                ;

weight          : WEIGHT INTEGER
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("weight(%d)", $2));
                }
                ;

orientation     : ORIENT {}
                | ORIENT STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("orient(\"%s\")",
                                     quote_lua_string($2).c_str()));
                }
                ;

welcome         : WELCOME STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("welcome(\"%s\")",
                                     quote_lua_string($2).c_str()));
                }
                ;

map_def         : map_lines
                ;

map_lines       : map_lines map_line
                | map_line
                ;

map_line        : MAP_LINE
                {
                    lc_map.mapchunk.add(
                        yylineno,
                        make_stringf("map(\"%s\")",
                                     quote_lua_string($1).c_str()));
                }
                ;

%%