%{

#include <getopt.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include "tokens.h"


/* Affiche un message d'erreur suite à l'analyse en échec. */
static int yyerror(rented_coder *, char *, char *);

/* Affiche des indications sur l'utilisation du programme. */
static void show_usage(const char *);

/* Prépare le traitement d'un contenu en l'affichant en mémoire. */
static void *map_input_data(const char *, size_t *);

%}


%code requires {

#include "coder.h"
#include "helpers.h"
#include "args/decl.h"
#include "bits/decl.h"
#include "conv/decl.h"
#include "format/decl.h"
#include "hooks/decl.h"
#include "rules/decl.h"
#include "syntax/decl.h"


#define handle_coder_format(c, r)                           \
    ({                                                      \
        encoding_spec *__spec;                              \
        operands_format *__format;                          \
        bool __status;                                      \
        __spec = get_current_encoding_spec(c);              \
        __format = get_format_in_encoding_spec(__spec);     \
        __status = load_format_from_raw_line(__format, r);  \
        if (!__status) YYABORT;                             \
    })

#define handle_coder_bits(c, e, r)                          \
    ({                                                      \
        encoding_spec *__spec;                              \
        coding_bits *__bits;                                \
        bool __status;                                      \
        __spec = get_current_encoding_spec(c);              \
        __bits = get_bits_in_encoding_spec(__spec);         \
        __status = load_bits_from_raw_line(__bits, e, r);   \
        if (!__status) YYABORT;                             \
    })

#define handle_coder_syntax(c, r)                           \
    ({                                                      \
        encoding_spec *__spec;                              \
        asm_syntax *__syntax;                               \
        bool __status;                                      \
        __spec = get_current_encoding_spec(c);              \
        __syntax = get_syntax_in_encoding_spec(__spec);     \
        __status = load_syntax_from_raw_line(__syntax, r);  \
        if (!__status) YYABORT;                             \
    })

#define handle_coder_conversions(c, r)                      \
    ({                                                      \
        encoding_spec *__spec;                              \
        conv_list *__list;                                  \
        bool __status;                                      \
        __spec = get_current_encoding_spec(c);              \
        __list = get_conversions_in_encoding_spec(__spec);  \
        __status = load_convs_from_raw_block(__list, r);    \
        if (!__status) YYABORT;                             \
    })

#define handle_coder_hooks(c, r)                            \
    ({                                                      \
        encoding_spec *__spec;                              \
        instr_hooks *__hooks;;                              \
        bool __status;                                      \
        __spec = get_current_encoding_spec(c);              \
        __hooks = get_hooks_in_encoding_spec(__spec);       \
        __status = load_hooks_from_raw_line(__hooks, r);    \
        if (!__status) YYABORT;                             \
    })

#define handle_coder_rules(c, r)                            \
    ({                                                      \
        encoding_spec *__spec;                              \
        decoding_rules *__rules;                            \
        bool __status;                                      \
        __spec = get_current_encoding_spec(c);              \
        __rules = get_rules_in_encoding_spec(__spec);       \
        __status = load_rules_from_raw_block(__rules, r);   \
        if (!__status) YYABORT;                             \
    })

}

%union {

    char character;                         /* Simple caractère isolé      */
    char *string;                           /* Chaîne de caractères #1     */
    const char *cstring;                    /* Chaîne de caractères #2     */

    int integer;                            /* Valeur entière              */

}


/**
 * Cf.
 * http://stackoverflow.com/questions/34418381/how-to-reference-lex-or-parse-parameters-in-flex-rules/34420950
 */

%define api.pure full

%parse-param { rented_coder *coder } { char *temp }
%lex-param { char *temp }

%code provides {

#define YY_DECL \
    int d2c_lex(YYSTYPE *yylvalp, char *temp)

YY_DECL;

}


%token COPYRIGHT
%token TITLE
%token INS_NAME INS_SEP INS_DETAILS
%token DESC

%token ENCODING
%token TYPE NUMBER
%token ENC_START ENC_END

%token FORMAT
%token WORD HALF
%token SYNTAX
%token CONV
%token HOOKS
%token RULES

%token RAW_LINE RAW_BLOCK


%type <string> COPYRIGHT INS_NAME
%type <character> INS_SEP
%type <cstring> INS_DETAILS

%type <string> TYPE
%type <integer> NUMBER

%type <cstring> RAW_LINE RAW_BLOCK


%%


input : name encodings { if (!dump_all_routines_using_coder(coder)) YYABORT; }
      | name desc encodings { if (!dump_all_routines_using_coder(coder)) YYABORT; }

