/* sim.c */

#include <stdio.h>
#include <string.h>
#include "corewar.h"
#include "sim.h"

#define  THR_LEAF  2

#define  INRANGE\
    ((((unsigned)(addr2 - xeq_addr + w_radius) <= (unsigned)(2*w_radius))) ||\
     ((unsigned)(addr2 - xeq_addr + (coresize - w_radius)) >=\
      (unsigned)(2*(coresize-w_radius))))

typedef struct  arg_struct  {
	aval_t   addr, val;
} arg_t;

static tblock_t  *new_tblock();
static tlist_t  *free_tlist(tlist_t  *tl);
static void  free_tblock(tblock_t *tb);
static arg_t  fetch_arg(amode_t am, aval_t val, aval_t cur_addr, int cur_id);
static int  instruction(thread_t *th, op_t op, amode_t am1, aval_t val1,
                        amode_t am2, aval_t val2);

tlist_t  *sim_tlist = NULL;

static core_el_t  empty_core = {NULL, 0, 0, JTINDEX(op_dat, am_dir, am_dir)};


unsigned  new_core()  {
	unsigned  i;

	empty_core.instr = jmptable[empty_core.index];

	if (core != NULL)
		free(core);
	
	while (sim_tlist != NULL)
		sim_tlist = free_tlist(sim_tlist);
	core = (core_el_t *)tmalloc(coresize * sizeof(core_el_t));
	for (i = 0;  i < coresize;  ++i)
		core[i] = empty_core;
	return(TRUE);
}


void  set_first(int  id)  {
	while (sim_tlist->id != id)
		sim_tlist = sim_tlist->next;
}


void  clear_core(aval_t start, unsigned len, int id)  {
	tlist_t  *idt = sim_tlist, *new_idt;

	while (len--)  {
		core[start] = empty_core;
#ifndef  NO_DISP
		if (interactive)
			di_drawaddr(start, DI_EMPTY);
#endif  /* NO_DISP */
		if (++start == coresize)
			start = 0;
	}
	while (idt->id != id)
		idt = idt->next;
	new_idt = free_tlist(idt);
	if (idt == sim_tlist)
		sim_tlist = new_idt;
}


static tlist_t  *free_tlist(tlist_t  *tl)  {
	tlist_t  *ntl = tl->next;

	tl->prev->next = ntl;
	ntl->prev = tl->prev;
	free_tblock(tl->tblock);
	free(tl);
	if (tl == ntl)
		return(NULL);
	else
		return(ntl);
}


static void  free_tblock(tblock_t *tb)  {
	if (tb != NULL)  {
		free_tblock(tb->next);
		free(tb);
	}
}


unsigned  load_core(program_t *prog, aval_t ld_start, unsigned id)  {
	aval_t  addr = ld_start;
	unsigned  i;
	tlist_t  *new_tlist, *old_tlist;

	new_tlist = sim_tlist;
	if (new_tlist == NULL)  {
		new_tlist = sim_tlist = (tlist_t *)tmalloc(sizeof(tlist_t));
		sim_tlist->next = sim_tlist;
		sim_tlist->prev = sim_tlist;
	} else  {
		while (new_tlist->next != sim_tlist)  {
			if (new_tlist->id == id)  {
				sprintf(cwerrstr, "id %d is taken already.", id);
				return(FALSE);
			}
			if ((new_tlist->id < id) && (new_tlist->next->id > id))
				break;
			new_tlist = new_tlist->next;
		}
		if (new_tlist->id == id)  {
			sprintf(cwerrstr, "id %d is taken already.", id);
			return(FALSE);
		}
		old_tlist = new_tlist->next;
		new_tlist->next = (tlist_t *)tmalloc(sizeof(tlist_t));
		new_tlist = new_tlist->next;
		new_tlist->next = old_tlist;
		new_tlist->prev = old_tlist->prev;
		old_tlist->prev = new_tlist;
		if (id < sim_tlist->id)
			sim_tlist = new_tlist;
	}

	new_tlist->tblock = new_tblock();
	new_tlist->tblock->threads[0].next = new_tlist->tblock->threads + 0;
	new_tlist->tblock->threads[0].prev = new_tlist->tblock->threads + 0;

	new_tlist->head = new_tlist->tblock->threads + 0;
	new_tlist->empty = new_tlist->tblock->threads + 1;

	new_tlist->count = 1;
	new_tlist->id = id;
	new_tlist->ld_start = ld_start;

	if ((new_tlist->tblock->threads[0].addr = addr + prog->startaddr) >=
			coresize)
		new_tlist->tblock->threads[0].addr -= coresize;

	for (i = 0;  i < prog->proglen;  ++i)  {
		int  cindex;

		core[addr] = prog->listing[i];
		if (core[addr].val1 == AVAL_PROGID)
			core[addr].val1 = ld_start;
		if (core[addr].val2 == AVAL_PROGID)
			core[addr].val2 = ld_start;
		cindex = core[addr].index;
#ifndef  NO_DISP
		if (interactive && ((JTOP(cindex) != op_dat) ||
												(JTAM1(cindex) > am_ind) ||
												(JTAM2(cindex) > am_ind) ||
												core[addr].val1 || core[addr].val2))
			di_drawaddr(addr, DI_DAT + (id * DI_PLSIZ));
#endif  /* NO_DISP */
		if (++addr >= coresize)
			addr -= coresize;
	}
#ifndef  NO_DISP
	if (interactive)
		di_drawaddr(new_tlist->tblock->threads[0].addr, (id * DI_PLSIZ) + DI_XEQ);
#endif  /* NO_DISP */
	return(TRUE);
}


