#include <jansson.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>

#include "pdp11_defs.h"
#include "pdp11_cpumod.h"
#include "sim_defs.h"

//extern t_stat sim_brk_init(void);
//extern t_stat sim_brk_set(t_addr loc, int32 sw, int32 ncnt, CONST char *act);
extern t_stat sim_instr();
//extern uint32 sim_brk_summ;
extern void PWriteW(int32 data, int32 addr);
extern int32 PReadW(int32 addr);
extern int32 REGFILE[6][2];
extern int32 STACKFILE[4];
extern int32 saved_PC;
extern int32 PSW;
extern t_stat cpu_reset(DEVICE *dptr);
extern DEVICE cpu_dev;
extern int32 STACKFILE[4];
extern int32 sim_interval;
extern t_stat reason;
extern int32 MMR1;
extern int32 MMR2;
extern CPUTAB cpu_tab[];

struct __mem_writes {
        int32_t addr;
        uint8_t data;
};

extern struct __mem_writes *mem_writes;
extern int n_mem_writes;
extern void reset_mem_writes();
extern void init_mem_writes();

struct mem_t {
	uint32_t addr;
	uint16_t value;
};

int is_prime(int n) {
    if (n <= 1)
	    return 0;
    for(int i = 2; i*i <= n; i++) {
	    if (n % i == 0)
		    return 0;
    }
    return 1;
}

int file_exist(const char *const filename)
{
	struct stat st;
	return stat(filename, &st) == 0;
}

uint16_t test_values[65536];
int n_test_values = 0;

void generate_test_values()
{
	for(int i=1; i<65536; i+=28) {
		if (is_prime(i))
			test_values[n_test_values++] = i;
	}

	for(int i=1; i<16; i++)
		test_values[n_test_values++] = 1 << i;

	test_values[n_test_values++] = 0;
	test_values[n_test_values++] = 255;
	test_values[n_test_values++] = 32767;
	test_values[n_test_values++] = 65535;
}

void put_mem(json_t *memory_i, struct mem_t *mem, int n_mem)
{
	for(size_t i=0; i<n_mem; i++) {
		char buffer1[16];
		char buffer2[16];
		json_t *put_mem_i_0 = json_object();
		json_t *put_mem_i_1 = json_object();
		PWriteW(mem[i].value, mem[i].addr);

		if (mem[i].addr & 1)
			printf("FAIL\n");

		sprintf(buffer1, "%06o", mem[i].addr);
		json_object_set(put_mem_i_0, buffer1, json_integer(mem[i].value & 255));
		json_array_append_new(memory_i, put_mem_i_0);

		sprintf(buffer2, "%06o", mem[i].addr + 1);
		json_object_set(put_mem_i_1, buffer2, json_integer(mem[i].value >> 8));
		json_array_append_new(memory_i, put_mem_i_1);
	}
}