name : COPYRIGHT TITLE INS_NAME                     { save_notes_for_coder(coder, $1, $3, '\0', NULL); }
     | COPYRIGHT TITLE INS_NAME INS_SEP INS_DETAILS { save_notes_for_coder(coder, $1, $3, $4, $5); }

desc : DESC RAW_LINE

encodings : /* empty */
          | encoding encodings

encoding : ENCODING TYPE NUMBER format_encoding { push_encoding_spec(coder, $2, $3); }
         | ENCODING format_encoding { push_encoding_spec(coder, NULL, -1); }
         | ENCODING TYPE NUMBER raw_encoding { push_encoding_spec(coder, $2, $3); }


/* Définitions à l'aide d'un format défini */

format_encoding : format format_content

format : FORMAT RAW_LINE { handle_coder_format(coder, $2); }

format_content : /* empty */
               | hooks format_content
               | rules format_content


/* Définitions à l'aide de données brutes */

raw_encoding : bitfield raw_content

raw_content : /* empty */
            | syntax raw_content
            | conversions raw_content
            | hooks raw_content
            | rules raw_content

bitfield : HALF RAW_LINE { handle_coder_bits(coder, 16, $2); }
         | WORD RAW_LINE { handle_coder_bits(coder, 32, $2); }

syntax : SYNTAX RAW_LINE { handle_coder_syntax(coder, $2); }

conversions : CONV RAW_BLOCK { handle_coder_conversions(coder, $2); }


/* Définitions communes */

hooks : HOOKS RAW_BLOCK { handle_coder_hooks(coder, $2); }

rules : RULES RAW_BLOCK { handle_coder_rules(coder, $2); }


%%


/******************************************************************************
*                                                                             *
*  Paramètres  : coder = codeur impliqué dans le processus.                   *
*                temp  = zone de travail à destination des lectures manuelles.*
*                msg   = message d'erreur.                                    *
*                                                                             *
*  Description : Affiche un message d'erreur suite à l'analyse en échec.      *
*                                                                             *
*  Retour      : 0                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static int yyerror(rented_coder *coder, char *temp, char *msg)
{
	printf("yyerror line %d: %s\n", yyget_lineno(), msg);

	return 0;

}


/******************************************************************************
*                                                                             *
*  Paramètres  : argv0 = nombre du programme exécuté.                         *
*                                                                             *
*  Description : Affiche des indications sur l'utilisation du programme.      *
*                                                                             *
*  Retour      : -                                                            *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void show_usage(const char *argv0)
{
    printf("\n");

    printf("Usage: %s [options]\n", argv0);

    printf("\n");

    printf("Options:\n");

    printf("\n");

    printf("\t-h | --help\t\t\tDisplay this messsage.\n");
    printf("\t-i | --input <file>\t\tProvide the input file containing the description.\n");
    printf("\t-t | --type <raw|format>\tSet the type of the input file.\n");
    printf("\t-d | --dir <string>\t\tSpecify the main output directory.\n");
    printf("\t-a | --arch <string>\t\tDefine the archicture to handle.\n");
    printf("\t-H | --header <string>\t\tSet the base of the #ifndef / #define game.\n");
    printf("\t-e | --encoding <none|string>\tDefine encoding prefixes for files.\n");
    printf("\t-M | --macro <string>\t\tRegister some conversion functions.\n");
    printf("\t-n | --operand <string>\t\tRegister a function producing final operands.\n");
    printf("\t-p | --prefix <string>\t\tDefine a prefix to format operand type constants (see -t).\n");

    printf("\n");

}


/******************************************************************************
*                                                                             *
*  Paramètres  : filename = chemin du fichier à charger en mémoire.           *
*                length   = taille de l'espace mémoie à mettre en place. [OUT]*
*                                                                             *
*  Description : Prépare le traitement d'un contenu en l'affichant en mémoire.*
*                                                                             *
*  Retour      : Adresse valide ou MAP_FAILED en cas d'échec.                 *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

static void *map_input_data(const char *filename, size_t *length)
{
    void *result;                           /* Espace mémoire à retourner  */
    int fd;                                 /* Descripteur du fichier      */
    struct stat info;                       /* Informations sur le fichier */
    int ret;                                /* Bilan d'un appel            */

    result = NULL;

    fd = open(filename, O_RDONLY);
    if (fd == -1)
    {
        perror("open");
        goto mid_exit;
    }

    ret = fstat(fd, &info);
    if (ret == -1)
    {
        perror("fstat");
        goto mid_exit_with_fd;
    }

    *length = info.st_size;

    result = mmap(NULL, *length, PROT_READ, MAP_PRIVATE, fd, 0);
    if (result == MAP_FAILED)
    {
        perror("mmap");
        goto mid_exit_with_fd;
    }

 mid_exit_with_fd:

    close(fd);

 mid_exit:

    return result;


}


