#include "Tables.h"
#include "XdYMath.h"

#include <stdlib.h>
#include <math.h>

#define max(X,Y) ( (X)>(Y) ? (X) : (Y) )
#define min(X,Y) ( (X)<(Y) ? (X) : (Y) )
#define abs(X,Y) ( (X)<0 ? -(X) : (X) )
#define clip(X,Y) ( (X)>(Y) ? (Y) : (X) )


/* Calculate blows per round */
int blows( xdy_values *v )
{
	double	swi;
	int i;
	for ( i=0 ; v->chr.str > str_table[i].str ; i++ )
		;	/* Find SWI for character's strength */
	swi = (double) str_table[i].swi;
	swi /= max( v->wpn.w, class_mww[v->chr.class] );
	swi *= class_wsm[v->chr.class];
	swi = min( floor(swi), 11 );
	for ( i=0 ; v->chr.dex > blows_per_round[i].dex ; i++ )
		;	/* Find appropriate line in dex/SWI table */
	i = clip( blows_per_round[i].bpr[(int)swi], max_attacks[v->chr.class] );
	return i + v->wpn.a;	/* + extra attacks */
}

/* Calculate melee combat ability (and if bskill!=NULL, store the bonus */
/* to melee skill in it) */
int skill( xdy_values *v, double *bskill )
{
	double	base,bonus;
	double	mw;			/* max weapon weight */
	int		i;
	/* Calculate base melee skill */
	base = (double)class_bms[ v->chr.class ];
	base += (double)race_msm[ v->chr.race ];
	base += class_mslm[ v->chr.class ] * v->chr.level;
	/* Calculate bonus melee skill */
	for ( i=0 ; v->chr.str > str_table[i].str ; i++ )
		;	/* Find +ToHit for character's strength */
	bonus = str_table[i].hit_bonus;
	mw = str_table[i].w_limit;
	for ( i=0 ; v->chr.dex > dex_modtohit[i].dex ; i++ )
		;	/* Find +ToHit for character's dex */
	bonus += dex_modtohit[i].hit_bonus;
	bonus += v->other.ap;	/* armour penalty */
	bonus += v->other.h;	/* ... of slaying, etc */
	bonus += v->wpn.h;		/* weapon's + to hit */
	bonus -= 10.0 * max( v->wpn.w - mw, 0 );	/* weight penalty */
	if ( v->chr.class == CLASS_PRIEST && v->wpn.e )
		bonus -= 2.0;	/* edged weapon penalty (as appropriate) */
	if ( bskill ) { *bskill = bonus; }
	return (int) max( base + ( 3 * bonus ), 0 );
}

/* Calculate probablity of landing a hit */
double hit_prob( int ac, int m_skill )
{
	double	p;
	if ( m_skill )
		p = (((double)m_skill)-(0.75*ac)) / ((double)m_skill);
	else
		p = 0.0;
	if ( p < 0.0 ) { p = 0.0; }
	return 0.05 + 0.90 * p;
}

/* Calculate the number of integers between dmin & dmax (incl) that also lie */
/* between rmin and rmax (incl), and use this to determine the probablity */
/* that a random int in [dmin,dmax] is in [rmin,rmax] */
double range_prob( int dmin, int dmax, int rmin, int rmax )
{
	int rw;
	if ( (dmin > rmax) || (dmax < rmin) ) { return 0.0; }
	rw = min( rmax, dmax ) - max( rmin, dmin );
	return rw<0 ? 0.0 : ((double)rw) / ((double)dmax-dmin);
}


/* Calculate various probabilities of critical hits based on *v and */
/* the bonus melee skill value supplied, and store them in *r */
void calculate_crit_probs( xdy_values *v, double bskill, crit_probs *r )
{
	int p;
	/* chance in 5000 of getting a crit. */
	p = (int) ((v->wpn.w * 10.0) + (bskill * 5.0) + (v->chr.level * 3.0));
	r->prob = (double)p / 5000.0;
	/* weighting (groan) to crit type roll */
	p = (int) (v->wpn.w * 10);
	r->good = range_prob( 1+p, 650+p, 1, 399 );
	r->great = range_prob( 1+p, 650+p, 400, 699 );
	r->superb = range_prob( 1+p, 650+p, 700, 899 );
	r->s_great = range_prob( 1+p, 650+p, 900, 1299 );
	r->s_superb = range_prob( 1+p, 650+p, 1300, 4096 );	/* overkill */
}

