#include "sysproto.h"

void	V_GLOBAL
sysPrint(vp_stack_p syss)
{
	u32_t i;
	vec_t total_time = 0;
	tsys_p s;
	for(i = 0;i < vpsCount(syss);i++)
	{
		s = (tsys_p)vpsGet(syss, i);
		conPrintf(
			"System '%s': vf = %g, "
			"fibers = %u, time to process: app. %.2g seconds.\n",
			s->name, s->volumetric_content,
			stackCount(&s->fs), s->time_estimation);
		total_time += s->time_estimation;
	}
	conPrintf("Total: %u systems, "
		"app. %.2g minutes to compute (2.4GHz Lynn core, GCC 4.2).\n",
		vpsCount(syss), total_time / 60);
}

void	V_GLOBAL
sysPrintOut(vp_stack_p syss, FILE *out)
{
	u32_t i;
	tsys_p s;
	vboolean autonaming = out ? false : true;
	char buf[4096];
	char lastname[MAX_ID_LEN] = "";
	for(i = 0;i < vpsCount(syss);i++)
	{
		s = (tsys_p)vpsGet(syss, i);
		if(autonaming && strcmp(lastname, s->parent_name))
		{
			char buf[MAX_ID_LEN + 512];
			if(out)
				fclose(out);
			strCopy(lastname, s->parent_name, MAX_ID_LEN);
			sprintf(buf, "%s.csv", lastname);
			out = fopen(buf, "wb");
			if(!out)
			{
				perror("fopen");
				return;
			}
		}
		floatToStrEx(buf, 4096, s->volume, ',');
		fprintf(out,"%u;%s;volume;%s;", (unsigned)i, s->name, buf);
		floatToStrEx(buf, 4096, s->volumetric_content, ',');
		fprintf(out,"vf;%s;fibers;%u;contacts;%u;", buf,
			stackCount(&s->fs), stackCount(&s->cs));
		floatToStrEx(buf, 4096, 
			(vec_t)stackCount(&s->cs) / (vec_t)stackCount(&s->fs), ',');
		fprintf(out,"rcv;%s;",buf);
		floatToStrEx(buf, 4096, s->average_length, ',');
		fprintf(out,"average_length;%s;",buf);
		floatToStrEx(buf, 4096, s->average_diameter, ',');
		fprintf(out,"average_diameter;%s;",buf);
		floatToStrEx(buf, 4096, s->average_length / s->average_diameter, ',');
		fprintf(out,"form_factor;%s;",buf);
		floatToStrEx(buf, 4096, s->extrapolated_vc, ',');
		fprintf(out,"extrapolated_vf;%s\n",buf);
	}
}

/*
 * First two checks are performed in the inner loop.
 * In principle, 4 versions of new_fiber() can be implemented,
 * for covering all possible cases of generators' combinations.
 * But the following code is several orders of magnitude slower,
 * so don't bother.
 */
static void
new_fiber(vhandle_t rg,vec3_t mins, vec3_t maxs,
	fgen_p lengen, fgen_p diamgen, fp_p dest)
{
	vec3_t fibcenter, halfdir;

	if(IS_ITERATOR(lengen)) /* loop is in outer code */
		dest->length = lengen->last;
	else
	{
		vboolean result = lengen->next(lengen, &dest->length);
		V_ASSERT(result);
	}

	if(IS_ITERATOR(diamgen)) /* loop is in outer code */
		dest->radius = diamgen->last;
	else
	{
		vboolean result = diamgen->next(diamgen, &dest->radius);
		V_ASSERT(result);
	}

	dest->radius *= (vec_t)0.5;
	dest->volume = M_PI * dest->radius * dest->radius * dest->length;
	ranNextVec3RangeV(rg, fibcenter, mins, maxs);
	ranDirection(rg, halfdir);
	vec3Scale(halfdir, halfdir, (vec_t)0.5 * dest->length);
	vec3Sub(dest->p1, fibcenter, halfdir);
	vec3Add(dest->p2, fibcenter, halfdir);
}