json_t *generate_test(int *const id, struct mem_t *mem, size_t n_mem, int run_n_instructions, struct mem_t *setup_mem, int n_setup_mem)
{
	json_t *before = json_object();

	json_object_set(before, "PC", json_integer(saved_PC));

	json_object_set(before, "run-n-instructions", json_integer(run_n_instructions));

	json_object_set(before, "stack-0", json_integer(STACKFILE[0]));
	json_object_set(before, "stack-1", json_integer(STACKFILE[1]));
	json_object_set(before, "stack-2", json_integer(STACKFILE[2]));
	json_object_set(before, "stack-3", json_integer(STACKFILE[3]));

	json_object_set(before, "mmr1", json_integer(MMR1));
	json_object_set(before, "mmr2", json_integer(MMR2));

	json_t *memory_i = json_array();
	put_mem(memory_i, mem, n_mem);
	if (setup_mem)
		put_mem(memory_i, setup_mem, n_setup_mem);
	json_object_set(before, "memory", memory_i);

	for(int k=0; k<6; k++) {
		char name[16];

		sprintf(name, "reg-%d.%d", k, 0);
		json_object_set(before, name, json_integer(REGFILE[k][0]));

		sprintf(name, "reg-%d.%d", k, 1);
		json_object_set(before, name, json_integer(REGFILE[k][1]));
	}

	json_object_set(before, "PSW", json_integer(PSW));

	// do
	int failed = 0;
	for(int k=0; k<run_n_instructions; k++)
		failed |= sim_instr() != 0;

	json_t *after = json_object();
	json_object_set(after, "PC", json_integer(saved_PC));

	for(int k=0; k<6; k++) {
		char name[16];

		sprintf(name, "reg-%d.%d", k, 0);
		json_object_set(after, name, json_integer(REGFILE[k][0]));

		sprintf(name, "reg-%d.%d", k, 1);
		json_object_set(after, name, json_integer(REGFILE[k][1]));
	}

	json_object_set(after, "PSW", json_integer(PSW));

	json_object_set(after, "stack-0", json_integer(STACKFILE[0]));
	json_object_set(after, "stack-1", json_integer(STACKFILE[1]));
	json_object_set(after, "stack-2", json_integer(STACKFILE[2]));
	json_object_set(after, "stack-3", json_integer(STACKFILE[3]));

	json_object_set(after, "mmr1", json_integer(MMR1));
	json_object_set(after, "mmr2", json_integer(MMR2));

	json_t *memory_o = json_array();

	for(int i=0; i<n_mem_writes; i++) {
		char buffer[16];
		sprintf(buffer, "%06o", mem_writes[i].addr);

		json_t *get_mem = json_object();
		json_object_set(get_mem, buffer, json_integer(mem_writes[i].data));
		json_array_append_new(memory_o, get_mem);
	}

	json_object_set(after, "memory", memory_o);

	json_t *collection = json_object();
	json_object_set(collection, "id", json_integer(*id));
	(*id)++;
	json_object_set(collection, "before", before);
	json_object_set(collection, "after", after);

	/*
	if (failed) {
		json_decref(collection);
		return NULL;
	}
	*/

	return collection;
}

void init_simh()
{
	// reset_all(0);  is this required?
	cpu_reset(&cpu_dev);
	sim_interval = 32767;
	reset_mem_writes();
	reason = 0;

	cpu_model = MOD_1170;
	cpu_type = 1u << cpu_model;
	cpu_opt = cpu_tab[cpu_model].std;
	cpu_set_bus (cpu_opt);
}

void randomize_registers_all_values()
{
	for(int k=0; k<6; k++) {
		REGFILE[k][0] = rand() & 0xffff;
		REGFILE[k][1] = rand() & 0xffff;
	}
}

void init_stack_registers()
{
	STACKFILE[0] = STACKFILE[1] = STACKFILE[2] = STACKFILE[3] = 010000;
}

void produce_set_register(const uint16_t instr, const uint16_t psw, int *const id, json_t *const out)
{
	for(int v1=0; v1<n_test_values; v1++) {
		for(int v2=0; v2<n_test_values; v2++) {
			init_simh();

			saved_PC = 0100;

			randomize_registers_all_values();
			REGFILE[0][0] = REGFILE[0][1] = test_values[v1];
			REGFILE[1][0] = REGFILE[1][1] = test_values[v2];

			init_stack_registers();

			struct mem_t mem[1] = {
				{ 0100, instr }
			};

			PSW = psw;

			json_t *obj = generate_test(id, mem, 1, 1, NULL, 0);
			if (obj)
				json_array_append_new(out, obj);
		}
	}
}

void produce_set_register_indirect(const uint16_t instr, const uint16_t psw, int *const id, json_t *const out)
{
	if (((instr >> 3) & 7) != 1 || (instr & 7) != 1) {
		printf("Not an R1 target!");
		return;
	}

	for(int v1=0; v1<n_test_values; v1++) {
		for(int v2=0; v2<n_test_values; v2++) {
			init_simh();

			saved_PC = 0100;

			randomize_registers_all_values();
			REGFILE[0][0] = REGFILE[0][1] = test_values[v1];
			REGFILE[1][0] = REGFILE[1][1] = 01002;

			init_stack_registers();

			struct mem_t mem[2] = {
				{ 0100, instr },
				{ 01002, test_values[v2] }
			};

			PSW = psw;

			json_t *obj = generate_test(id, mem, 2, 1, NULL, 0);
			if (obj)
				json_array_append_new(out, obj);
		}
	}
}