/* Calculate average damage per hit from v and r */
double dam_per_hit( xdy_values *v, crit_probs *r )
{
	double	dam, cdam;
	int i;
	dam = ((double) v->wpn.x * v->wpn.y + v->wpn.x ) / 2.0;
	/* at this point, we'd add in any Ego bonuses */
	cdam = ( ((dam*2.0+5.0)*r->good) + ((dam*2.0+10.0)*r->great)
				+ ((dam*3.0+15.0)*r->superb) + ((dam*2.0+20.0)*r->s_great)
				+ ((dam*3.5+25.0)*r->s_superb) );
	/* avg. dam = (crit dam * prob) + (non crit dam * prob ) */
	dam = (r->prob * cdam) + ( 1.0-(r->prob) ) * dam;
	dam += v->wpn.d;	/* + weapon's damage bonus */
	for ( i=0 ; v->chr.str > str_table[i].str ; i++ )
		;	/* Find +ToDam for character's strength */
	dam += str_table[i].dam_bonus;	/* + strength bonus */
	dam += v->other.d;				/* + any other bonuses */
	if ( v->chr.class == CLASS_PRIEST && v->wpn.e )
		dam -= 2.0;	/* edged weapon penalty (as appropriate) */
	if ( dam < 0 ) { dam = 0; }
	return dam;
}

int min_dam_per_hit( xdy_values *v )
{
	int dam, i;
	dam = v->wpn.x + v->wpn.d;
	for ( i=0 ; v->chr.str > str_table[i].str ; i++ )
		;	/* Find +ToDam for character's strength */
	dam += str_table[i].dam_bonus;	/* + strength bonus */
	dam += v->other.d;				/* + any other bonuses */
	if ( v->chr.class == CLASS_PRIEST && v->wpn.e )
		dam -= 2;	/* edged weapon penalty (as appropriate) */
	if ( dam < 0 ) { dam = 0; }
	return dam;
}


int max_dam_per_hit( xdy_values *v, crit_probs *r )
{
	int dam, i;
	dam = v->wpn.x*v->wpn.y;
	/* at this point, we'd add in any Ego bonuses */
	/* Now, treat as the best crit possible */
	if ( r->s_superb > 0.00154 )  dam = (int) (dam * 3.5 + 25.0);
	else if ( r->s_great > 0.00154 ) dam = (int) (dam * 3.0 + 20.0);
	else if ( r->superb > 0.00154 ) dam = (int) (dam * 2.0 + 15.0);
	else if ( r->great > 0.00154 ) dam = (int) (dam * 2.0 + 10.0);
	else if ( r->good > 0.00154 ) dam = (int) (dam * 2.0 + 5.0);
	for ( i=0 ; v->chr.str > str_table[i].str ; i++ )
		;	/* Find +ToDam for character's strength */
	dam += str_table[i].dam_bonus;		/* + strength bonus */
	dam += v->wpn.d;					/* + weapon's bonus */
	dam += v->other.d;					/* + other bonuses */
	if ( v->chr.class == CLASS_PRIEST && v->wpn.e )
		dam -= 2;	/* edged weapon penalty (as appropriate) */
	if ( dam < 0 ) { dam = 0; }
	return dam;
}



void xdy_calculate( xdy_values *stats, xdy_result *result )
{
	double bskill;	/* bonus portion of melee skill */
	result->blows = blows( stats );
	result->skill = skill( stats, &bskill );
	result->hit_prob = hit_prob( result->ac, result->skill );
	calculate_crit_probs( stats, bskill, &(result->crit) );
	result->dam_per_hit = dam_per_hit( stats, &(result->crit) );
	result->min_dam = min_dam_per_hit( stats );
	result->max_dam = max_dam_per_hit( stats, &(result->crit) );
	result->dam_per_round = result->blows*result->hit_prob*result->dam_per_hit;
}

void xdy_calculate_new_ac( xdy_values *stats, xdy_result *result )
{
	result->hit_prob = hit_prob( result->ac, result->skill );
	result->dam_per_round = result->blows*result->hit_prob*result->dam_per_hit;
}