static tblock_t  *new_tblock()  {
	tblock_t  *ntb;
	unsigned  i;

	ntb = (tblock_t *)tmalloc(sizeof(tblock_t));
	for (i = 0;  i < TBLOCK_SIZE - 1;  ++i)
		ntb->threads[i].next = ntb->threads + i + 1;
	ntb->threads[TBLOCK_SIZE - 1].next = NULL;
	ntb->next = NULL;
	return(ntb);
}


int  simulate(unsigned cycles, unsigned progs)  {
	unsigned  cycleft, procs;
	thread_t  *th, *nxt_th;
	core_el_t  *tcore = core;
	aval_t  tw_radius = w_radius, tcoresize = coresize;

	cycleft = cycles * progs;
	for (cycleft = cycles * progs;  cycleft > 0;  --cycleft)  {
		th = sim_tlist->head;
		nxt_th = th->next;
		procs = tcore[th->addr].instr(th, tcore, tw_radius, tcoresize,
																	th->addr,
																	tcore[th->addr].val1, tcore[th->addr].val2);
		sim_tlist->head = nxt_th;
		if ((sim_tlist->count += procs) == 0)  {
			int  newprogs = progs - 1;

			sim_tlist = free_tlist(sim_tlist);
			if (newprogs <= 1)
				break;
			cycleft = ((cycleft * newprogs + newprogs) / progs);
			progs = newprogs;
		} else
			sim_tlist = sim_tlist->next;
	}
	return(cycles - (cycleft / progs));
}


aval_t  sim_nextaddr()  {
	return(sim_tlist->head->addr);
}


int  sim_nextplayer()  {
	return(sim_tlist->id);
}


unsigned  sim_cyc()  {
	thread_t  *th, *nxt_th;
	unsigned  nprocs;

	th = sim_tlist->head;
	nprocs = sim_tlist->count;
	nxt_th = th->next;
	nprocs += instruction(th, JTOP(core[th->addr].index),
												JTAM1(core[th->addr].index),
												core[th->addr].val1,
												JTAM2(core[th->addr].index),
												core[th->addr].val2);
	if (nprocs == 0)
		sim_tlist = free_tlist(sim_tlist);
	else  {
		sim_tlist->head = nxt_th;
		sim_tlist->count = nprocs;
		sim_tlist = sim_tlist->next;
	}
	return(nprocs);
}


#define  IMM1  (am1 == am_imm)
#define  IMM2  (am2 == am_imm)
#define  INTERACTIVE  TRUE
#ifdef  NO_DISP
#define  DRAW(a, b)
#else  /* NO_DISP */
#define  DRAW(a, b)  di_drawaddr(a, b)
#endif  /* NO_DISP */