void dump_json(const char *const filename, json_t *const j)
{
	FILE *fh = fopen(filename, "w");
	json_dumpf(j, fh, JSON_INDENT(1));
	fclose(fh);

	json_decref(j);
}

void emit_branch_instructions()
{
	printf("Branch instructions\n");
	const char *const filename = "pdp1170-valtest-COND-BRANCHES.json";
	if (file_exist(filename))
		return;
	int id = 0;
	json_t *out = json_array();
	for(int group=0; group<2; group++) {  // word/byte
		for(int bt=0; bt<8; bt++) {
			if (group == 0 && bt == 0)  // SWAB
				continue;
			for(int direction=0; direction<4; direction++) {
				uint16_t instr = (group << 15) | (bt << 8);

				if (direction & 2)
					instr |= 62 + (direction & 1);
				else
					instr |= 218 + (direction & 1);

				for(int psw_val=0; psw_val<16; psw_val++) {
					init_simh();

					saved_PC = 0100;

					randomize_registers_all_values();

					init_stack_registers();

					struct mem_t mem[1] = {
						{ 0100, instr }
					};

					PSW = psw_val;

					json_t *obj = generate_test(&id, mem, 1, 1, NULL, 0);
					if (obj)
						json_array_append_new(out, obj);
				}
			}
		}
	}
	dump_json(filename, out);
}

void emit_condition_sets()
{
	printf("Condition set instructions\n");
	const char *const filename = "pdp1170-valtest-CONDITIONS.json";
	if (file_exist(filename))
		return;
	int id = 0;
	json_t *out = json_array();
	for(int condition=0; condition<16; condition++) {
		uint16_t instr = 0240 + condition;

		for(int psw_val=0; psw_val<16; psw_val++) {
			init_simh();

			saved_PC = 0100;

			randomize_registers_all_values();

			init_stack_registers();

			struct mem_t mem[1] = {
				{ 0100, instr }
			};

			PSW = psw_val;

			json_t *obj = generate_test(&id, mem, 1, 1, NULL, 0);
			if (obj)
				json_array_append_new(out, obj);
		}
	}
	dump_json(filename, out);
}

void emit_add_sub_c()
{
	printf("ADD/SUB instructions\n");
	const char *const filename = "pdp1170-valtest-ADD_SUB.json";
	if (file_exist(filename))
		return;
	int id = 0;
	json_t *out = json_array();
	for(int group=0; group<2; group++) {
		for(int word=0; word<2; word++) {
			uint16_t instr = (6 << 12 /* instr */) | (word << 15 /* ADD/SUB */) | (1 << 6 /* src=R1 */);

			produce_set_register(instr, 0, &id, out);
		}
	}
	dump_json(filename, out);
}

void emit_single_operand_instructions()
{
	printf("single operand instructions\n");
	int groups[] = { 00001, 00003, 00050, 01050, 00051, 01051, 00052, 01052, 00053, 01053, 00054, 01054, 00055, 01055, 00056, 01056, 00057, 01057, 00060, 01060, 00061, 01061, 00062, 01062, 00063, 01063, 00067, -1 };

	for(int dst=0; dst<2; dst++) {
		for(int group=0; group<27; group++) {
			char filename[128] = { 0 };
			sprintf(filename, "pdp1170-valtest-SINGLE_OPERAND-%o-%d.json", groups[group], dst);
			if (file_exist(filename))
				continue;
			printf("%s       \r", filename);
			fflush(NULL);

			int id = 0;
			json_t *out = json_array();

			uint16_t instr = 0;

			if (groups[group] == 00001 || dst == 1)  // JMP can't jump to a register
				instr = (groups[group] << 6) | 010;  // (R0)
			else
				instr = (groups[group] << 6) | 001;  // R0

			for(int v1=0; v1<n_test_values; v1++) {
				for(int psw_val=0; psw_val<2; psw_val++) {
					init_simh();

					saved_PC = 0100;

					randomize_registers_all_values();
					init_stack_registers();

					PSW = psw_val;

					struct mem_t mem[2] = {
						{ 0100, instr },
						{ 02000, test_values[v1] }
					};

					json_t *obj = NULL;

					if (groups[group] == 00001 || dst == 1) {
						REGFILE[0][0] = REGFILE[0][1] = 02000;
						obj = generate_test(&id, mem, 2, 1, NULL, 0);
					}
					else {
						REGFILE[0][0] = REGFILE[0][1] = test_values[v1];
						obj = generate_test(&id, mem, 1, 1, NULL, 0);
					}

					if (obj)
						json_array_append_new(out, obj);
				}
			}
			dump_json(filename, out);
		}
	}
}

