/* '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)
 *
 * stack implementation
 */

/* the stack size depends on count of rules and count of numbers, in general
 * 65536 should be enough, otherwise you need to increase it */
#include <stddef.h>
#include <stdlib.h>
#include "parser_types.h"
#include "check.h"

#ifdef TEMPLATE_STACK_MAXSTACKDEPTH
#error "TEMPLATE_STACK_MAXSTACKDEPT was already defined, but should be exclusive for " __FILE__
#endif
#ifdef TEMPLATE_STACK_INTERNALSTACKDEPTH
#error "TEMPLATE_STACK_INTERNALSTACKDEPTH was already defined, but should be exclusive for " __FILE__
#endif
#define TEMPLATE_STACK_MAXSTACKDEPTH 65536
#define TEMPLATE_STACK_INTERNALSTACKDEPTH 32



/*
#define T int
#define PRINT_TYPE(a,b) printf("%i,%i\n", a,b);
*/

#ifndef T
#error "no stacktype <T> defined!"
#endif
#ifndef PRINT_TYPE
#error "set PRINT_TYPE to to a function which handles the signature 'f(int, <T>)"
#endif


#define CAT(a, b) a##b
#define PASTE(a, b) CAT(a, b)
#define JOIN(prefix, name) PASTE(prefix, PASTE(_, name))
#define STACK_S JOIN(T, stack_s)
#define STACK_T JOIN(T, stack_t)
#define STRINGIFY(a) #a
//#define T_AS_STRING STRINGIFY(T)
#define AS_STRING(a) "a"
#define QuoteIdent(ident) #ident
#define QuoteMacro(macro) QuoteIdent(macro)
#define T_AS_STRING QuoteMacro(T)

struct STACK_S {
    char * _first_caller;
    int _first_line; /* line no */
    int last_index; /* top value index */
    size_t capacity; /* size of stack */
    T * value_p; /* values */
};

typedef struct STACK_S STACK_T;


/* init */
static void JOIN(T,init)(STACK_T *this, const char * caller, int line) {
    assert(this != NULL);
    if (this->value_p != NULL) {
        fprintf(stderr, "stack of type %s was already initialized!\n", T_AS_STRING);
        fprintf(stderr, "this caller (caller %s, %i)\n", caller, line);
        fprintf(stderr, "first caller (caller %s, %i)\n",this->_first_caller, this->_first_line);
        exit(EXIT_FAILURE);
    }
    this->_first_caller = strdup(caller);
    this->_first_line = line;
    this->capacity=TEMPLATE_STACK_INTERNALSTACKDEPTH;
    this->last_index=-1;
    this->value_p = malloc(TEMPLATE_STACK_INTERNALSTACKDEPTH * sizeof( T ));
#ifdef STACKDEBUG
    printf("init stack of type %s\n", STRINGIFY(T) );
#endif
}

#define NEW new
static STACK_T * JOIN(T,NEW)() {
    STACK_T *this = malloc( sizeof (STACK_T));
    if (NULL == this) {
        fprintf(stderr,"could not create a stack of type %s", T_AS_STRING);
        exit(EXIT_FAILURE);
    }
    this->_first_caller="";
    this->value_p=NULL;
    JOIN(T,init)(this, __FILE__, __LINE__);
    return this;
}
#undef NEW


/* increase capacity */
static void JOIN(T, increase_capacity) (STACK_T *this) {
    if (this->capacity > TEMPLATE_STACK_MAXSTACKDEPTH) {
        perror("max stack capacity reached, abort");
        exit(EXIT_FAILURE);
    }
    size_t new_capacity = 0;
    if (this->capacity == 0) {
        new_capacity = TEMPLATE_STACK_INTERNALSTACKDEPTH;
    } else {
        new_capacity = 2 * (this->capacity);
    }
    assert( new_capacity > this->capacity);
    assert( new_capacity > 0);
#ifdef STACKDEBUG
    printf("increased stack capacity for stacktype %s to %lu\n", STRINGIFY(T), new_capacity );
#endif
    T * new_value_p = NULL;
    if (this->value_p == NULL) {
        new_value_p = calloc(new_capacity,sizeof(T));
    } else {
#ifdef COMPAT_NEED_REALLOCARRAY
        new_value_p = (T*) realloc( this->value_p, new_capacity * sizeof( T ));
#else
        new_value_p = reallocarray(this->value_p, new_capacity, sizeof(T));
#endif
    }
    if (NULL == new_value_p) {
        perror( "could not reallocate memory for increased stack, abort");
        exit(EXIT_FAILURE);
    }
    this->capacity = new_capacity;
    this->value_p = new_value_p;
}

/* is_empty */
static bool JOIN(T,is_empty)(STACK_T *this) {
    return (this->last_index == -1);
}

/* size() */
static size_t JOIN(T,size)(STACK_T *this) {
    return (this->last_index+1);
}