static int  instruction(thread_t *th, op_t op, amode_t am1, aval_t argval1,
                        amode_t am2, aval_t argval2)  {
	arg_t  arg;
	aval_t  xeq_addr = th->addr, addr1, val1, addr2, val2;
	int  id = sim_tlist->id;

	arg = fetch_arg(am1, argval1, xeq_addr, id);
	addr1 = arg.addr;  val1 = arg.val;
	arg = fetch_arg(am2, argval2, xeq_addr, id);
	addr2 = arg.addr;  val2 = arg.val;
	switch(op)  {
	case op_dat:
		/* instruction op_dat US_NONE US_NONE */
		DRAW(xeq_addr, DI_DEAD);
		th->next->prev = th->prev;
		th->prev->next = th->next;
		th->next = sim_tlist->empty;
		sim_tlist->empty = th;
		return(-1);
		/* instruction done */
	case op_mov:
		/* instruction op_mov US_VAL (US_ADDR|US_RANGE) */
		if (INRANGE)  {
			core[addr2].val2 = val1;
			if (!IMM1 && !IMM2)  {
				core[addr2].instr = core[addr1].instr;
				core[addr2].val1 = core[addr1].val1;
				if (INTERACTIVE)
					core[addr2].index = core[addr1].index;
			}
			DRAW(addr2, DI_DAT + (id * DI_PLSIZ));
		}
		DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
		if (++xeq_addr == coresize)
			xeq_addr = 0;
		DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		th->addr = xeq_addr;
		return(0);
		/* instruction done */
	case op_add:
		/* instruction op_add US_VAL (US_VAL|US_RANGE) */
		if (INRANGE)  {
			if ((val2 += val1) >= coresize)
				val2 -= coresize;
			core[addr2].val2 = val2;
			if (!IMM1 && !IMM2)  {
				if ((core[addr2].val1 += core[addr1].val1) >= coresize)
					core[addr2].val1 -= coresize;
			}
			DRAW(addr2, DI_DAT + (id * DI_PLSIZ));
		}
		DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
		if (++xeq_addr == coresize)
			xeq_addr = 0;
		DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		th->addr = xeq_addr;
		return(0);
		/* instruction done */
		break;
	case op_sub:
		/* instruction op_sub US_VAL (US_VAL|US_RANGE) */
		if (INRANGE)  {
			if ((int)(val2 -= val1) < 0)
				val2 += coresize;
			core[addr2].val2 = val2;
			if (!IMM1 && !IMM2)  {
				if ((int)(core[addr2].val1 -= core[addr1].val1) < 0)
					core[addr2].val1 += coresize;
			}
			DRAW(addr2, DI_DAT + (id * DI_PLSIZ));
		}
		DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
		if (++xeq_addr == coresize)
			xeq_addr = 0;
		th->addr = xeq_addr;
		DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		return(0);
		/* instruction done */
		break;
	case op_jmp:
		/* instruction op_jmp US_ADDR US_NONE */
		if (!IMM1)  {
			if (INTERACTIVE && (xeq_addr != addr1))
				DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
			th->addr = addr1;
		}
		DRAW(addr1, DI_XEQ + (id * DI_PLSIZ));
		return(0);
		/* instruction done */
		break;
	case op_jmz:
		/* instruction op_jmz US_ADDR US_VAL */
		if (val2 == 0)  {
			if (!IMM1)  {
				if (INTERACTIVE && (addr1 != xeq_addr))
					DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
				th->addr = addr1;
			}
			DRAW(addr1, DI_XEQ + (id * DI_PLSIZ));
		} else  {
			DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
			if (++xeq_addr >= coresize)
				xeq_addr -= coresize;
			th->addr = xeq_addr;
			DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		}
		return(0);
		/* instruction done */
		break;
	case op_jmn:
		/* instruction op_jmn US_ADDR US_VAL */
		if (val2 != 0)  {
			if (!IMM1)  {
				if (INTERACTIVE && (addr1 != xeq_addr))
					DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
				th->addr = addr1;
			}
			DRAW(addr1, DI_XEQ + (id * DI_PLSIZ));
		} else  {
			DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
			if (++xeq_addr >= coresize)
				xeq_addr -= coresize;
			th->addr = xeq_addr;
			DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		}
		return(0);
		/* instruction done */
		break;
	case op_djn:
		/* instruction op_djn US_ADDR (US_VAL|US_RANGE) */
		if (val2 == 0)
			val2 = coresize;
		--val2;
		if (INRANGE)  {
			core[addr2].val2 = val2;
			DRAW(addr2, DI_DAT + (id * DI_PLSIZ));
		}
		if (val2 != 0)  {
			if (!IMM1)  {
				if (INTERACTIVE && (addr1 != xeq_addr))
					DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
				th->addr = addr1;
			}
			DRAW(addr1, DI_XEQ + (id * DI_PLSIZ));
		} else  {
			DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
			if (++xeq_addr >= coresize)
				xeq_addr -= coresize;
			th->addr = xeq_addr;
			DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		}
		return(0);
		/* instruction done */
		break;
	case op_cmp:
		/* instruction op_cmp US_VAL US_VAL */
		DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
		++xeq_addr;
		if (IMM1 || IMM2)  {
			if (val1 == val2)
				++xeq_addr;
		} else  {
			if ((val1 == val2) && (core[addr1].val1 == core[addr2].val1) &&
					(core[addr1].instr == core[addr2].instr))
				++xeq_addr;
		}
		if (xeq_addr >= coresize)
			xeq_addr -= coresize;
		DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		th->addr = xeq_addr;
		return(0);
		/* instruction done */
		break;
	case op_spl:
		/* instruction op_spl US_ADDR US_VAL */
		if (INTERACTIVE)  {
			if ((xeq_addr != addr1) || (sim_tlist->count >= maxthreads))
				DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
			if (sim_tlist->count < maxthreads)
				DRAW(addr1, DI_RAN + (id * DI_PLSIZ));
		}
		if (++xeq_addr >= coresize)
			xeq_addr -= coresize;
		DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		th->addr = xeq_addr;
		if (split_id && (val2 != sim_tlist->ld_start))
			return(0);
		else
			return(sim_create_thread(th, addr1));
		/* instruction done */
		break;
	case op_slt:
		/* instruction op_slt US_VAL US_VAL */
		DRAW(xeq_addr, DI_RAN + (id * DI_PLSIZ));
		++xeq_addr;
		if (val1 < val2)
			++xeq_addr;
		if (xeq_addr >= coresize)
			xeq_addr -= coresize;
		DRAW(xeq_addr, DI_XEQ + (id * DI_PLSIZ));
		th->addr = xeq_addr;
		return(0);
		/* instruction done */
		break;
	}
}


