/* Copyright 2016 Samsung Electronics Co., Ltd.
 *
 * 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 "scl/string.h"

#if defined(SCL_USE_ASSERT_H) && (SCL_USE_ASSERT_H == 0)
#	include <stdlib.h>
#	if defined(EXIT_FAILURE)
#		define SCL_ASSERT(assertion) { \
			if (!(assertion)) \
				exit(EXIT_FAILURE); \
		}
#	else
#		define SCL_ASSERT(assertion) { \
			if (!(assertion)) \
				exit(1); \
		}
#	endif
#else
#	include <assert.h>
#	define SCL_ASSERT(assertion) assert(assertion)
#endif

static void init_src(char *src, scl_size_t size) {
	scl_size_t i;
	for (i = 0; i < size - 1; ++i) {
		src[i] = 1 + i % CHAR_MAX;
	}
	src[i] = '\0';
}

static void check_src(char *src, scl_size_t size) {
	scl_size_t i;
	for (i = 0; i < size - 1; ++i) {
		SCL_ASSERT(src[i] == 1 + i % CHAR_MAX);
	}
	SCL_ASSERT(src[i] == '\0');
}

static void init_dest(char *dest, scl_size_t size) {
	scl_size_t i;
	for (i = 0; i < size; ++i) {
		dest[i] = CHAR_MAX;
	}
}

static void check_dest(char *dest, scl_size_t size, scl_size_t start, scl_size_t end, scl_size_t ch) {
	scl_size_t i;
	for (i = 0; i < start; ++i) {
		SCL_ASSERT(dest[i] == CHAR_MAX);
	}
	while (i < end) {
		SCL_ASSERT(dest[i] == 1 + (ch + (i - start)) % CHAR_MAX);
		++i;
	}
	SCL_ASSERT(dest[i] == '\0');
	++i;
	while (i < size) {
		SCL_ASSERT(dest[i] == CHAR_MAX);
		++i;
	}
}

static void check_copy(void) {
	int ret, i;
	char dest[256 + 16], src[256];
	init_src(src, sizeof(src)/ sizeof(src[0]));

	for (i = 0; i < 256; ++i) {
		init_dest(dest, sizeof(dest) / sizeof(dest[0]));
		ret = scl_strcpy(dest + 8, sizeof(dest) - 8 * sizeof(dest[0]), src + i);
		SCL_ASSERT(ret);
		check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + 255 - i, src[i] - 1);
		check_src(src, sizeof(src) / sizeof(src[0]));
	}

	ret = scl_strcpy(dest + 8, sizeof(src) / sizeof(src[0]), src);
	SCL_ASSERT(ret);

	ret = scl_strcpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src);
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');
}

static int min(int a, int b) {
	if (a > b) {
		a = b;
	}
	return a;
}

static void check_ncopy(void) {
	int ret, i;
	char dest[256 + 16], src[256];
	init_src(src, sizeof(src) / sizeof(src[0]));

	for (i = 0; i < 256; ++i) {
		init_dest(dest, sizeof(dest) / sizeof(dest[0]));
		ret = scl_strncpy(dest + 8, sizeof(dest) - 8 * sizeof(dest[0]), src + i, 64 + i * (1 - i % 2) / 2);
		SCL_ASSERT(ret);
		check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + min(255 - i, 64 + i * (1 - i % 2) / 2), src[i] - 1);
		check_src(src, sizeof(src) / sizeof(src[0]));
	}

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]), src, sizeof(src) / sizeof(src[0]) - 1);
	SCL_ASSERT(ret);
	check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + sizeof(src) / sizeof(src[0]) - 1, '\0');
	check_src(src, sizeof(src) / sizeof(src[0]));

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]) - 1);
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]));
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]) - 2);
	SCL_ASSERT(ret);
}

static void check_cat(void) {
	int ret, i;
	char dest[256 + 16], src[256];
	init_src(src, sizeof(src)/ sizeof(src[0]));

	for (i = 0; i < 256; ++i) {
		init_dest(dest, sizeof(dest) / sizeof(dest[0]));
		dest[8] = '\0';
		ret = scl_strcat(dest, sizeof(dest) - 8 * sizeof(dest[0]), src + i);
		SCL_ASSERT(ret);
		check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + 255 - i, src[i] - 1);
		check_src(src, sizeof(src) / sizeof(src[0]));
	}

	dest[8] = '\0';
	ret = scl_strcat(dest, sizeof(src) / sizeof(src[0]) + 8, src);
	SCL_ASSERT(ret);

	dest[8] = '\0';
	ret = scl_strcat(dest, sizeof(src) / sizeof(src[0]) + 7, src);
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');
}

static void check_ncat(void) {
	int ret, i;
	char dest[256 + 16], src[256];
	init_src(src, sizeof(src)/ sizeof(src[0]));

	for (i = 0; i < 256; ++i) {
		init_dest(dest, sizeof(dest) / sizeof(dest[0]));
		dest[8] = '\0';
		ret = scl_strncat(dest, sizeof(dest) - 8 * sizeof(dest[0]), src + i, 64 + i * (1 - i % 2) / 2);
		SCL_ASSERT(ret);
		check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + min(255 - i, 64 + i * (1 - i % 2) / 2), src[i] - 1);
		check_src(src, sizeof(src) / sizeof(src[0]));
	}

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]), src, sizeof(src) / sizeof(src[0]) - 1);
	SCL_ASSERT(ret);
	check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + sizeof(src) / sizeof(src[0]) - 1, '\0');
	check_src(src, sizeof(src) / sizeof(src[0]));

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]) - 1);
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]));
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');

	ret = scl_strncpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]) - 2);
	SCL_ASSERT(ret);
}

static void check_len(void) {
	static char src[SCL_STRING_MAX_LEN + 2];
	long i;
	int ret;
	scl_size_t len;

	for (i = 0; i < SCL_STRING_MAX_LEN + 1; ++i) {
		src[i] = 1 + i % CHAR_MAX;
	}
	src[i] = '\0';

	ret = scl_strlen(src, sizeof(src), &len);
	SCL_ASSERT(!ret && (-1 == len));
	check_src(src, i + 1);

	--i;
	src[i] = '\0';
	ret = scl_strlen(src, sizeof(src), &len);
	SCL_ASSERT(ret);
	SCL_ASSERT(len == SCL_STRING_MAX_LEN);
	check_src(src, i + 1);

	for (i = 256; i >= 0; --i) {
		src[i] = '\0';
		ret = scl_strlen(src, sizeof(src), &len);
		SCL_ASSERT(ret);
		check_src(src, i + 1);
	}
}

static void check_strmemcpy(void) {
	int ret, i;
	char dest[256 + 16], src[256];
	init_src(src, sizeof(src)/ sizeof(src[0]));

	for (i = 0; i < 256; ++i) {
		init_dest(dest, sizeof(dest) / sizeof(dest[0]));
		ret = scl_strmemcpy(dest + 8, sizeof(dest) - 8 * sizeof(dest[0]), src + i, 64 + i * (1 - i % 2) / 2);
		SCL_ASSERT(ret == (64 + i * (1 - i % 2) / 2 < (int)(sizeof(src)/ sizeof(src[0])) - i));
		if (ret) {
			check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + min(255 - i, 64 + i * (1 - i % 2) / 2), src[i] - 1);
		} else {
			SCL_ASSERT(dest[8] == '\0');
		}
		check_src(src, sizeof(src) / sizeof(src[0]));
	}

	ret = scl_strmemcpy(dest + 8, sizeof(src) / sizeof(src[0]), src, sizeof(src) / sizeof(src[0]) - 1);
	SCL_ASSERT(ret);
	check_dest(dest, sizeof(dest) / sizeof(dest[0]), 8, 8 + sizeof(src) / sizeof(src[0]) - 1, '\0');
	check_src(src, sizeof(src) / sizeof(src[0]));

	ret = scl_strmemcpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]) - 1);
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');

	ret = scl_strmemcpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]));
	SCL_ASSERT(!ret);
	SCL_ASSERT(dest[8] == '\0');

	ret = scl_strmemcpy(dest + 8, sizeof(src) / sizeof(src[0]) - 1, src, sizeof(src) / sizeof(src[0]) - 2);
	SCL_ASSERT(ret);
}

static void do_tests(void) {
	check_len();
	check_copy();
	check_ncopy();
	check_cat();
	check_ncat();
	check_strmemcpy();
}

#if defined(START_FUNC) && (START_FUNC == 1)
#	undef START_FUNC
#	define START_FUNC test_string
#endif

#if defined(START_FUNC)
	extern void START_FUNC(void) {
		do_tests();
	}
#else
	extern int main(int argc, char const **argv) {
		do_tests();
		return 0;
	}
#endif
