/* 'checkit_tiff' is a conformance checker for baseline TIFFs
 *
 * author: Andreas Romeyke, 2015-2022
 * licensed under conditions of libtiff
 * (see http://libtiff.maptools.org/misc.html)
 *
 */

/* #define YY_DEBUG */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "config_parser.h"
#include "check_helper.h"
#include "msg_parser.h"
#include "simple_string.h"

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h> /* lib pcre2, see http://www.pcre.org/ */
#include <libgen.h>

void print_st (int i, internal_stack_entry_t  v) {
    switch (v.type) {
        case st_unsigned_int:
            printf("[%i] uint (%u)\n", i, v.value.i);
            break;
        case st_regex:
            printf("[%i] regex (%s)\n", i, v.value.regex);
            break;
/*
        case st_val:
            printf("[%i] val (%i)\n", i,v.value.val);
        break;
*/
        case st_LOW_GUARD:
            printf("[%i] LOW GUARD!\n", i);
            break;
        case st_HIGH_GUARD:
            printf("[%i] HIGH_GUARD!\n", i);
    }
}

void print_res(int i, const full_res_t full_result) {
    printf ("returncode = %i\n", full_result.returncode);
    printf("i=%i lineno=%i tag=%hu func=%s (%i) returncode=%s (%i)\n", i,
           full_result.lineno,
           full_result.tag,
           get_parser_function_description(full_result.function),
           full_result.function,
           get_parser_error_description(full_result.returncode),
           full_result.returncode
    );
}

void print_exe(int i, const exe_entry_t exe) {
    printf("/* lineno=%03i */ ", exe.lineno);
    if (exe.is_precondition) { printf("PRECOND: "); }
    else { printf("EXEC:    "); }
    printf("%s tag=%i", get_parser_function_name(exe.function),
           exe.tag);
    if (!exe.is_precondition) { printf("\n"); }
    printf("\n");
}

void print_val(int i, values_t v) {
    printf("[%i] val (%i)\n", i,v);
}

#define YY_CTX_LOCAL

/*
#define RULE_DEBUG 1
#define DEBUG 1
#define EXE_DEBUG 1
#define YY_DEBUG 1
*/

/* global vars */
static parser_state_t parser_state;

/* redefined YY_INPUT to read from stream */
void
summarize_results(size_t count_of_all_results, size_t count_of_valid_results, retmsg_t **actual_render, ret_t *res);

#define YY_INPUT(yyctx, buf, result, max_size)		\
  {							\
    int yyc= fgetc(parser_state.stream);		\
    (result)= (EOF == yyc) ? 0 : (*(buf)= yyc, 1);	\
    yyprintf((stderr, "<%c>", yyc));			\
  }


/* prints a plan (list) of functions */
/*
void print_plan () {
  printf("print plan:\n");
  executionentry_t * this_exe_p = plan.start;
  while (NULL != this_exe_p) {
    printf("next action is: %s\n", this_exe_p->name);
    this_exe_p = this_exe_p->next;
  }
}
*/

static void result_push(const full_res_t r) {
    full_res_t_push( parser_state.result_stack, r);
}

static void result_push_back(const full_res_t r) {
    full_res_t_push_back( parser_state.result_stack, r);
}
static void result_printstack(void) {
  printf("=== BEGIN result_printstack\n");
  full_res_t_print_top(parser_state.result_stack);
  printf("=== END result_printstack\n");
}

static void i_push (unsigned int i) {
#ifdef RULE_DEBUG
    printf("i_push, %u\n", i);
#endif
    internal_stack_entry_t tmp;
    tmp.type=st_unsigned_int;
    tmp.value.i = i;
    internal_stack_entry_t_push(parser_state.intermediate_stack, tmp);
}
static unsigned int i_pop (void) {
    internal_stack_entry_t tmp = internal_stack_entry_t_pop(parser_state.intermediate_stack);
    assert(tmp.type == st_unsigned_int);
    return tmp.value.i;
}
static void v_push ( values_t v) {
#ifdef RULE_DEBUG
    printf("v_push, %i\n", v);
#endif
    values_t_push(parser_state.values_stack, v);
}

static values_t v_pop (void) {
    return values_t_pop(parser_state.values_stack);
}

static void r_push (const char * r) {
#ifdef RULE_DEBUG
    printf("r_push, %s\n", r);
#endif
    internal_stack_entry_t tmp;
    tmp.type=st_regex;
    tmp.value.regex = r;
    internal_stack_entry_t_push(parser_state.intermediate_stack, tmp);
}

static const char * r_pop (void) {
    internal_stack_entry_t tmp = internal_stack_entry_t_pop(parser_state.intermediate_stack);
    assert(tmp.type == st_regex);
    return tmp.value.regex;
}

static void exe_push (const exe_entry_t i) { exe_entry_t_push( parser_state.exe_stack, i);}
static exe_entry_t exe_pop (void) { return exe_entry_t_pop( parser_state.exe_stack); }

void exe_printstack_human_readable (void) {
    printf("\n/* the rules are in stack order, the top comes first */\n\n");
    exe_entry_t_print_top(parser_state.exe_stack);
}

