/*
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include <aerospike/as_arraylist.h>
#include <aerospike/as_list.h>
#include <aerospike/as_integer.h>
#include <aerospike/as_stream.h>
#include <aerospike/as_string.h>
#include <aerospike/as_rec.h>
#include <aerospike/as_map.h>
#include <aerospike/as_hashmap.h>
#include <aerospike/as_module.h>
#include <aerospike/mod_lua.h>
#include <aerospike/mod_lua_config.h>
#include <citrusleaf/alloc.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#include "../test.h"
#include "../util/test_aerospike.h"
#include "../util/test_logger.h"
#include "../util/map_rec.h"

/******************************************************************************
 * VARIABLES
 *****************************************************************************/

typedef struct {
	char *	filename;
	char * 	description;
	bool 	is_valid;
} validation_entry;

#define SCRIPT_LEN_MAX 1048576

static int readfile(const char * filename, char ** content, uint32_t * size) {
	
	uint8_t * content_v = content ? (uint8_t *) *content : NULL;
	uint32_t size_v = size ? (uint32_t) *size : 0;

	FILE * file = fopen(filename,"r"); 

	if (!file) { 
		error("cannot open script file %s : %s", filename, strerror(errno));  
		return -1; 
	} 

	if (content_v == NULL) {
		content_v = (uint8_t *) cf_malloc(SCRIPT_LEN_MAX);
		if (content_v == NULL) { 
			error("cf_malloc failed");
			fclose(file);
			return -2;
		}
	}

	uint32_t size_b = 0;

	uint8_t * buff = content_v; 
	int read = (int)fread(buff, 1, 512, file);
	while (read) { 
		size_b += read; 
		buff += read; 
		read = (int)fread(buff, 1, 512, file);
		if (size_b >= size_v-1) {
			break;
		}
	}           
	fclose(file);

	content_v[size_b] = '\0';

	*content = (char *) content_v;
	*size = size_b;

	return 0;
}

/******************************************************************************
 * TEST CASES
 *****************************************************************************/

TEST(validation_basics_1, "validation: src/test/lua/validate_*.lua")
{
	validation_entry entries[] = {
		{ AS_START_DIR "src/test/lua/validate_1.lua", "single file local variable", true },
		{ AS_START_DIR "src/test/lua/validate_2.lua", "invalid function in module scope", false },
		{ AS_START_DIR "src/test/lua/validate_3.lua", "invalid statement in function scope", false },
		{ AS_START_DIR "src/test/lua/validate_4.lua", "index a global variable in module scope", false },
		{ AS_START_DIR "src/test/lua/validate_5.lua", "index a global variable in function scope", true },
		{ AS_START_DIR "src/test/lua/validate_6.lua", "create closure on a global variable in function scope", true },
		{ NULL }
	};

	for(validation_entry * entry = entries; entry != NULL && entry->filename != NULL; entry++) {

		info("validating %s [%s] - %s", entry->filename, entry->is_valid ? "VALID" : "INVALID", entry->description);

		as_module_error err;
		int rc = 0;

		char * buff = NULL;
		uint32_t size = 0;

		rc = readfile(entry->filename, &buff, &size);
		assert(rc == 0);

		rc = as_module_validate(&mod_lua, ctx.as, entry->filename, buff, size, &err);
		cf_free(buff);
		
		if (rc != 0 && entry->is_valid) {
			info("error = {");
			info("  scope   = %d", err.scope);
			info("  code    = %d", err.code);
			info("  message = %s", err.message);
			info("  file    = %s", err.file);
			info("  line    = %d", err.line);
			info("  func    = %s", err.func);
			info("}");
			assert(rc == 0 && entry->is_valid);
		}
		
		if (rc == 0 && !entry->is_valid) {
			assert(rc != 0 && !entry->is_valid);
		}
	}
}

/******************************************************************************
 * TEST SUITE
 *****************************************************************************/

SUITE(validation_basics, "record basics")
{
	suite_before(test_suite_before);
	
	suite_add(validation_basics_1);
}
