/* 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 <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

typedef enum {
	SCL_ITID_CHAR_SIGNED        = 0,
	SCL_ITID_CHAR_UNSIGNED      = 1,
	SCL_ITID_SHORT              = 2,
	SCL_ITID_SHORT_UNSIGNED     = 3,
	SCL_ITID_INT                = 4,
	SCL_ITID_UNSIGNED           = 5,
	SCL_ITID_LONG               = 6,
	SCL_ITID_LONG_UNSIGNED      = 7,
	SCL_ITID_LONG_LONG          = 8,
	SCL_ITID_LONG_LONG_UNSIGNED = 9,

	SCL_ITID_COUNT
} scl_type_t;

typedef enum {
	SCL_OPER_WRONG = 0,
	SCL_OPER_ADD   = 1,
	SCL_OPER_SUB   = 2,
	SCL_OPER_MULT  = 3,
	SCL_OPER_DIV   = 4,
	SCL_OPER_MOD   = 5,

	SCL_OPER_LIMIT
} scl_oper_t;

typedef enum {
	SCL_MOV_WRONG = 0,
	SCL_MOV_ADD   = 1,
	SCL_MOV_SUB   = 2,
	SCL_MOV_MULT  = 3,
	SCL_MOV_DIV   = 4,
	SCL_MOV       = 5,

	SCL_MOV_LIMIT
} scl_mov_t;

static char const *operators[] = {"", "+", "-", "*", "/", "%"};
static char const *assigns[] = {"", "+=", "-=", "*=", "/=", "="};
static struct Type {
	char sign;
	char const *t, *v, *min, *max;
} const types[] = {
	{'i', "char signed",		"c",	"SCHAR_MIN",	"SCHAR_MAX"},
	{'u', "char unsigned",		"uc",	"0",			"UCHAR_MAX"},
	{'i', "short",				"s",	"SHRT_MIN",		"SHRT_MAX"},
	{'u', "unsigned short",		"us",	"0",			"USHRT_MAX"},
	{'i', "int",				"i",	"INT_MIN",		"INT_MAX"},
	{'u', "unsigned",			"u",	"0",			"UINT_MAX"},
	{'i', "long", 				"l",	"LONG_MIN",		"LONG_MAX"},
	{'u', "unsigned long",		"ul",	"0",			"ULONG_MAX"},
	{'i', "long long",			"ll",	"LLONG_MIN",	"LLONG_MAX"},
	{'u', "unsigned long long",	"ull",	"0",			"ULLONG_MAX"}
};

static char const *oper_mnem[] = {"", "add", "sub", "mult", "div", "mod"};


FILE *out;

static void p(char const *format, ...) {
	va_list vl;
	va_start(vl, format);
		vfprintf(out, format, vl);
		fprintf(out, "\n");
	va_end(vl);
}

static void gen(int res_tid,
				char const *start, char const *st_val,
				scl_mov_t mov,
				char const *first, char const *f_val,
				scl_oper_t op,
				char const *second, char const *s_val)
{
	if (SCL_MOV != mov) {
		p("	res = %s;", st_val);
	}
	p("	cr = SCL_DO(res,%s,%s,%s,%s);", assigns[mov], f_val, operators[op], s_val);
	p("	sl_res = sl_%s(%s, %s);", oper_mnem[op], first, second);
	if (SCL_MOV != mov) {
		p("	sl_res = sl_%s(%s, sl_res);", oper_mnem[mov], start);
	}
	/* TODO more strict correctnesses comparison */
	p("	SCL_ASSERT(!cr || !(sl_res.overflow || (sl_in_range(sl_res, sl_limits[%d]) != 0)));", res_tid);
	p("	if (cr) {");
	p("		scl_res = SL(res);");
	p("		SCL_ASSERT(!cr || (sl_cmp(scl_res, sl_res) == 0));");
	p("	}\n");
}

static void sprint_sl_val(char str[], struct Type t, int i) {
	switch (i) {
	case 0:
		sprintf(str, "sl_%s_min", t.v);
		break;
	case 1:
		sprintf(str, "sl_%s_max", t.v);
		break;
	case 2:
		sprintf(str, "sl_%s_minp1", t.v);
		break;
	case 3:
		sprintf(str, "sl_%s_maxm1", t.v);
		break;
	case 4:
		sprintf(str, "sl_0");
		break;
	case 5:
		sprintf(str, "sl_m1");
		break;
	case 6:
		sprintf(str, "sl_1");
		break;
	default:
		assert(0 > 1);
		break;
	}
}

static void sprint_val(char str[], struct Type t, int i) {
	switch (i) {
	case 0:
		sprintf(str, "%s", t.min);
		break;
	case 1:
		sprintf(str, "%s", t.max);
		break;
	case 2:
		sprintf(str, "(%s + 1)", t.min);
		break;
	case 3:
		sprintf(str, "(%s - 1)", t.max);
		break;
	case 4:
		sprintf(str, "0");
		break;
	case 5:
		sprintf(str, "-1");
		break;
	case 6:
		sprintf(str, "1");
		break;
	default:
		assert(0 > 1);
		break;
	}
}