/*  reduce results */
static void reduce_results(void) {
    full_res_t_stack_t * temporary_results = full_res_t_new();
    //full_res_t_init(temporary_results, __FILE__, __LINE__);
#ifdef DEBUG
    full_res_t_print_top( parser_state.result_stack);
#endif
    while (!full_res_t_is_empty(parser_state.result_stack)) {
        full_res_t v = full_res_t_pop(parser_state.result_stack);
        if (v.returncode == parser_logical_combine_close) {
            /* check all values until parser_logical_close found */
            bool one_result_is_ok = false;
            char * found_fails = calloc(512,sizeof(char));
            while (!full_res_t_is_empty(parser_state.result_stack)) {
                full_res_t tmp = full_res_t_pop(parser_state.result_stack);
                if (tmp.returncode == parser_logical_combine_open) {
                    /* push combined result to temporary_results */
                    if (one_result_is_ok == true) {
                        tmp.returncode = is_valid;
                        free( found_fails);
                        found_fails = NULL;
                    } else {
                        tmp.found_value = str(found_fails);
                    }
                    full_res_t_push(temporary_results, tmp);
                    //full_res_t_push(&temporary_results, tmp);
                    break; /* exit inner loop */
                } else {
                    if (tmp.returncode != is_valid) {
                        found_fails = strncat( found_fails, tmp.found_value.string, tmp.found_value.len);
                        found_fails = strcat( found_fails, " ");
                    } else {
                        one_result_is_ok = true;
                    }
                }
            }
        } else {
            full_res_t_push(temporary_results, v);
        }
    }
    /* now go through temporary results and push them back to result stack */
    assert( full_res_t_is_empty(parser_state.result_stack) );
#ifdef DEBUG
    full_res_t_print_top( temporary_results);
#endif
    while (!full_res_t_is_empty(temporary_results)) {
        full_res_t v = full_res_t_pop(temporary_results);
        full_res_t_push(parser_state.result_stack, v);
    }
}

/* stack function for parser */
static void exe_i_push (exe_entry_t * ep, unsigned int i) {
  internal_stack_entry_t tmp;
  tmp.type=st_unsigned_int;
  tmp.value.i = i;
  internal_stack_entry_t_push(ep->stack, tmp);
}
static unsigned int exe_i_pop(exe_entry_t * ep) {
  internal_stack_entry_t tmp = internal_stack_entry_t_pop(ep->stack);
  assert(tmp.type == st_unsigned_int);
  return tmp.value.i;
}
/* stack function for parser */
static exe_entry_t * exe_regex_push (exe_entry_t * ep, const char * s) {
#ifdef EXE_DEBUG
  printf("-------------------\n");
  printf("\tpush REGEX='%s'\n", s);
  printf("-------------------\n");
#endif
    internal_stack_entry_t tmp;
    tmp.type = st_regex;
    tmp.value.regex = strdup(s);
    internal_stack_entry_t_push(ep->stack, tmp);
#ifdef EXE_DEBUG
  printf("\ton top='%s'\n", ep->regex_stack[ (ep->regex_stackp)-1 ]);
#endif
  return ep;
}

static const char * exe_regex_pop(exe_entry_t * ep) {
  internal_stack_entry_t tmp = internal_stack_entry_t_pop(ep->stack);
  assert(tmp.type == st_regex);
  const char * s = tmp.value.regex;
#ifdef EXE_DEBUG
  printf("-------------------\n");
  printf("\tpop REGEX='%s'\n", s);
  printf("-------------------\n");
  printf("\ton top='%s'\n", ep->regex_stack[ (ep->regex_stackp) ]);
#endif
  return s;
}

static ret_t call_exec_function(ctiff_t * ctif, ret_t * retp, const exe_entry_t * exep) {
	ret_t ret = *retp;
	exe_entry_t exe = *exep;
	switch (exe.function) {
	        case fc_true:                           { ret.returncode=is_valid; break;}
	        case fc_false:                          { ret.returncode=should_not_occur; break; }
	        case fc_tag_has_some_of_these_values:   { unsigned int count = exe_i_pop(&exe);
	                                                  unsigned int values[count];
	                                                  for (unsigned int j=0; j<count; j++) {values[j]=exe_i_pop(&exe);}
	                                                  ret = check_tag_has_some_of_these_values(ctif, exe.tag, count, values);
	                                                  break;
	                                                }
	        case fc_tag_has_valuelist:              { unsigned int count = exe_i_pop(&exe);
	                                                  unsigned int values[count]; /* values are in parser_state.valuelist! */
                                                      for (unsigned int j=0; j<count; j++) {values[j]=exe_i_pop(&exe);}
	                                                  ret = check_tag_has_valuelist(ctif, exe.tag, count, values);
	                                                  break;
	                                                }
	        case fc_tag_has_value_in_range:         { unsigned int a = exe_i_pop(&exe);
	                                                  unsigned int b = exe_i_pop(&exe);
	                                                  ret = check_tag_has_value_in_range(ctif, exe.tag, a, b);
	                                                  break;
	                                                }
	        case fc_tag_has_value:                  { unsigned int a = exe_i_pop(&exe);
	                                                  ret = check_tag_has_value(ctif, exe.tag, a);
	                                                  break;
	                                                }
	        case fc_tag_has_value_quiet:            { unsigned int a = exe_i_pop(&exe);
	                                                  ret = check_tag_has_value_quiet(ctif, exe.tag, a);
	                                                  break;
	                                                }
	        case fc_tag:                            { ret = check_tag(ctif, exe.tag); break;}
	        case fc_tag_quiet:                      { returncode_t rc = check_tag_quiet(ctif, exe.tag); ret.returncode = rc; break;}
	        case fc_notag:                          {
	                                                  ret = check_notag(ctif, exe.tag);
	                                                  break;
	                                                }
	        case fc_tag_has_valid_type:             { ret = check_tag_has_valid_type(ctif, exe.tag); break;}
	        case fc_datetime:                       { ret = check_datetime(ctif); break;}
	        case fc_icc:                            { ret = check_icc(ctif); break;}
	        case fc_has_only_one_ifd:               { ret = check_has_only_one_ifd(ctif); break;}
	        case fc_tagorder:                       { ret = check_tagorder(ctif); break;}
	        case fc_tag_has_valid_asciivalue:       { ret = check_tag_has_valid_asciivalue(ctif, exe.tag); break;}
	        case fc_tag_has_value_matching_regex:   { const char * regex_string = exe_regex_pop( &exe);
	                                                  ret = check_tag_has_value_matching_regex(ctif, exe.tag, regex_string);
                                                      //free( regex_string );
	                                                  break;
	                                                }
	        case fc_all_offsets_are_word_aligned:   { ret = check_all_offsets_are_word_aligned(ctif); break;}
	        case fc_all_offsets_are_used_once_only: { ret = check_all_offsets_are_used_once_only(ctif); break;}
	        case fc_all_IFDs_are_word_aligned:      { ret = check_all_IFDs_are_word_aligned(ctif); break;}
	        case fc_internal_logic_combine_open:    { ret.returncode = parser_logical_combine_open ; break; }
	        case fc_internal_logic_combine_close:   { ret.returncode = parser_logical_combine_close ; break; }
	        case fc_all_offsets_are_not_zero:       { ret = check_all_offsets_are_greater_zero(ctif); break;}
            case fc_all_offsets_within_filesize:    { ret = check_all_offsets_within_filesize(ctif); break;}
            case fc_all_IFDs_have_tags:             { ret = check_all_IFDs_have_tags(ctif); break;}
            case fc_all_geotiff_tags_have_same_count_of_values: {
                                                          ret = check_all_geotiff_tags(ctif); break;
                                                        }
            case fc_check_cardinality: { ret = check_cardinality_of_some_tags_are_equal(ctif); break;}

	        default: {
	                   GET_EMPTY_RET(res)
	                   res.value_found = empty_str();
	                   fprintf(stderr, "lineno=%i, stack entry tag %i", parser_state.lineno, exe.tag);
	                   exit(parser_error_wrong_function_found_in_parser_state_exe_stack);
	                 }
	      }

	      assert( ret.returncode != should_not_occur );
	      assert( ret.returncode != calling_error_count_size);
	      *retp = ret;
	      return ret;
}