void emit_cmp()
{
	printf("CMP instructions\n");
	const char *const filename = "pdp1170-valtest-CMP.json";
	if (file_exist(filename))
		return;
	int id = 0;
	json_t *out = json_array();
	for(int group=0; group<2; group++) {
		uint16_t instr = 0;
		int      word  = group & 1;

		if (group == 0 || group == 1)
			instr = (2 << 12 /* instr */) | (word << 15 /* CMPb/CMPw */) | (1 << 6 /* src=R1 */);

		for(int psw_val=0; psw_val<2; psw_val++)
			produce_set_register(instr, psw_val, &id, out);
	}
	dump_json(filename, out);
}

void emit_add_double_oper_instr()
{
	printf("addition double operand instructions\n");
	const char *const filename = "pdp1170-valtest-DOUB_OPER-INSTR.json";
	if (file_exist(filename))
		return;
	int id = 0;
	json_t *out = json_array();
	for(int group=0; group<8; group++) {
		uint16_t instr = (7 << 12) | (group << 9 /* instr */) | (2 << 6 /* src=R2 */);

		if (group == 5 || group == 6)
			continue;

		produce_set_register(instr, 0, &id, out);
	}
	dump_json(filename, out);
}

void emit_bit_instructions()
{
	printf("bit instructions\n");
	for(int group=3; group<6; group++) {
		char filename[64];
		sprintf(filename, "pdp1170-valtest-BIT-INSTRUCTIONS-%d.json", group);
		if (file_exist(filename))
			continue;
		int id = 0;
		json_t *out = json_array();
		for(int word=0; word<2; word++) {
			uint16_t instr = (word << 15) | (group << 12) | (1 << 6 /* src=R1 */);

			for(int psw_val=0; psw_val<2; psw_val++) {
				produce_set_register(instr, psw_val, &id, out);

				if (group == 4 || group == 5) {
					uint16_t instr2 = (word << 15) | (group << 12) | 011 /* target is '(R1)' */;
					produce_set_register_indirect(instr2, psw_val, &id, out);
				}
			}
		}
		dump_json(filename, out);
	}
}

void emit_misc_operations()
{
	printf("misc instructions\n");

	const char *const filename = "pdp1170-valtest-MISC.json";
	if (file_exist(filename) == 0) {
		int id = 0;
		json_t *out = json_array();

		int groups[] = { 2 /* RTI */, 6 /* RTT */ };

		for(int group=0; group<2; group++) {
			for(int psw_val=0; psw_val<65536; psw_val++) {
				if ((psw_val & 0174377) != psw_val)
					continue;

				uint16_t instr = groups[group];

				init_simh();

				saved_PC = 0100;
				randomize_registers_all_values();
				STACKFILE[0] = STACKFILE[1] = STACKFILE[2] = STACKFILE[3] = 07774;

				struct mem_t mem[3] = {
					{ 0100, instr },
					{ 07776, 01234 },
					{ 07774, 06420 }
				};

				PSW = psw_val;

				//if (instr == 2)
				//	printf("PSW=%06o, ", PSW);

				json_t *obj = generate_test(&id, mem, 3, 1, NULL, 0);
				if (obj)
					json_array_append_new(out, obj);
			}
		}

		for(int group=0; group<3; group++) {
			uint16_t instr = 0;
			if (group == 0)
				instr = 0100 | 010;  // JMP (R0)
			else if (group == 1)
				instr = 04000 | 0110;  // JSR R1,(R0)
			else if (group == 2)
				instr = 0200 | 01;  // RTS (R1)

			produce_set_register(instr, 0, &id, out);
		}

		dump_json(filename, out);
	}

	// TRAP
	const char *const filename2 = "pdp1170-valtest-MISC2.json";
	if (file_exist(filename2) == 0) {
		int id = 0;
		json_t *out = json_array();

		uint16_t instr = 0104420;  // TRAP #020

		init_simh();

		saved_PC = 0100;
		randomize_registers_all_values();
		init_stack_registers();

		struct mem_t mem[1] = {
			{ 0100, instr }
		};

		PSW = 012;

		json_t *obj = generate_test(&id, mem, 1, 1, NULL, 0);
		if (obj)
			json_array_append_new(out, obj);

		dump_json(filename2, out);
	}
}