int  sim_create_thread(thread_t  *th, aval_t addr)  {
	thread_t  *oldn = th->next, *newth;

	if (sim_tlist->count >= maxthreads)
		return(0);
	if (sim_tlist->empty == NULL)  {
		tblock_t  *lblock = sim_tlist->tblock, *newblock;
	
		newblock = new_tblock();
		sim_tlist->tblock = newblock;
		newblock->next = lblock;
		sim_tlist->empty = newblock->threads + 0;
	}
	newth = sim_tlist->empty;
	sim_tlist->empty = newth->next;
	th->next = newth;
	newth->prev = th;
	oldn->prev = newth;
	newth->next = oldn;
	newth->addr = addr;
	return(1);
}


#define  USAGE  US_VAL
#define  ADDR   result.addr
#define  VAL    result.val
#define  ARG    val
#define  TMP1   tmp1
#define  TMP2   tmp2
static arg_t  fetch_arg(amode_t am, aval_t val, aval_t xeq_addr, int cur_id)  {
	aval_t  ival;
	arg_t  result;
	aval_t  tmp1, tmp2;

	if ((ival = val + xeq_addr) >= coresize)
		ival -= coresize;
	switch(am)  {
	case am_imm:
		/* fetch am_imm notmp */
		if (USAGE != US_NONE)  {
			ADDR = xeq_addr;
			if (USAGE & US_VAL)
				VAL = ARG;
		}
#if (USAGE & US_RANGE)
#undef  INRANGE
#define  INRANGE  TRUE
#endif
		/* fetch done */
		break;
	case am_dir:
		/* fetch am_dir notmp */
		if (USAGE != US_NONE)  {
			if ((ADDR = xeq_addr + ARG) >= coresize)
				ADDR -= coresize;
			if (USAGE & US_VAL)
				VAL = core[ADDR].val2;
		}
#if (USAGE & US_RANGE)
#undef  INRANGE
#define  INRANGE  ((arg2 <= w_radius) || (arg2 >= coresize - w_radius))
#endif
		/* fetch done */
		break;
	case am_ind:
		/* fetch am_ind */
		if (USAGE != US_NONE)  {
			if ((ADDR = xeq_addr + ARG) >= coresize)
				ADDR -= coresize;
			if ((ADDR += core[ADDR].val2) >= coresize)
				ADDR -= coresize;
			if (USAGE & US_VAL)
				VAL = core[ADDR].val2;
		}
#if (USAGE & US_RANGE)
#undef  INRANGE
#define  INRANGE\
    ((((unsigned)(addr2 - xeq_addr + w_radius) <= (unsigned)(2*w_radius))) ||\
     ((unsigned)(addr2 - xeq_addr + (coresize - w_radius)) >=\
      (unsigned)(2*(coresize-w_radius))))
#endif
		/* fetch done */
		break;
	case am_dec:
		/* fetch am_dec tmp */
		if ((TMP1 = xeq_addr + ARG) >= coresize)
			TMP1 -= coresize;
		if (USAGE == US_NONE)  {
			if ((ARG <= w_radius) || (ARG >= coresize - w_radius))  {
				if ((int)(TMP2 = core[TMP1].val2 - 1) < 0)
					TMP2 += coresize;
				core[TMP1].val2 = TMP2;
			}
		} else  {
			if ((int)(TMP2 = core[TMP1].val2 - 1) < 0)
				TMP2 += coresize;
			if ((ARG <= w_radius) || (ARG >= coresize - w_radius))  {
				core[TMP1].val2 = TMP2;
				DRAW(TMP1, DI_DAT + (cur_id * DI_PLSIZ));
			}
			if ((ADDR = TMP1 + TMP2) >= coresize)
				ADDR -= coresize;
			if (USAGE & US_VAL)
				VAL = core[ADDR].val2;
		}
#if (USAGE & US_RANGE)
#undef  INRANGE
#define  INRANGE\
    ((((unsigned)(addr2 - xeq_addr + w_radius) <= (unsigned)(2*w_radius))) ||\
     ((unsigned)(addr2 - xeq_addr + (coresize - w_radius)) >=\
      (unsigned)(2*(coresize-w_radius))))
#endif
		/* fetch done */
		break;
	case am_inc:
		/* fetch am_inc tmp */
		if ((TMP1 = xeq_addr + ARG) >= coresize)
			TMP1 -= coresize;
		if (USAGE == US_NONE)  {
			if ((ARG <= w_radius) || (ARG >= coresize - w_radius))  {
				if ((TMP2 = core[TMP1].val2 + 1) == coresize)
					TMP2 = 0;
				core[TMP1].val2 = TMP2;
			}
		} else  {
			ADDR = core[TMP1].val2;
			if ((ARG <= w_radius) || (ARG >= coresize - w_radius))  {
				if ((TMP2 = ADDR + 1) == coresize)
					TMP2 = 0;
				core[TMP1].val2 = TMP2;
				DRAW(TMP1, DI_DAT + (cur_id * DI_PLSIZ));
			}
			if ((ADDR += TMP1) >= coresize)
				ADDR -= coresize;
			if (USAGE & US_VAL)
				VAL = core[ADDR].val2;
		}
#if (USAGE & US_RANGE)
#undef  INRANGE
#define  INRANGE\
    ((((unsigned)(addr2 - xeq_addr + w_radius) <= (unsigned)(2*w_radius))) ||\
     ((unsigned)(addr2 - xeq_addr + (coresize - w_radius)) >=\
      (unsigned)(2*(coresize-w_radius))))
#endif
		/* fetch done */
		break;
	}
	return(result);
}


int  proc_count(unsigned  id)  {
	tlist_t  *tl = sim_tlist;

	if (tl == NULL)
		return(0);
	while ((tl->id != id) && (tl->next != sim_tlist))
		tl = tl->next;
	if (tl->id != id)  {
		sprintf(cwerrstr, "id %d is not in use.", id);
		return(0);
	}
	return(tl->count);
}


aval_t  proc_exec(unsigned  id)  {
	tlist_t  *tl = sim_tlist;

	while ((tl->id != id) && (tl->next != sim_tlist))
		tl = tl->next;
	if (tl->id != id)  {
		sprintf(cwerrstr, "id %d is not in use.", id);
		return(AVAL_NOGOOD);
	}
	return(tl->head->addr);
}