/* ececute fc directly */
static ret_t execute_function_and_set_results_for_next_call (ctiff_t * ctif, const exe_entry_t * exep, const ret_t * retp, int * precondition_result_p, function_t * precondition_function_p ) {
    assert(NULL != ctif);
    assert(NULL != exep);
    assert(NULL != retp);
    exe_entry_t exe = *exep;
    ret_t ret = *retp;
    ret = call_exec_function(ctif,  &ret, &exe );
#ifdef EXE_DEBUG
      printf("ret.returncode=%i, exe.is_precondition=%i is_valid=%i", ret.returncode, exe.is_precondition, is_valid);
      if (exe.function == fc_tag_has_valid_type) {
        printf("SSS tag %i has valid type: %i==%i (%s)\n", exe.tag, ret.returncode, is_valid, ret.value_found);
      }

#endif
      if (false == exe.is_precondition) { /* no precondition */
	      if (exe.tag >= MINTAGS) { /* exe.tag is 16bit, therefore true: && exe.tag <= (MAXTAGS-1)) */
              parser_state.called_tags[exe.tag]=true; /* mark tag that it has a rule */
          }
      }
      /* combine results */
    if (
        ( true == exe.is_precondition ) &&

        (
            ! (
                exe.function == fc_tag_has_valid_type &&
                (
                    ret.returncode == tagerror_unexpected_type_found ||
                    ret.returncode == tag_does_not_exist
                )
            )

        )
    ) { /* precondition, next run depends on this result */
        *precondition_result_p = ret.returncode;
        ret.value_found = empty_str();
        ret.expected_value = empty_str();
        //if (NULL != expected_value) {
        //  free(expected_value);
        //  expected_value = NULL;
        //}
      } else { /*  no precondition function */
	/* if true, execute function */
        full_res_t full;
        full.tag = exe.tag;
        full.function=exe.function;
        full.lineno=exe.lineno;
        full.returncode=ret.returncode;
        full.expected_value=empty_str();
        full.found_value=empty_str();
        if (ret.returncode == tag_does_not_exist) {
            full.found_value = const_str("Tag not exist");
        }
        else if (ret.returncode != is_valid) {
          full.expected_value = ret.expected_value;
          full.found_value = ret.value_found;
        }
        else {
          ret.value_found= empty_str();
        }
        result_push( full );
        *precondition_result_p=is_valid;
        *precondition_function_p = exe.function;
      }
#ifdef EXE_DEBUG
      printf(" precondition_result=%i\n", *precondition_result_p);
#endif
      return ret;
}

static ret_t clean_logical_or_if_precondition_failed (const exe_entry_t * exep, const ret_t * retp) {
    assert(NULL != exep);
    assert(NULL != retp);
    exe_entry_t exe = *exep;
    ret_t ret = *retp;
   #ifdef EXE_DEBUG
    printf ( "\t\t\t\t\t\t\t####################################\n" );
    printf ( "exe function: %s\n", get_parser_function_description ( exe.function ) );
#endif
    if ( exe.function == fc_internal_logic_combine_open ) { /* clean logical or if precondition fails */
        if (!exe_entry_t_is_empty(parser_state.exe_stack)) {
            do {
                exe_entry_t l_exe = exe_pop();
#ifdef EXE_DEBUG
                printf ( "~~~ eliminating Logicals because PRE fails: %s\n", get_parser_function_description ( l_exe.function ) );
#endif

                if (l_exe.function == fc_internal_logic_combine_close) { break; }
            } while (!exe_entry_t_is_empty(parser_state.exe_stack));
        }
#ifdef EXE_DEBUG
        printf ( "\t\t\t\t\t\t\t# ende\n" );
#endif
    }
    ret.returncode = is_valid;
    return ret;
}