/*
  	MOV     #0172340,R0
	MOV     #0000,(R0)  ; 000000-020000 (K,I) will be mapped to physical address 00000
	MOV     #0172300,R0
	MOV     #077006,(R0)  ; 000000-020000 full access

	MOV     #0172366,R0
	MOV     #0400,(R0)  ; 060000-080000 (K,D) will be mapped to physical address 04000
	MOV     #0172326,R0
	MOV     #077006,(R0)  ; 000000-020000 full access

	MOV     #0177572,R0
	BIS     #513,(R0)  ; enable MMU & trap

	MOV     #0100,R0  ; JMP to main code
	JMP     (R0)
*/
#define MMU_SETUP_PROG \
{ 0500, 012700 }, \
{ 0502, 0172340 }, \
{ 0504, 012710 }, \
{ 0506, 0000000 }, \
{ 0510, 012700 }, \
{ 0512, 0172300 }, \
{ 0514, 012710 }, \
{ 0516, 0077006 }, \
{ 0520, 012700 }, \
{ 0522, 0172366 }, \
{ 0524, 012710 }, \
{ 0526, 0000400 }, \
{ 0530, 012700 }, \
{ 0532, 0172326 }, \
{ 0534, 012710 }, \
{ 0536, 0077006 }, \
{ 0540, 012700 }, \
{ 0542, 0177572 }, \
{ 0544, 052710 }, \
{ 0546, 0000513 }, \
{ 0550, 012700 }, \
{ 0552, 0000100 }, \
{ 0554, 000110 }, \
{ 0004, 02300 }, /* trap address */ \
{ 0006, 0123456 }, /* PSW */ \
{ 0034, 02500 }, /* trap address */ \
{ 0036, 0177777 }, /* PSW */ \