/* reset_capacity() */
static void JOIN(T, reset_capacity) (STACK_T *this) {
    if (
            (JOIN(T, size)(this) < TEMPLATE_STACK_INTERNALSTACKDEPTH)
            && (this->capacity > TEMPLATE_STACK_INTERNALSTACKDEPTH)
            ){
        size_t new_capacity = TEMPLATE_STACK_INTERNALSTACKDEPTH;
#ifdef STACKDEBUG
        printf("increased stack capacity for stacktype %s to %lu\n", STRINGIFY(T), new_capacity );
#endif
        T *new_value_p = NULL;
#ifdef COMPAT_NEED_REALLOCARRAY
        new_value_p = (T*) realloc( this->value_p, new_capacity * sizeof( T ));
#else
        new_value_p = reallocarray(this->value_p, new_capacity, sizeof(T));
#endif
        if (NULL == new_value_p) {
            perror("could not reallocate memory for increased stack, abort");
            exit(EXIT_FAILURE);
        }
        this->capacity = new_capacity;
        this->value_p = new_value_p;
    }
}

/* delete() */
static void JOIN(T, delete) (STACK_T *this) {
    if (JOIN(T, is_empty)(this)) {
        free(this->value_p);
        this->value_p = NULL;
        this->capacity = 0;
    }
}




static void JOIN(T, print_typed_value) (int i, const T v) {
    PRINT_TYPE(i,v);
}

/* print_top */
static void JOIN(T, print_top)(STACK_T *this) {
    if (JOIN(T, is_empty)(this)) {
        printf("empty stack (type=%s)\n", T_AS_STRING);
    } else {
        printf("\nstack (lastidx=%i, type=%s):\n", this->last_index, T_AS_STRING);
        printf("------------------------------------\n");
        assert( this->last_index >= 0);
        for (int i = this->last_index; i >= 0; i--) {
            JOIN(T, print_typed_value)(i, this->value_p[i]);
        }
        printf("------------------------------------\n");
    }
}

/* push */
static void JOIN(T,push)(STACK_T *this, T v) {
        if (
                ((size_t) this->last_index >= this->capacity-1)
                ) {
            JOIN(T, increase_capacity) (this);
        }
        assert((long signed int) this->capacity > 0);
        assert((long signed int) this->capacity > (long signed int) this->last_index);
#ifdef STACKDEBUG
        JOIN(T,print_top)(this);
        printf ("(push) this->last_index = %lu (after)\n", this->last_index+1);
#endif
        this->value_p[++this->last_index] = v;
}


/* pop */
static T JOIN(T,pop)(STACK_T *this) {
    if (this->last_index < 0) {
        perror ("Stack underflow!");
        exit(EXIT_FAILURE);
    }
#ifdef STACKDEBUG
    JOIN(T,print_top)(this);
    printf ("(pop) this->last_index = %li (before)\n", this->last_index);
#endif
    T v = this->value_p[this->last_index--];
#ifdef STACKDEBUG
    JOIN(T,print_top)(this);
    printf ("(pop) this->last_index = %li (before)\n", this->last_index);
    printf("END AFTER pop\n");
#endif
    if (JOIN(T, is_empty)(this)) {
        JOIN(T, delete)(this);
    } else if (this->last_index < TEMPLATE_STACK_INTERNALSTACKDEPTH / 2) {
        JOIN(T, reset_capacity)(this);
    }
    return v;
}

/* push_back */
static void JOIN(T,push_back)(STACK_T *this, const T v_init) {
    int orig_index = this->last_index;
    this->last_index++;
    if ((this->last_index >= 0)
        && ((size_t) this->last_index >= this->capacity-1)) {
        JOIN(T, increase_capacity) (this);
    }
    assert((long signed int) this->capacity > (long signed int) this->last_index);
    /* copy from index last to 0 to last+1 to 1 */
#ifndef BROKEN_PUSHBACK_MEMMOVE
    for (size_t i = this->last_index; i > 0; i--) {
        assert( (i+1) < this->capacity );
        assert( i > 0 );
        this->value_p[i] = this->value_p[i-1];
    }
#else
/* the following code is broken yet: */
    printf(">>>>>>>>>>>>>>>>>>>>>>>>>>\n");
    PRINT_TYPE(orig_index, this->value_p[orig_index]);
    if (orig_index >= 0) {
        const T * current_p = this->value_p + 1;
        T * new_p = this->value_p;
        printf("DEBUG current %p, current+1 %p \n", new_p, current_p);
        size_t values = (size_t) orig_index;
        memmove(new_p, current_p, sizeof(this->value_p[orig_index]) * values);
    }
    PRINT_TYPE(orig_index, this->value_p[orig_index]);
    printf("--------------------------, origindex+1\n");
    PRINT_TYPE(orig_index+1, this->value_p[orig_index+1]);
    printf("<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
#endif

    /* copy v_init on top */
    this->value_p[0] = v_init;
    assert( orig_index+1 == this->last_index);
}

#undef TEMPLATE_STACK_MAXSTACKDEPTH
#undef TEMPLATE_STACK_INTERNALSTACKDEPTH
#undef T
#undef PRINT_TYPE