/* executes a plan (list) of functions, checks if predicate-function calls are
 * needed, too. The plan is a global variable.
 *
 * HINT: the order of stack is reversed, that means:
 *   first, push an execute-function
 *   second, push the corresponding precondition
 *
 *   Example: "123; optional; only("1")"
 *   push fc_tag_has_value 123 <--- test if tag 123 has only value 1, but only if precondition succeeds
 *   push fc_tag_quiet 123 <--- precondition, checks if tag exists
 *
 * @param tif pointer to TIFF structure
 * @return return-code is 0 if all called functions are succeeds
 */
void execute_plan (ctiff_t * ctif) {
  /*  iterate other function-stack */
  int precondition_result=is_valid;
  function_t precondition_function=fc_true;
  int * restrict called_tags = parser_state.called_tags;

  memset(&called_tags[MINTAGS], false, sizeof(int) * (MAXTAGS-MINTAGS));
  if (!exe_entry_t_is_empty(parser_state.exe_stack)) {
      bool last_run_was_a_precondition=false;
#ifdef EXE_DEBUG
      printf("------------------------------------\n");
  //i_printstack();
  //r_printstack();
  exe_printstack();
  printf("------------------------------------\n");
#endif
      do { /* parser_state.exe_stackp > 0 */
          GET_EMPTY_RET(ret)
          exe_entry_t exe = exe_pop();
          bool should_we_go_in_fc_call =
                  ( /* Precondition was valid */
                          (true == last_run_was_a_precondition) &&
                          (is_valid == precondition_result)
                  ) ||
                  ( /* Precondition was valid, but has a type warning */
                          (true == last_run_was_a_precondition) &&
                          (tagwarn_type_of_unknown_tag_could_not_be_checked == precondition_result)
                  ) ||

                  ( /* last was a normal function */
                          false == last_run_was_a_precondition
                  );
#ifdef EXE_DEBUG
          //i_printstack();
          //exe_printstack();
          printf("last run was a precondition? %s\n", last_run_was_a_precondition?"true":"false");
          printf("last run was a precondition? (2) %s\n", (true==last_run_was_a_precondition)?"true":"false");
          printf(".. precondition_result=%s\n", get_parser_error_description(precondition_result));
          printf(".. precondition_function=%s\n", get_parser_function_description(precondition_function));
          printf("should we go in fc call? %s\n",  ( /* last was successful precondition */
                      should_we_go_in_fc_call)?"true":"false");
          printf("parsing function %s (%i) (linecode=%i)\n", get_parser_function_description( exe.function), exe.function, exe.lineno);
#endif
          if (should_we_go_in_fc_call) { /* if true, execute function */
              ret = execute_function_and_set_results_for_next_call(ctif, &exe, &ret, &precondition_result,
                                                                   &precondition_function);
          } else { // if precondition fails, return precondition if they have unexpected value!
              if (last_run_was_a_precondition) {
                  ret = clean_logical_or_if_precondition_failed(&exe, &ret);
              }
          }
          last_run_was_a_precondition = exe.is_precondition;
          assert(ret.returncode != should_not_occur);
#ifdef EXE_DEBUG
          if (ret.returncode == is_valid) {
            printf("tmpresult = %s {returncode=%i, tag=%i}\n\nexe is precondition=%s\n", get_parser_error_description(ret.returncode), ret.returncode, exe.tag, exe.is_precondition==true?"true":"false");;
          }else{
            printf("tmpresult = %s {returncode=%i, tag=%i}\n\nexe is precondition=%s\n", get_parser_error_description(ret.returncode), ret.returncode, exe.tag, exe.is_precondition==true?"true":"false");;
          }
          printf("==========\n");
#endif
      } while (!exe_entry_t_is_empty(parser_state.exe_stack));
  }
#ifdef EXE_DEBUG
  printf("all processed\n");
#endif
#ifndef NOTAGCHECK
  for (unsigned int tag = MINTAGS;  tag < MAXTAGS; tag++) {

    if (
            (-1 != TIFFGetRawTagListIndex(ctif->ifd0,  tag))
            && (false==called_tags[tag])
        ) { // tag found in tiff, but not called
#ifdef EXE_DEBUG
        //        printf("checking %u -> res=%s (%tag)\n", tag, get_parser_error_description(res.returncode), res.returncode);
#endif
        full_res_t full;
        full.tag = (tag_t) tag;
        full.function = fc_notag;
        full.lineno = -1;
        full.returncode = tagerror_not_white_listed;
        full.expected_value = const_str("Tag should not exist.");
        full.found_value = empty_str();
        result_push_back(full);
    }
  }
#endif /* NOTAGCHECK */

}


/* function to clean an execution plan */
void clean_plan (void) {
}



/* helper function for parser */
static tag_t settag( tag_t tag) {
#ifdef RULE_DEBUG
    printf("settag, %i\n", tag);
#endif
    parser_state.tag=tag;
    return tag;
}

static tag_t settagref( tag_t tag) {
#ifdef RULE_DEBUG
    printf("settagref, %i\n", tag);
#endif
    parser_state.tagref=tag;
    return tag;
}
/* helper function for parser */
static tag_t gettag(void) { return parser_state.tag;}
static int incrlineno(void) {
  parser_state.lineno++;
#ifdef RULE_DEBUG
  printf("##lineno=%i\n", parser_state.lineno);
#endif
  return parser_state.lineno;
}

