/*
 * CSV scanner, for tests.
 *
 * The structure of this program is tightly coupled to the the set of
 * tests in src/grammar/testcases.csv, and you should consult the
 * documentation at the top of that file to understand what this
 * program is trying to do.
 *
 * Note that the testcases.csv file must be run through the stripat
 * program, to generate the per-syntax tests which are the actual
 * input to this file.  See the testcases-%.csv target in
 * src/grammar/Makefile for an indication of how to do that.
 */

%{
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "../unity.h"

#define TOKEN_NEWLINE 1
#define TOKEN_COMMA 2
#define TOKEN_STRING 3
static char* lval = "";

%}

%option noyywrap
%option nounput
%option prefix="testyy"

WHITESPACE	[ \t]
NEWLINECHAR	[\n\r]
NONCOMMA	[^,#\n\r]
%%

{NEWLINECHAR}			{ return TOKEN_NEWLINE; }
#.*				{ return TOKEN_NEWLINE; }
{WHITESPACE}*,{WHITESPACE}*	{ return TOKEN_COMMA; }
{NONCOMMA}+			{ lval = testyytext; return TOKEN_STRING; }
     
%%

// generous buffer size
#define BUFLEN 1024

void Usage(void);

const char* progname;

int main(int argc, char** argv) 
{
    int token;
    const char* filename;
    FILE* csv;
    char* input = NULL;
    const UnitExpression *unit_expr = 0;
    char should_fail_p = 0;     /* should_fail_p true if this test-case should fail */
    char should_fail_msg[BUFLEN];
    int ngood = 0;
    int nbad = 0;
    char actual_parse_result[BUFLEN];
    char expected[BUFLEN];
    int column_number;
    const char* parser_name = "fits";
    char expected_formats_list[BUFLEN+1];
    char* expected_formats_p = NULL;
    int expected_formats_len = 0;
    int expected_status;
    int parser_id;

    progname = argv[0];
    {
        int ch;
        while ((ch = getopt(argc, argv, "p:")) >= 0) {
            switch (ch) {
              case 'p':
                parser_name = optarg; /* can I store this pointer? */
                break;
              default:
                Usage();
                break;
            }
        }
        argc -= optind;
        argv += optind;
    }
    if (argc != 1) {
        Usage();
    }

    filename = argv[0];

    if ((csv = fopen(filename, "r")) == NULL) {
        fprintf(stderr, "%s: can't open file %s to read\n", progname, filename);
        exit(1);
    }

    testyyin = csv;
    column_number = 0;
    should_fail_p = 0;
    expected_formats_p = NULL;
    memset((void*)should_fail_msg, 0, BUFLEN);

    // expected_status indicates further tests:
    //   no further tests:
#   define EXPECTED_NONE 0
    //   includes unrecognised units:
#   define EXPECTED_UNRECOGNISED 1
    //   all units recognised, but at least one deprecated:
#   define EXPECTED_DEPRECATED 2
    //   uses SI prefixes where forbidden:
#   define EXPECTED_SI_PREFIXES 3
    //   uses binary prefixes where forbidden:
#   define EXPECTED_BINARY_PREFIXES 4
    //   all constraints satisfied:
#   define EXPECTED_ALL_SATISFIED 5
    expected_status = EXPECTED_NONE;
    
    parser_id = unity_identify_parser(parser_name);
    if (parser_id < 0) {
        fprintf(stderr, "Unidentified parser: %s\n", parser_name);
        exit(1);
    }

    while ((token = testyylex()) != 0) {

        switch (token) {
          case TOKEN_NEWLINE:
            if (column_number == 0) {
                // empty line
                break;
            }
            if (unit_expr != NULL) {
                unity_write_formatted(actual_parse_result, BUFLEN, unit_expr, UNITY_SYNTAX_DEBUG);
            }
            if (should_fail_p) {
                // this shouldn't have parsed
                if (unit_expr == NULL) {
                    ngood++;
                } else {
                    printf("Parsing (%s): %s    Should have failed (%s), but produced: %s\n",
                           parser_name, input, should_fail_msg, actual_parse_result);
                    nbad++;
                }
            } else {
                // this should have parsed
                if (unit_expr == NULL) {
                    // didn't parse
                    printf("Parsing (%s): %s    Failed to parse\n", parser_name, input);
                    nbad++;
                } else if (strcmp(expected, actual_parse_result) == 0) {
                    // this test passed
                    ngood++;
                    switch (expected_status) {
                      case EXPECTED_NONE:
                        break;  /* no other checks required */
                      case EXPECTED_UNRECOGNISED:
                        if (unity_check_expression(unit_expr, parser_id, UNITY_CHECK_RECOGNISED)) {
                            printf("Parsing (%s): %s    has non-recognised units; didn't spot them!\n",
                                   parser_name, input);
                            nbad++;
                        } else {
                            ngood++;
                        }
                        break;
                      case EXPECTED_DEPRECATED:
                        // Check that the units in the expression are all recognised,
                        // and that none are recommended (this test only really makes
                        // sense if the 'expression' is a single unit, which we are
                        // checking to be recognised as deprecated).
                        if (unity_check_expression(unit_expr, parser_id, UNITY_CHECK_RECOGNISED)
                            && !unity_check_expression(unit_expr, parser_id, UNITY_CHECK_RECOMMENDED)) {
                            ngood++;
                        } else {
                            printf("Parsing (%s): %s    has deprecated units; didn't spot them! (%s recognised)\n",
                                   parser_name, input,
                                   (unity_check_expression(unit_expr, parser_id, UNITY_CHECK_RECOGNISED) ? "are" : "not"));
                            nbad++;
                        }                        
                        break;
                      case EXPECTED_SI_PREFIXES:
                      case EXPECTED_BINARY_PREFIXES:
                        if (unity_check_expression(unit_expr, parser_id, UNITY_CHECK_CONSTRAINTS)) {
                            printf("Parsing (%s): %s    has non-conformant units; didn't spot them!\n",
                                   parser_name, input);
                            nbad++;
                        } else {
                            ngood++;
                        }
                        break;
                      case EXPECTED_ALL_SATISFIED:
                        if (! unity_check_expression(unit_expr, parser_id, UNITY_CHECK_ALL)) {
                            printf("Parsing (%s): %s    should have passed all tests, but didn't!\n",
                                   parser_name, input);
                            nbad++;
                        } else {
                            ngood++;
                        }
                        break;
                      default:
                        fprintf(stderr, "Impossible expected_status=%d\n", expected_status);
                        exit(1);
                    }
                } else {
                    // parsed, but didn't produce expected output
                    printf("Parsing (%s): %s    expected: %s\n    actual:   %s\n",
                           parser_name, input, expected, actual_parse_result);
                    nbad++;
                }
            }
            if (unit_expr && expected_formats_p) {
                // check this parsed expression formats OK
                char actual_format[BUFLEN];
                char* syntax_s;

                for (syntax_s = strtok(expected_formats_list, ":");
                     syntax_s;
                     syntax_s = strtok(NULL, ":")) {

                    char* expected_format = strtok(NULL, ":");
                    UnitySyntax syntax = unity_identify_parser(syntax_s);

                    if (expected_format == NULL) {
                        fprintf(stderr, "Malformed test file: expected formats unbalanced\n");
                        exit(1);
                    } else if (syntax == UNITY_SYNTAX_NONE) {
                        fprintf(stderr, "Unidentifiable syntax: %s\n", syntax_s);
                        exit(1);
                    }
                    unity_write_formatted(actual_format, BUFLEN, unit_expr, syntax);
                    if (*expected_format == '!') {
                        // this is "syntax:!teststring" -- we should
                        // check that the formatted version matches
                        // this, but we shouldn't try to re-parse it
                        // (we don't try the re-parse in this test
                        // harness anyway)
                        expected_format++;
                    }
                    
                    if (strcmp(actual_format, expected_format) == 0) {
                        ngood++;
                    } else {
                        printf("Formatting (%s): expected: %s\n    actual: %s\n",
                               syntax_s, expected_format, actual_format);
                        nbad++;
                    }

                    // We should try re-parsing the 'expected_format',
                    // in order to check that the writer has produced
                    // only valid string, but we don't, at present.
                }
            }
            // all done!

            if (unit_expr != NULL) {
                unity_free_expression(unit_expr);
                unit_expr = NULL;
            }
            assert(unit_expr == NULL);

            // prepare for next time
            column_number = 0; // reset column number, so we start another check
            should_fail_p = 0; // reset to default false (if there's no second field)
            expected_formats_p = NULL;
            break;

          case TOKEN_COMMA:
            column_number++;
            break;
            
          case TOKEN_STRING: 
            switch (column_number) {
              case 0:
                // First item on CSV line
                input = lval;
                unit_expr = unity_parse_string(lval, parser_id);
                //fprintf(stderr, "parsing <%s>...\n", lval);
                break;
              case 1:
                // Non-empty second column -- this input should fail to parse.
                // Copy the lval to the should_fail (explanation) buffer.
                should_fail_p = 1;
                strncpy(should_fail_msg, lval, BUFLEN-1);
                break;
              case 2:
                if (isdigit(lval[0]) || lval[0] == '-') {
                    // OK
                    expected_status = EXPECTED_NONE;
                    strncpy(expected, lval, BUFLEN-1);
                } else {
                    strncpy(expected, &lval[1], BUFLEN-2);
                    switch (lval[0]) {
                      case 'u':
                        expected_status = EXPECTED_UNRECOGNISED;
                        break;
                      case '?':
                        expected_status = EXPECTED_DEPRECATED;
                        break;
                      case 's':
                        expected_status = EXPECTED_SI_PREFIXES;
                        break;
                      case 'b':
                        expected_status = EXPECTED_BINARY_PREFIXES;
                        break;
                      case '!':
                        expected_status = EXPECTED_ALL_SATISFIED;
                        break;
                      default:
                        fprintf(stderr, "Malformed test: unrecognised flag in %s", lval);
                        exit(1);
                    }
                }
                break;
              case 3:
                expected_formats_p = expected_formats_list;
                expected_formats_len = BUFLEN;
                strncpy(expected_formats_p, lval, expected_formats_len);
                expected_formats_p += strlen(lval);
                expected_formats_len -= strlen(lval);
                break;
              default:
                *expected_formats_p++ = ':';
                expected_formats_len--;
                strncpy(expected_formats_p, lval, expected_formats_len);
                expected_formats_p += strlen(lval);
                expected_formats_len -= strlen(lval);
                break;
            }
            break;

          default:
            fprintf(stderr, "Impossible token: %d\n", token);
            break;
        }
    }

    if (nbad == 0) {
        printf("%8s : ALL %d tests pass\n", parser_name, ngood);
    } else {
        printf("%8s : Passes: %d\n         FAILS:  %d\n", parser_name, ngood, nbad);
    }

    return 0;
}

void Usage(void) 
{
    fprintf(stderr, "Usage: %s [-p parser] csv-file\n", progname);
    exit(1);
}