static void generate_predefined(int interface) {
	int i;
	if (interface) {
		p("extern superlong_t sl_m1, sl_0, sl_1,");
	} else {
		p("superlong_t sl_m1, sl_0, sl_1,");
	}
	for (i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
		p("	sl_%s_min, sl_%s_max, sl_%s_minp1, sl_%s_maxm1,", types[i].v, types[i].v, types[i].v, types[i].v);
	}
	p("sl_just_end;\n");

	if (!interface) {
		p("static void predefined_init(void) {");
		for (i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
			p("	sl_%s_min = SL(%s);", types[i].v, types[i].min);
			p("	sl_%s_max = SL(%s);", types[i].v, types[i].max);
			p("	sl_%s_minp1 = SL(%s + 1);", types[i].v, types[i].min);
			p("	sl_%s_maxm1 = SL(%s - 1);\n", types[i].v, types[i].max);
		}
		p("	sl_m1 = SL(-1);");
		p("	sl_0 = SL(0);");
		p("	sl_1 = SL(1);");
		p("}\n");
	}
}

static void gen_func(scl_mov_t mov, scl_type_t t) {
	scl_oper_t op;
	char first[128], second[128], f_val[128], s_val[128];
	int i, v;

	assert((t >= 0) && (t < SCL_ITID_COUNT));
	p("extern void check_%d_%d(void) {", mov, t);
	if (t > SCL_ITID_LONG_UNSIGNED) {
		p("#	if !defined(SCL_USE_LONG_LONG) && ((__STDC_VERSION__ >= 199901) || (__cplusplus >= 201103L))");
	}
	p("	%s res;", types[t].t);
	p("	int cr;");
	p("	superlong_t sl_res, scl_res;");
	v = 7 - t % 2 * 3;
	for (op = (scl_oper_t)(SCL_OPER_WRONG + 1); op < SCL_OPER_LIMIT; op = (scl_oper_t)(op + 1)) {
		for (i = 0; i < v * v; ++i) {
			sprint_sl_val(first, types[t], i % v);
			sprint_sl_val(second, types[t], i / v);
			sprint_val(f_val, types[t], i % v);
			sprint_val(s_val, types[t], i / v);
			gen(t,
				"sl_0", "0",
				mov,
				first, f_val,
				op,
				second, s_val
			);
			if (SCL_MOV != mov) {
				gen(t,
					"sl_1", "1",
					mov,
					first, f_val,
					op,
					second, s_val
				);
				if (t % 2 == 0) {
					gen(t,
						"sl_m1", "-1",
						mov,
						first, f_val,
						op,
						second, s_val
					);
				}
			}
		}
	}
	if (t > SCL_ITID_LONG_UNSIGNED) {
		p("#	endif");
	}
	p("}\n");
}

extern int main(int argc, char const **argv) {
	scl_mov_t mov;
	scl_type_t type;
	char name[4096];
	int prefix_len;
	int ret;

	ret = EXIT_SUCCESS;

	if (argc > 1) {
		prefix_len = strlen(argv[1]);
		if (prefix_len > sizeof(name) / sizeof(name[0]) - 16) {
			ret = EXIT_FAILURE;
		} else {
			memcpy(name, argv[1], prefix_len);
		}
	} else {
		prefix_len = 0;
	}

	if (EXIT_SUCCESS == ret) {
		for (type = SCL_ITID_INT; type < SCL_ITID_COUNT; ++type) {
			for (mov = SCL_MOV_LIMIT - 1; mov > SCL_MOV_WRONG; --mov) {
				sprintf(name + prefix_len, "%d_%d.c", mov, type);
				out = fopen(name, "w");
				if (NULL == out) {
					fprintf(stderr, "Can not open file %s\n", name);
					ret = EXIT_FAILURE;
				} else {
					p("#include \"scl/arithmetic.h\"\n");
					p("#include \"superlong.h\"\n");
					generate_predefined(1);
					gen_func(mov, type);
					fclose(out); out = NULL;
				}
			}
		}
		sprintf(name + prefix_len, "main.c");
		out = fopen(name, "w");
		if (NULL == out) {
			fprintf(stderr, "Can not open file %s\n", name);
			ret = EXIT_FAILURE;
		} else {
			p("#include \"scl/arithmetic.h\"\n");
			p("#include \"superlong.h\"\n");
			generate_predefined(0);
			if (argc > 2) {
				p("extern int %s(void) {", argv[2]);
			} else {
				p("extern int main(int argc, char **argv) {");
			}
			p("	extern void ");
			for (type = SCL_ITID_INT; type < SCL_ITID_COUNT; ++type) {
				for (mov = SCL_MOV_LIMIT - 1; mov > SCL_MOV_WRONG; --mov) {
					p("		check_%d_%d(void),", mov, type);
				}
			}
			p("	just_end(void);\n");

			p("	sl_init(argc > 1);");
			p("	predefined_init();");
			for (type = SCL_ITID_INT; type < SCL_ITID_COUNT; ++type) {
				for (mov = SCL_MOV_LIMIT - 1; mov > SCL_MOV_WRONG; --mov) {
					p("\tcheck_%d_%d();", mov, type);
				}
			}
			p("	return 0;\n}");
			fclose(out);
		}
	}
	return ret;
}