/* helper function for parser */
static int getlineno(void) { return parser_state.lineno;}


/* helper function for parser */
static void commentline(void) {
#ifdef RULE_DEBUG
  printf("commentline, %i\n", parser_state.lineno);
#endif
}

/* helper function for parser */
static void set_mandatory(void) {
#ifdef RULE_DEBUG
  printf("tag '%u' is mandatory\n", gettag());
#endif
  parser_state.req=mandatory;
}

/* helper function for parser */
static void set_optional(void) {
#ifdef RULE_DEBUG
  printf("tag '%u' is optional\n", gettag());
#endif
  parser_state.req=optional;
  parser_state.any_reference=any_ref;
  parser_state.tagref=gettag();
}

/* helper function for parser */
static void set_ifdepends(void) {
#ifdef RULE_DEBUG
  printf("tag '%u' is set if depends\n", gettag());
#endif
  parser_state.req=ifdepends;
}

/* helper function for parser */
static void set_optdepends(void) {
#ifdef RULE_DEBUG
  printf("tag '%u' is set optional depends\n", gettag());
#endif
  parser_state.req=optdepends;
}

/* helper function for parser */
static void regex_push(char * regex_string) {
  PCRE2_SIZE erroffset;
  int errorcode;
  pcre2_code *re = pcre2_compile(
      (unsigned char *) regex_string, /* the pattern */
      PCRE2_ZERO_TERMINATED, /* the pattern length in code units */
      0, /* default options */
      &errorcode, /* for error code */
      &erroffset, /* for error offset */
      NULL
  );                           /* no compile context */
  if (NULL != re) {
#ifdef DEBUG
    printf("regex found: '%s' in line %i\n", regex_string, parser_state.lineno);
#endif
    pcre2_code_free(re);
    r_push(regex_string);
  } else {
    unsigned char errormsg[512];
      pcre2_get_error_message(errorcode, errormsg, 511);
    fprintf(stderr, "regex compile error: %s at offset: %li in line %i\n", errormsg, erroffset, parser_state.lineno);
    exit(EXIT_FAILURE);
  }
}

/* helper function for parser */
static void reset_valuelist(void) {
  parser_state.valuelist = 0;
}

/* helper function for parser */
static void incr_values (void) {
  parser_state.valuelist++;
}

/* prepare functions for preconditions */
static exe_entry_t prepare_internal_entry(void) {
  exe_entry_t p;
  p.stack = internal_stack_entry_t_new();
  p.lineno=getlineno();
  p.is_precondition=true;
  p.tag=parser_state.tagref;
  p.function=fc_tag_quiet;
  switch (parser_state.any_reference) {
    case no_ref:
    case any_ref:
      p.function=fc_tag_quiet;
      break;
    case only_ref:
      p.function=fc_tag_has_value_quiet;
      exe_i_push(&p, i_pop() );
      break;
    case range_ref:
      p.function=fc_tag_has_value_in_range;
      exe_i_push(&p, i_pop() );
      exe_i_push(&p, i_pop() );
      break;
    case ntupel_ref:
      {
        unsigned int c = i_pop();
        for (unsigned int i = 0; i<c; i++) {
          exe_i_push(&p, i_pop() );
        }
        exe_i_push(&p, c);

        p.function=fc_tag_has_valuelist;
        break;
      }
    case regex_ref:
      p.function=fc_tag_has_value_matching_regex;
      exe_regex_push(&p, r_pop());
      break;
  }
  return p;
}



static void build_functional_structure__range(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                    printf("range found\n");
#endif
                    exe_i_push(e_p, i_pop() );
                    exe_i_push(e_p, i_pop() );
                    e_p->function=fc_tag_has_value_in_range;
}

static void build_functional_structure__ntupel(exe_entry_t * e_p) {
    #ifdef RULE_DEBUG
                    printf("ntupel found\n");
#endif
                     unsigned int c = i_pop();
                     for (unsigned int i = 0; i<c; i++) {
                       exe_i_push(e_p, i_pop() );
                     }
                     exe_i_push(e_p, c);
                     e_p->function=fc_tag_has_valuelist;
}
static void build_functional_structure__only(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                    printf("only found\n");
#endif
                   exe_i_push(e_p, i_pop() );
                   e_p->function=fc_tag_has_value;
}

static void build_functional_structure__any(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                    printf("any found\n");
#endif
                  e_p->function=fc_tag;
}

static void build_functional_structure__regex(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                    printf("regex found\n");
#endif
                    exe_regex_push(e_p, r_pop());
                    e_p->function=fc_tag_has_value_matching_regex;
}

static void build_functional_structure__iccprofile(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                         printf("iccprofile found\n");
#endif
                         e_p->function=fc_icc;
}

static void build_functional_structure__datetime(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                       printf("datetime found\n");
#endif
                       e_p->function=fc_datetime;
}