/******************************************************************************
*                                                                             *
*  Paramètres  : argc = nombre d'arguments dans la ligne de commande.         *
*                argv = arguments de la ligne de commande.                    *
*                                                                             *
*  Description : Point d'entrée du programme.                                 *
*                                                                             *
*  Retour      : EXIT_SUCCESS si le prgm s'est déroulé sans encombres.        *
*                                                                             *
*  Remarques   : -                                                            *
*                                                                             *
******************************************************************************/

int main(int argc, char **argv)
{
    int result;                             /* Bilan à retourner           */
    rented_coder *coder;                    /* Codeur à briffer & employer */           
    int index;                              /* Indice de fichier à traiter */
    bool need_help;                         /* Affichage de l'aide ?       */
    bool has_error;                         /* Erreur dans la ligne de cmd.*/
    int ret;                                /* Bilan d'une lecture d'arg.  */
    char *sep;                              /* Caratère '=' en coupure     */
    size_t length;                          /* Nombre d'octets à traiter   */
    char *content;                          /* Contenu brut à analyser     */
    char *temp;                             /* Zone de travail temporaire  */
    YY_BUFFER_STATE state;                  /* Contexte d'analyse          */

    static struct option long_options[] = {

        { "help",       no_argument,        NULL,   'h' },
        { "input",      required_argument,  NULL,   'i' },
        { "type",       required_argument,  NULL,   't' },
        { "dir",        required_argument,  NULL,   'd' },
        { "arch",       required_argument,  NULL,   'a' },
        { "header",     required_argument,  NULL,   'H' },
        { "encoding",   required_argument,  NULL,   'e' },
        { "macro",      required_argument,  NULL,   'M' },
        { "operand",    required_argument,  NULL,   'n' },
        { "prefix",     required_argument,  NULL,   'p' },
        { NULL,         0,                  NULL,   0   }

    };

    result = EXIT_SUCCESS;

    coder = create_coder();

    index = 0;

    need_help = false;
    has_error = false;

    while (!has_error)
    {
        ret = getopt_long(argc, argv, "hi:t:d:a:H:e:M:n:p:", long_options, &index);
        if (ret == -1) break;

        switch (ret)
        {
            case 'h':
                need_help = true;
                break;

            case 'i':
                set_coder_input_file(coder, optarg);
                break;

            case 't':
                if (strcmp(optarg, "raw") == 0)
                    set_coder_input_type(coder, IOT_RAW);
                else if (strcmp(optarg, "format") == 0)
                    set_coder_input_type(coder, IOT_FORMAT);
                break;

            case 'd':
                set_coder_output_directory(coder, optarg);
                break;

            case 'a':
                set_coder_arch(coder, optarg);
                break;

            case 'H':
                set_coder_header_base(coder, optarg);
                break;

            case 'e':

                if (strcmp(optarg, "none") == 0)
                    register_empty_encoding(get_coder_pre_proc(coder));
 
                else
                {
                    sep = strchr(optarg, '=');
                    has_error = (sep == NULL);

                    if (!has_error)
                    {
                        *sep = '\0';
                        register_encoding(get_coder_pre_proc(coder), optarg, sep + 1);
                    }

                }

                break;

            case 'M':

                sep = strchr(optarg, '=');
                has_error = (sep == NULL);

                if (!has_error)
                {
                    *sep = '\0';
                    define_macro(get_coder_pre_proc(coder), optarg, sep + 1);
                }

                break;

            case 'n':
                register_as_operand_producer(get_coder_pre_proc(coder), optarg);
                break;

            case 'p':
                set_coder_const_prefix(coder, optarg);
                break;

        }

    }

    if (need_help || has_error || !do_basic_checks_with_coder(coder) || optind != argc)
    {
        show_usage(argv[0]);
        result = (need_help ? EXIT_SUCCESS : EXIT_FAILURE);
        printf("need help ? %d  -  result = %d vs %d\n", need_help, result, EXIT_SUCCESS);
        goto exit;
    }

    content = map_input_data(get_coder_input_file(coder), &length);
    if (content == MAP_FAILED)
    {
        result = EXIT_FAILURE;
        goto exit;
    }

    temp = (char *)calloc(length, sizeof(char));

    state = d2c__scan_bytes(content, length);

    result = yyparse(coder, temp);

    yy_delete_buffer(state);

    free(temp);

    munmap(content, length);

 exit:

    delete_coder(coder);

    return result;

}