/*
 * Estimation of computation time.
 *
 * Reference system: 2.4GHz Xeon (Lynnfield), 64-bit FreeBSD, gcc 4.2.2.
 * 200000 fibers, (2e+5)^2 / 2 = 2e+10 checks: app. 800 seconds,
 * rate is 25e+6 cps.
 *
 * On 2GHz C2D mobile, WATCOM compiler - cps is about 4 times slower;
 * if segCrossing() is compiled by icl 8 - app. 2.5 times slower.
 */

#define	REF_C_RATE	(25e+6)

static vec_t
sysEstimateTime(tsys_p sys)
{
	double numchecks = (double)stackCount(&sys->fs);
	numchecks *= numchecks * 0.5;
	return (vec_t)(numchecks / REF_C_RATE);
}

#define	FP_NUM_WARNING			200000
#define	FP_NUM_ABORT			2000000

static vboolean
sysOne(vhandle_t rg,tseq_p seq, vp_stack_p store)
{
	CALLER_NAME(sysOne)
	fp_t fib;
	fgen_p vfgen = seq->gens[GEN_VC];
	u32_t count = 0;
	vec3_t bs;
	vec_t target_vc, fibs_volume, target_volume, sr = 0, slen = 0;
	tsys_p s = (tsys_p)memAllocZ(sizeof(tsys_t));
	vec3Copy(s->bbox[0], seq->bbox[0]);
	vec3Copy(s->bbox[1], seq->bbox[1]);
	vec3Sub(bs, s->bbox[1], s->bbox[0]);
	s->volume = bs[0] * bs[1] * bs[2];
	s->volumetric_content = 0;
	stackInit(&s->fs, sizeof(fp_t));
	stackInit(&s->cs, sizeof(fc_t));
	/*
	 * Fill the box until the target volumetric content is reached.
	 * VF can be either constant, or iterator; if later, it will be
	 * iterated in outer code.
	 */
	target_vc = vfgen->last;
	target_volume = s->volume * target_vc;
	for(fibs_volume = 0;fibs_volume < target_volume;count++)
	{
		if(count == FP_NUM_WARNING)
		{
			WARNING_GENERIC("Too many fibers,"
				" computation will take more than 10 minutes");
		}
		if(count == FP_NUM_ABORT)
		{
			ERROR_GENERIC("Too many fibers, DAY to complete the task!\n"
				"Redesign the system");
			stackClear(&s->fs);
			memFree(s);
			return false;
		}
		new_fiber(rg, s->bbox[0], s->bbox[1],
			seq->gens[GEN_FL], seq->gens[GEN_FD], &fib);
		stackPush(&s->fs, &fib);
		fibs_volume += fib.volume;
		slen += fib.length;
		sr += fib.radius;
	}
	s->average_diameter = sr * 2 / stackCount(&s->fs);
	s->average_length = slen / stackCount(&s->fs);
	s->volumetric_content = fibs_volume / s->volume;
	strCopy(s->parent_name, seq->name, MAX_ID_LEN);
	sprintf(s->name, "%s_%03u", seq->name, (unsigned)seq->num_systems++);
	s->time_estimation = sysEstimateTime(s);
	vpsPush(store, s);
	return true;
}

static vboolean
sys1DArray(vhandle_t rg, tseq_p seq, vp_stack_p store)
{
	/*
	 * Loop thow values of seq->gens[seq->iterators[0]].
	 */
	fgen_p ig = seq->gens[seq->iterators[0]];
	ig->reset(ig);
	do
	{
		ig->next(ig, NULL);
		if(!sysOne(rg, seq, store))
			return false;
	} while(!ig->finished);
	return true;
}