static void build_functional_structure__printable_ascii(exe_entry_t * e_p) {
#ifdef RULE_DEBUG
                              printf("printable_ascii found\n");
#endif
                              exe_regex_push(e_p, "^[[:print:]]*$");
                              e_p->function=fc_tag_has_value_matching_regex;
}
/* builds an entry structure holding function and their values */
static void build_functional_structure(exe_entry_t * e_p, values_t val) {
    switch (val) {
        case range:
            build_functional_structure__range(e_p);
            break;
        case ntupel:
            build_functional_structure__ntupel(e_p);
            break;
        case only:
            build_functional_structure__only(e_p);
            break;
        case any:
            build_functional_structure__any(e_p);
            break;
        case regex:
            build_functional_structure__regex(e_p);
            break;
        case iccprofile:
            build_functional_structure__iccprofile(e_p);
            break;
        case datetime:
            build_functional_structure__datetime(e_p);
            break;

        case printable_ascii:
            build_functional_structure__printable_ascii(e_p);
            break;
            /* TODO:
            case sbit: {
            #ifdef RULE_DEBUG
       printf("sbit found\n");
            #endif
        exe_regex_push(e_p, r_pop());
        e_p->function=fc_tag_has_value;
        break;
      }
             */
        default:
            fprintf(stderr, "unknown val %u, should not occur\n", val);
            exit(EXIT_FAILURE);
    }
}

/* add preconditions to stack, first comes the real function, than the preconditions! */
static void evaluate_req_and_push_exe(requirements_t req, exe_entry_t e) {
    assert( e.function != fc_dummy);
#ifdef RULE_DEBUG
    printf("eval e=%s\n", get_parser_function_description(e.function));
#endif

    if (e.function == fc_internal_logic_combine_close) {
#ifdef RULE_DEBUG
        printf("\n### eval req logical close found\n");
#endif
        e.function = fc_internal_logic_combine_close;
                exe_push(e); // TODO: exe_push(e); /* <-- logical close */
                // TODO: exe_push(e); /* <--   real rule(s) */
                for (int i = 0; i< parser_state.logical_elements; i++) {
                    values_t val = v_pop();
                    e.function = fc_dummy;
                    build_functional_structure(&e, val); /* <-- this builds the tiff test */
#ifdef RULE_DEBUG
                    printf("###### i=%i e=%s\n", i, get_parser_function_description(e.function));
#endif
                    exe_push(e);
                };
                e.function=fc_internal_logic_combine_open;
    }
    exe_push(e);
    switch (req) {
        case ifdepends:
        case optional:
        {
            exe_entry_t p = prepare_internal_entry();

            if (parser_state.mode & mode_enable_type_checks) {
                exe_entry_t z = e;
                z.is_precondition = true;
                z.function = fc_tag_has_valid_type;
                exe_push(z);
            }
            exe_push(p);
            break;
        }
        case mandatory:
        {

            if (parser_state.mode & mode_enable_type_checks) {
                exe_entry_t z = e;
                z.is_precondition = true;
                z.function = fc_tag_has_valid_type;
                exe_push(z);
            }
            break;
        }
        case optdepends:
        {
            exe_entry_t pp;
            pp.stack = internal_stack_entry_t_new();
            pp.lineno = getlineno();
            pp.is_precondition = true;
            pp.tag = e.tag;
            pp.function = fc_tag_quiet;

            exe_entry_t p = prepare_internal_entry();

            if (parser_state.mode & mode_enable_type_checks) {
                exe_entry_t z = e;
                z.is_precondition = true;
                z.function = fc_tag_has_valid_type;
                exe_push(z);
            }
            exe_push(p);
            exe_push(pp);
            break;
        }
        default:
        {
            fprintf(stderr, "unknown parserstate.req (%i), should not occur\n", parser_state.req);
            exit(EXIT_FAILURE);
        }

    }
}

static void set_rule_logical_open(void) {
#ifdef RULE_DEBUG
  printf("rule_add_logical_config\n");
#endif
  parser_state.within_logical_or=true;
}

static void incr_logical_elements(void) {
#ifdef RULE_DEBUG
  printf("incr logical elements to %i\n",  parser_state.logical_elements+1);
#endif

  parser_state.logical_elements++;
}

/* helper function for parser */
static void set_rule_logical_close(void) {
#ifdef RULE_DEBUG
  printf("reset_logical_list\n");
#endif
  exe_entry_t e;
  e.tag = parser_state.tag;
  e.stack = internal_stack_entry_t_new();
  e.lineno=getlineno();
  e.is_precondition=false;
  e.function=fc_internal_logic_combine_close;
  //exe_push(e);
  evaluate_req_and_push_exe( parser_state.req, e);
  parser_state.within_logical_or=false;
  parser_state.logical_elements=0;
}

/* this adds the config of a tagline to execution plan
 * HINT: order of calling arguments from stacks is IMPORTANT! */
static void rule_addtag_config(void) {
  if(!values_t_is_empty(parser_state.values_stack)) {
#ifdef RULE_DEBUG
    printf("rule_addtag_config\n");
#endif
    exe_entry_t e;
    e.stack =internal_stack_entry_t_new();
    e.lineno=getlineno();
    e.is_precondition=false; /*  no precondition as default */
    e.tag = parser_state.tag;

#ifdef RULE_DEBUG
    printf( "try to match tagline at line %i\n", e.lineno);
#endif
    /* HINT: order of evaluating last val and last req is IMPORTANT! */
    /* stack should be organized as:
     * [ruleA]
     * [preconditionA]    <- optional
     * [prepreconditionA] <- optional
     * [ruleB]
     * [preconditionB]    <- optional
     * [prepreconditionB] <- optional
     */
    do {
      values_t val = v_pop();
      e.function = fc_dummy;
      build_functional_structure(&e, val); /* <-- this builds the tiff test */
      /* set predicate if and only if lastreq = depends */
      /* HINT: order of evaluating last val and last req is IMPORTANT! */
      /* HINT: order of calling arguments from stacks is IMPORTANT! */
      evaluate_req_and_push_exe( parser_state.req, e); /* <-- this sets the predicates */
    } while (!values_t_is_empty(parser_state.values_stack));
    reset_valuelist();
    parser_state.any_reference = 0;
#ifdef RULE_DEBUG
    printf("ENDE rule_addtag\n");
    printf("\n\n\n");

#endif
  }
}