void emit_mov()
{
	printf("mov instructions\n");

	struct mem_t setup[27] = {
		MMU_SETUP_PROG
	};

	for(int d_i=0; d_i<2; d_i++) {
		char filename[64];
		sprintf(filename, "pdp1170-valtest-MOV_%d.json", d_i);

		if (file_exist(filename) == 0) {
			int id = 0;
			json_t *out = json_array();

			{
				init_simh();
				saved_PC = 0100;
				randomize_registers_all_values();
				init_stack_registers();
				PSW = 0;

				struct mem_t mem[7] = {
					{ 0100, 012705 },  // MOV #01234,R5
				        { 0102, 001234 },
					{ 0104, 010525 },  // MOV R5,(R5)+
				};
				json_t *obj = generate_test(&id, mem, 3, 2, NULL, 0);
				if (obj)
					json_array_append_new(out, obj);
			}

			uint16_t test_vals[] = { 0, 127, 128, 255, 256, 65535 };

			for(int i=0; i<24; i++) {
				init_simh();
				saved_PC = 0100;
				randomize_registers_all_values();
				init_stack_registers();
				PSW = 0;

				struct mem_t mem[7] = {
					{ 0100, 012700  },
					{ 0102, test_vals[i % 6] },
					{ 0104, 012701 },
					{ 0106, 02000 },
					{ 0110, 0110051 },  // @-(R1)
					{ 01776, 03000 },
					{ 03000, 0 }
				};

				int set = i / 6;
				if (set == 1)
					mem[4].value = 0010051;
				else if (set == 2) {
					mem[4].value = 0010031;  // @(R1)+
					mem[5].addr = 02000;
				}
				else if (set == 3) {
					mem[4].value = 0110031;  // @(R1)+
					mem[5].addr = 02000;
				}

				json_t *obj = generate_test(&id, mem, 7, 3, NULL, 0);
				if (obj)
					json_array_append_new(out, obj);
			}

			for(int i=0; i<12; i++) {
				init_simh();
				saved_PC = 0100;
				randomize_registers_all_values();
				init_stack_registers();
				PSW = 0;

				struct mem_t mem[7] = {
					{ 0100, 012700  },
					{ 0102, test_vals[i % 6] },
					{ 0104, 012701 },
					{ 0106, 02000 },
					{ 0110, 0110061 },  // @(R1)
					{ 0112, 0000004 },
					{ 02004, 012344 },
				};

				int set = i / 6;
				if (set == 1) {
					mem[5].value = 0100004;
					mem[6].addr  = 0102004;
				}

				json_t *obj = generate_test(&id, mem, 7, 3, NULL, 0);
				if (obj)
					json_array_append_new(out, obj);
			}

			{
				init_simh();
				saved_PC = 0100;
				randomize_registers_all_values();
				init_stack_registers();
				PSW = 0;

				struct mem_t mem[7] = {
					{ 0100, 012701 },
					{ 0102, 001000 },
					{ 0104, 012771 },  // @X(R7)
					{ 0106, 002222 },
					{ 0110, 001000 },
					{ 02000, 012344 },
				};
				json_t *obj = generate_test(&id, mem, 6, 2, NULL, 0);
				if (obj)
					json_array_append_new(out, obj);
			}

			for(int i=0; i<48; i++) {
				init_simh();
				randomize_registers_all_values();
				init_stack_registers();
				PSW = 0;

				struct mem_t mem[5] = {
					{ 0100, 012700   },  // MOV #random,(R0)
					{ 0102, test_vals[i % 6] },
					{ 0104, 012701   },  // MOV #0, (R1)
					{ 0106, 0        },
					{ 0110, 0110001  },  // MOV R0, R1
				};

				int set = i / 6;
				if (set == 1)  // R1
					mem[4].value = 0010001;  // MOVB R0,R1
				else if (set == 2) {  // (R1)
					mem[3].value = 0060000;  // MOV #060000,R1
					mem[4].value = 0110011;  // MOV R0,(R1)
				}
				else if (set == 3) {  // (R1)
					mem[3].value = 0060000;  // MOV #060000,R1
					mem[4].value = 0010011;  // MOVB R0,(R1)
				}
				else if (set == 4) {  // (R1)+
					mem[3].value = 0060000;  // MOV #060000,R1
					mem[4].value = 0110021;
				}
				else if (set == 5) {  // (R1)+
					mem[3].value = 0060000;
					mem[4].value = 0010021;
				}
				else if (set == 6) {  // -(R1)
					mem[3].value = 0060000;
					mem[4].value = 0110041;
				}
				else if (set == 7) {  // -(R1)
					mem[3].value = 0060000;
					mem[4].value = 0010041;
				}

				json_t *obj = NULL;
				if (d_i == 1) {
					saved_PC = 0500;
					obj = generate_test(&id, mem, 5, 3 + 12, setup, 27);
				}
				else {
					saved_PC = 0100;
					obj = generate_test(&id, mem, 5, 3, NULL, 0);
				}

				if (obj)
					json_array_append_new(out, obj);
			}

			dump_json(filename, out);
		}
	}
}

void produce_validation_tests()
{
	srand(123);  // for reproducability

	init_mem_writes();

	generate_test_values();

	emit_branch_instructions();  // conditional_branch_instructions*
	emit_condition_sets();  // condition_code_operations*
	emit_add_double_oper_instr();  // additional_double_operand_instructions*
	emit_add_sub_c();  // double_operand_instructions*, single_operand_instructions*
	emit_single_operand_instructions();  // single_operand_instructions
	emit_bit_instructions();  // double_operand_instructions
	emit_cmp();  // double_operand_instructions
	emit_misc_operations();
	emit_mov();

	// TODO:
	// - double_operand_instructions: MOV
	// - single_operand_instructions: MTPS, MFPI, MFPD, MTPI, MTPD, MFPS
	// ...
}