static vboolean
sys2DArray(vhandle_t rg, tseq_p seq, vp_stack_p store)
{
	/*
	 * Loop thow values of seq->gens[seq->iterators[1]].
	 */
	fgen_p ig = seq->gens[seq->iterators[1]];
	ig->reset(ig);
	do
	{
		ig->next(ig, NULL);
		if(!sys1DArray(rg, seq, store))
			return false;
	} while(!ig->finished);
	return true;
}

static vboolean
sys3DArray(vhandle_t rg, tseq_p seq, vp_stack_p store)
{
	/*
	 * Loop thow values of seq->gens[seq->iterators[2]]
	 * Here second non-VF iterator is incremented.
	 */
	fgen_p ig = seq->gens[seq->iterators[2]];
	ig->reset(ig);
	do
	{
		ig->next(ig, NULL);
		if(!sys2DArray(rg, seq, store))
			return false;
	} while(!ig->finished);
	return true;
}

vboolean	V_GLOBAL
sysCreateSystems(vp_stack_p seqs, vp_stack_p store)
{
	static s32_t extra_seed = 137;
	u32_t i;
	tseq_p seq;
	vhandle_t rg = ranCreateGenerator(extra_seed);
	for(i = 0;i < vpsCount(seqs);i++)
	{
		seq = (tseq_p)vpsGet(seqs, i);
		switch(seq->nesting_level)
		{
			case 0:
				if(!sysOne(rg, seq, store))
					goto err_exit;
				break;
			case 1:
				if(!sys1DArray(rg, seq, store))
					goto err_exit;
				break;
			case 2:
				if(!sys2DArray(rg, seq, store))
					goto err_exit;
				break;
			case 3:
				if(!sys3DArray(rg, seq, store))
					goto err_exit;
				break;
			default:
				V_ASSERT(0);
		}
	}
	ranDestroyGenerator(rg);
	return true;
err_exit:;
	ranDestroyGenerator(rg);
	return false;
}

void	V_GLOBAL
sysDestroy(vp_stack_p store)
{
	tsys_p ptr;
	while(vpsPop(store, (void_p)&ptr))
	{
		stackClear(&ptr->fs);
		stackClear(&ptr->cs);
		memFree(ptr);
	}
	vpsClear(store);
}

/*
 * The core. Quite simple.
 */
static void
find_contacts(tsys_p sys)
{
	u32_t i, j, num_fibers;
	fp_p fibers, f1, f2;
	fc_t contact;
	num_fibers = stackCount(&sys->fs);
	f1 = fibers = (fp_p)stackGetStorage(&sys->fs);
	for(i = 0;i < num_fibers;i++, f1++)
	{
		for(j = i + 1, f2 = fibers + j;j < num_fibers;j++, f2++)
		{
			/*
			 * NOTE: early out is already implemented in segCrossing().
			 */
			if(!segCrossing(f1->p1, f1->p2, f2->p1, f2->p2,
					&contact.dist, contact.cp))
				continue;
			if(contact.dist > (f1->radius + f2->radius))
				continue;
			contact.f1 = f1;
			contact.f2 = f2;
			contact.nf1 = i;
			contact.nf2 = j;
			stackPush(&sys->cs, &contact);
		}
	}
	if(stackCount(&sys->cs))
	{
		sys->extrapolated_vc = sys->volumetric_content *
			(vec_t)stackCount(&sys->fs) / (vec_t)stackCount(&sys->cs);
		if(sys->extrapolated_vc > 1)
			sys->extrapolated_vc = 1;
	}
	else
		sys->extrapolated_vc = 1;
}

/*
 * TODO for near holiday: sysParallelScheduler().
 */
vboolean	V_GLOBAL
sysSequentialScheduler(vp_stack_p syss)
{
	u32_t i;
	for(i = 0;i < vpsCount(syss);i++)
		find_contacts((tsys_p)vpsGet(syss, i));
	return true;
}

/*
 * Postprocessor.
 */

vboolean	V_GLOBAL
sysPostprocess(vp_stack_p syss, FILE *out)
{
	sysPrintOut(syss, out);
	return true;
}