/* set_mode */
static void set_mode(modes_t mode) {
#ifdef RULE_DEBUG
	printf("Mode=%i at line=%i (needs to be implemented)\n", mode, parser_state.lineno );
#endif
  exe_entry_t e;
  e.stack=internal_stack_entry_t_new();
  e.lineno=getlineno();
  e.is_precondition=false; /*  no precondition as default */
  e.tag = parser_state.tag;

  switch (mode) {
    case mode_enable_offset_checks: {
                                      e.function = fc_all_offsets_are_word_aligned;
                                      exe_push(e);
                                      e.function = fc_all_offsets_are_used_once_only;
                                      exe_push(e);
                                      e.function = fc_all_offsets_are_not_zero;
                                      exe_push(e);
                                      e.function = fc_all_offsets_within_filesize;
                                      exe_push(e);
                                      break;
                                    }
    case mode_enable_ifd_checks: {
                                      e.function = fc_all_IFDs_have_tags;
                                      exe_push(e);
                                      e.function = fc_all_IFDs_are_word_aligned;
                                      exe_push(e);
                                      e.function = fc_tagorder;
                                      exe_push(e);
                                      e.function = fc_check_cardinality;
                                      exe_push(e);
                                      break;
                                 }
    case mode_baseline: {
                                      e.function = fc_has_only_one_ifd;
                                      exe_push(e);
                                      break;
                        }
    case mode_enable_deep_geotiff_checks: {
                                      e.function = fc_all_geotiff_tags_have_same_count_of_values;
                                      exe_push(e);
                                      break;
                                    }
    case mode_enable_type_checks: { /*  nothing, because we must enable it only in rule_addtag_config */
                                  }
  }
  parser_state.mode |= mode;
}


/* reset the parser state */
static void reset_parser_state(void) {
  parser_state.any_reference=0;
  parser_state.exe_stack = exe_entry_t_new();
  parser_state.values_stack = values_t_new();
  parser_state.includedepth=0;
  parser_state.lineno=1;
  parser_state.req=0;
  parser_state.result_stack = full_res_t_new();
  parser_state.intermediate_stack = internal_stack_entry_t_new();
  parser_state.tag=0;
  parser_state.tagref=0;
  parser_state.valuelist=0;
  parser_state.mode=0;
  parser_state.within_logical_or=false;
  parser_state.logical_elements=0;
  /*
  int * restrict called_tags = parser_state.called_tags;
  for (register unsigned int i=0; i<MAXTAGS; i++) {
    called_tags[i]= false;
  }
  */
  memset(parser_state.called_tags, (int) false, sizeof(unsigned int)*MAXTAGS);
}

/* include the PEG generated parser, see "man peg" for details */
#include "config_dsl.grammar.c"   /* yyparse() */

/* set_include */
static void set_include( const char * include_file ) {
	if (parser_state.includedepth >= 1) {
		fprintf( stderr, "only include depth of %i is supported\n", MAXINCLUDEDEPTH);
		exit (EXIT_FAILURE);
	}
	parser_state.includedepth++;
    /* check if path is relative, then add current parent path */
    char * include_file_expanded = NULL;
    const char * current_file = parser_state.cfg_filename;

#ifndef __WIN32__
    if (include_file[0] != '/' && current_file != NULL) {
        char * absolute_current = realpath(current_file,  NULL);
        const char * dirname_current = dirname( absolute_current );
        size_t dirname_len = strlen( dirname_current);
        size_t include_len = strlen( include_file);
        include_file_expanded = calloc((dirname_len + include_len + 2), sizeof (char)); /* 2 because EOS + "/" */
        strcat( include_file_expanded, dirname_current);
        const char * delim = "/";
        strcat( include_file_expanded, delim);
        strcat( include_file_expanded, include_file);
        free( absolute_current );
   } else {
        include_file_expanded = strdup(include_file);
    }
#else
    /* if anybody sends a patch to detect if absolute path under MS Windows is ued, the code could be included.
     * Otherwise the config-file should ue absolute paths instead
     */
    include_file_expanded = strdup(include_file);
#endif
	printf("Include='%s'\n", include_file_expanded);
	FILE * cfg = fopen(include_file_expanded, "r");
	if (NULL == cfg) {
		fprintf( stderr, "file '%s' could not be opened\n", include_file_expanded);
		exit (EXIT_FAILURE);
	};
	clean_plan();
	yycontext ctx2 = {0};
    FILE * old_stream = parser_state.stream;
	int old_lineno = parser_state.lineno;
    const char * old_file = parser_state.cfg_filename;
	printf("At lineno %i, file '%s', switching to include file '%s' (expanded: '%s')\n", old_lineno, parser_state.cfg_filename, include_file, include_file_expanded);
	parser_state.lineno=1;
	parser_state.stream=cfg;
	while (yyparse(&ctx2))     /* repeat until EOF */
		;
	yyrelease(&ctx2);
	parser_state.includedepth--;
	printf("End of Include=%s\n", include_file);
	parser_state.stream = old_stream;
	parser_state.lineno = old_lineno;
    parser_state.cfg_filename = old_file;
	fclose(cfg);
    free( include_file_expanded);
}


/* function to parse a config file from STDIN */
void parse_plan (void) {
  reset_parser_state();
  yycontext ctx = {0};
  parser_state.stream=stdin;
  parser_state.cfg_filename=NULL;
  while (yyparse(&ctx))     /* repeat until EOF */
    ;
  yyrelease(&ctx);

}

