#include "heap.h"
#include "staticstring.h"
#include <string.h>

void reset_string_heap(void);

ErrorContext *test_fresh_heap_gives_strings(void)
{
  string *ptr = NULL;
  
  PREPARE_ERROR(errctx);
  for ( int i = 0; i < MAX_HEAP_STRING - 1; i++ ) {
    ATTEMPT {
	CATCH(errctx, heap_next_string(&ptr));
    } CLEANUP {
	reset_string_heap();
    } PROCESS(errctx) {
    } FINISH(errctx, true);
  }

  return 0;
}

ErrorContext *test_string_heap_error_when_no_strings_left(void)
{
    string *ptr;
    PREPARE_ERROR(errctx);
    for ( int i = 0; i < MAX_HEAP_STRING; i++ ) {
	HEAP_STRING[i].refcount = 1;
    }
    for ( int i = 0; i < MAX_HEAP_STRING - 1; i++ ) {
	ATTEMPT {
	    CATCH(errctx, heap_next_string(&ptr));
	} CLEANUP {
	} PROCESS(errctx) {
	} HANDLE(errctx, ERR_NULLPOINTER) {
	    return 0;
	} FINISH(errctx, true);
    }
    FAIL_RETURN(errctx, ERR_OUTOFBOUNDS, "Expected ERR_NULLPOINTER when accessing beyond string heap bounds");
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_string_heap_honors_refcount(void)
{
    string *firstptr = &HEAP_STRING[0];
    string *secondptr = &HEAP_STRING[1];
    string *testptr = NULL;
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, heap_next_string(&testptr));
	if ( testptr != firstptr ) {
	    FAIL_RETURN(
		errctx,
		ERR_VALUE,
		"Expected testptr to equal (HEAP_STRING[0] = %p) but got %p",
		firstptr,
		testptr
		);
	}
	CATCH(errctx, string_initialize(testptr, NULL));
	if ( testptr->refcount == 0 ) {
	    FAIL_RETURN(errctx, ERR_VALUE, "Expected string reference count to be nonzero but got 0");
	}
	if ( testptr != firstptr ) {
	    FAIL_RETURN(
		errctx,
		ERR_VALUE,
		"Expected testptr to equal (HEAP_STRING[1] = %p) but got %p",
		secondptr,
		testptr
		);
	}
	CATCH(errctx, heap_next_string(&testptr));
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_strcpy_to_all_strings_no_segfault(void)
{
    char copybuf[MAX_STRING_LENGTH];
    string *ptr;
    memset((void *)&copybuf, 'a', MAX_STRING_LENGTH);
    PREPARE_ERROR(errctx);
    ATTEMPT {
	for ( int i = 0; i < MAX_HEAP_STRING - 1; i++ ) {
	    CATCH(errctx, heap_next_string(&ptr));
	    strncpy(ptr->data, (char *)&copybuf, MAX_STRING_LENGTH);
	}
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}    

ErrorContext *test_string_initialize(void)
{
    string *ptr;
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, heap_next_string(&ptr));
	CATCH(errctx, string_initialize(ptr, NULL));
	FAIL_NONZERO_BREAK(errctx, ptr->data[0], ERR_VALUE, "Expected empty zero length string data");

	CATCH(errctx, heap_release_string(ptr));
	CATCH(errctx, heap_next_string(&ptr));
	CATCH(errctx, string_initialize(ptr, "Test value"));
	FAIL_NONZERO_BREAK(errctx, strcmp((char *)&ptr->data, "Test value"), ERR_VALUE, "Expected 'Test value', got %s", (char *)&ptr->data);

    	CATCH(errctx, heap_release_string(NULL));
	FAIL_BREAK(errctx, ERR_BEHAVIOR, "Failure to properly handle NULL pointer");
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    SUCCEED_RETURN(errctx);
}


void reset_string_heap(void)
{
    for ( int i = 0; i < MAX_HEAP_STRING; i++ ) {
	memset(&HEAP_STRING[i], 0x00, sizeof(string));
    }
}

int main(void)
{
    
    PREPARE_ERROR(errctx);
    ATTEMPT {
	printf("test_fresh_heap_gives_string ....\n");
	test_fresh_heap_gives_strings();
	reset_string_heap();
	printf("test_string_heap_error_when_no_strings_left ...\n");
	test_string_heap_error_when_no_strings_left();
	reset_string_heap();
	printf("test_string_heap_honors_refcount ...\n");
	test_string_heap_honors_refcount();
	reset_string_heap();
	printf("test_strcpy_to_all_strings_no_segfault ...\n");
	test_strcpy_to_all_strings_no_segfault();
	reset_string_heap();
	printf("test_string_initialize....\n");
	test_string_initialize();
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
    
    return 0;
}