/* function to parse a config file from file stream */
void parse_plan_via_stream( FILE * file, const char * optional_cfg_file ) {
  reset_parser_state();
  yycontext ctx = {0};
  parser_state.stream=file;
  parser_state.cfg_filename=optional_cfg_file;
  while (yyparse(&ctx))     /* repeat until EOF */
    ;
  yyrelease(&ctx);
}

/* function to parse a config file */
void parse_plan_via_file( const char * cfg_file ) {
 FILE * cfg = fopen(cfg_file, "r");
  if (NULL == cfg) {
	  fprintf( stderr, "file '%s' could not be opened\n", cfg_file);
	  exit (EXIT_FAILURE);
  };
  clean_plan();
  parse_plan_via_stream( cfg, cfg_file );
  fclose(cfg);
}

/* set parse error
 * @param msg describes the details about what was going wrong
 * @param yytext gives context of the error
 */
static void set_parse_error(const char * msg, char * yytext) {
  fprintf(stderr, "%s at line %i (error at '%s')\n", msg, parser_state.lineno, yytext);
  exit(EXIT_FAILURE);
}

static void clean_plan_results(void) {
    full_res_t_delete(parser_state.result_stack);
//    free(parser_state.result_stack);
    parser_state.result_stack = full_res_t_new();
}

/* prints a plan (list) of functions and their results*/
ret_t print_plan_results(retmsg_t * actual_render) {
  GET_EMPTY_RET(res)
#ifdef DEBUG
  printf("print plan results:\n");
  printf("####################(\n");
  full_res_t_print_top( parser_state.result_stack);
  printf(")####################\n");
  printf("reduced\n");
#endif

#ifdef EXE_DEBUG
  result_printstack();
#endif
  reduce_results();
#ifdef EXE_DEBUG
  result_printstack();
#endif
  size_t count_of_all_results = full_res_t_size(parser_state.result_stack);
  size_t count_of_valid_results = 0;
  while(!full_res_t_is_empty(parser_state.result_stack)) {
   full_res_t parser_result = full_res_t_pop(parser_state.result_stack);
#ifdef DEBUG
   full_res_t_print_top( parser_state.result_stack);
#endif
   returncode_t returncode =  parser_result.returncode;
   /* fill render pipeline */
   uint16 tag = parser_result.tag;
   /* fill with errorcodes */
   rm_type_t type= rm_default;
   switch ( returncode ) {
     /*  add rm_type */
     case is_valid: type = rm_is_valid; count_of_valid_results++; break;
     case parser_logicalor_error: type = rm_logicalor_error; break;
     case could_not_allocate_memory:
     case could_not_print:
     case should_not_occur: assert ( 1 );
     case code_error_streampointer_empty:
     case code_error_filedescriptor_empty:
     case code_error_ctif_empty:
     case pcre_compile_error:
     case tiff_seek_error_header:
     case tiff_read_error_header:
     case tiff_seek_error_offset:
     case tiff_read_error_offset:
                                  type = rm_hard_error; break;
     default: type = rm_error; break;
   }
   add_to_render_pipeline(&actual_render, empty_str(), type);

   /* fill with tag infos */
   if (tag > 0) {
     char tagstr [VALUESTRLEN];
     snprintf(tagstr, sizeof(tagstr), "tag %i (%s)", tag, TIFFTagName(tag));
     add_to_render_pipeline(&actual_render, str( tagstr), rm_tag);
   } else {
     add_to_render_pipeline(&actual_render, const_str("general"), rm_mode);
   }

   /* fill with rule infos */
   add_to_render_pipeline(&actual_render, str( get_parser_function_description(parser_result.function)),  rm_rule);
   add_to_render_pipeline(&actual_render, parser_result.expected_value, rm_expected);
   /* fill with value found */
   if (returncode != is_valid) {
     add_to_render_pipeline(&actual_render, str( get_parser_error_description(returncode)), rm_error_description);
     add_to_render_pipeline(&actual_render, parser_result.found_value, rm_value);
   }
   /*  fill with lineno */
   char msg[5];
   snprintf(msg, 5, "%i", parser_result.lineno);
   add_to_render_pipeline(&actual_render, str(msg), rm_lineno);

   /* fill with newline */
   add_to_render_pipeline(&actual_render, empty_str(), rm_endrule);
   if (type == rm_hard_error) { goto harderrors; }

   /*   printf( "%s", renderer( parser_state.result_stack[i].result)); */
  }
harderrors:
    summarize_results(count_of_all_results, count_of_valid_results, &actual_render, &res);
    add_to_render_pipeline(&actual_render, empty_str(), rm_endtiff);
  clean_plan_results();
  return res;
}

void summarize_results(size_t count_of_all_results, size_t count_of_valid_results, retmsg_t **actual_render, ret_t *res) {
    size_t errors = (count_of_all_results - count_of_valid_results);
    char emsg[25];
    char msg[25];
    snprintf(msg, 25, "Found %zi valid results", count_of_valid_results);
    snprintf(emsg, 25, "Found %zi errors", errors);
    add_to_render_pipeline(actual_render, str(msg), rm_count_valid);
    if (errors > 0) {
        (*res).returncode = should_not_occur;
        add_to_render_pipeline(actual_render, str(emsg), rm_count_invalid);
        add_to_render_pipeline(actual_render, const_str("No, the given tif is not valid :("),
                                           rm_summary_invalid);
    } else {
        (*res).returncode = is_valid;
        add_to_render_pipeline(actual_render, const_str("Yes, the given tif is valid :)"),
                                           rm_summary_valid);
    }
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 smarttab expandtab :*/
