/**
 *  Attack is a transient object, only existing long enough to provide
 *  information about an attack.
 */
package sso;

import java.awt.Point;

public class Attack
{
	// change GameObject attacker to PC attacker
	PC			attacker;
	Attackable	defender;
	Weapon		attackerWeapon;		// may be a Thing, Spell, etc.
	int			attackerRoll;
	int			defenderRoll;
	long		attackTime;
	int			attackResult;		// description, from constants
	int			attackType;			// type of attack
	int			damageRoll;			// let the defender accept the damage
	int			damageLoc;			// allow the defender to assign this if applicable

	// CONSTANTS
	// How the attack faired
	public static final int	MISS		= 0x00;
	public static final int	HIT			= 0x02;
	public static final int	RESISTED	= 0x04;
	public static final int	PARTIAL_RESIST	= 0x08;

	// modifiers
	public static final int	CRITICAL_HIT	= 0x10;
	public static final int	FUMBLE			= 0x20;
	
	// Dodging thrown weapons
	public static final String	DODGE_SKILL = "sso.skill.Dodge";
	
	// Constructor
	public Attack()
	{
		attackTime = System.currentTimeMillis();
		attackResult = MISS;
	}

	// field methods
	public PC getAttacker()
	{
		return attacker;
	}

	public void setAttacker(PC attacker)
	{
		this.attacker = attacker;
	}

	public Attackable getDefender()
	{
		return defender;
	}

	public void setDefender(Attackable defender)
	{
		this.defender = defender;
	}

	public Weapon getWeapon()
	{
		return attackerWeapon;
	}

	public void setWeapon(Weapon weapon)
	{
		attackerWeapon = weapon;
		attackType = weapon.getAttackType();
	}

	public int getResult()
	{
		return attackResult;
	}

	public long getAttackTime()
	{
		return attackTime;
	}

	public int getAttackerRoll()
	{
		return attackerRoll;
	}

	public int getDefenderRoll()
	{
		return defenderRoll;
	}

	public int getDamageRoll()
	{
		return damageRoll;
	}

	public int getDamageLocation()
	{
		return damageLoc;
	}

	public int getAttackType()
	{
		return attackType;
	}

	public void attack()
	{
		if (attacker != null && defender != null && attackerWeapon != null)
		{
			// do attack
			// set attacker roll
			attackerRoll = attacker.getSkill(attackerWeapon.getSkill()).roll() + attackerWeapon.getRating();

			// set defender roll
			// if the defender is a PC
			//    if the weapon has an opposing skill
			//        roll the defender in that opposing skill
			//    else
			//        assign the Skill DEFAULT_RATING as the defender's
			//        roll
			// else
			//    assign the rating of the defender (if it's a thing) as the 
			//    defender's roll, or assign the Skill DEFAULT_RATING
			defenderRoll = (defender instanceof PC ?  (attackerWeapon.getOpposingSkill() != null ?  ((PC)defender).getSkill(attackerWeapon.getOpposingSkill()).roll() : Skill.DEFAULT_RATING) : (defender instanceof Thing ? defender.getRating() : Skill.DEFAULT_RATING));
				
			// compute the attack result
			// did the defender avoid the attack completely?
			if (defenderRoll < attackerRoll)
			{
				// no
				attackResult |= HIT;
					
				// didn't resist the attack
				// get a damage location possibility (the defender
				// actually assigns where damage takes place, but we
				// can seed that process if it's implemented in the
				// defender)
				damageLoc = Random.roll(100);
					
				// roll for damage
				damageRoll = attackerWeapon.getDamage();

					
				// check resistances
				checkResistance();

				
			}
			else
			{
				attackResult |= MISS;
			}

			// check for fumble
			if (attackerRoll < 5)
			{
				if (Random.roll(100) < attackerWeapon.getFumble())
				{
					attackResult |= FUMBLE;
					attackerWeapon.doFumble();
				}
			}
				
			// now, it's up to the defender to apply damage to itself via 
			// the attack method
			defender.attack(this);
		}
	}

	public void checkResistance()
	{
		int res;

		// Due to how getResistance() in GameObject is coded, only a
		// MATCHING resistance will be called. So if an attack is
		// PHYSCIAL & FIRE, only a resistanced to PHYSICAL & FIRE will
		// be called instead of PHYSICAL & FIRE, PHYSICAL, and FIRE.	
		// *** TODO *** Fix it so that each resistance has a chance to
		// come out and play...iterating through attack types belongs
		// here, not in the GameObject
		if ((res = Random.roll(100)) > ((GameObject)defender).getResistance(attackType))
		{
			// check for critical hit
			// each weapon has a critical hit threshold
			if (attackerRoll > attackerWeapon.getCriticalHit())
			{
				attackResult |= CRITICAL_HIT;
			}
		}
		else
		{
			// defender resisted all or part of the damage
			if (res < (((GameObject)defender).getResistance(attackType) / 2))
			{
				// resisted all the damage
				attackResult |= RESISTED;
				damageRoll = 0;
			}
			else
			{
				// resisted part of the damage
				attackResult |= PARTIAL_RESIST;
				damageRoll /= 2;
			}
		}
	}
	

	/**
     *  Perform a thrown weapon attack.
	 *  TODO:  finish this when tiles are done so I can calculate distances
	 */
	public void throwAttack()
	{
		if (attackerWeapon != null && attacker != null && defender != null)
		{
			attackResult |= MISS;
	
			if (attackerWeapon instanceof Throwable)
			{
				Throwable t = (Throwable)attackerWeapon;

				// this is where the weapon will end up
				// determined as part of calculating the distance
				// and the attack roll (weapon may fall or short of go long)
				GameObject dest;

				// calculate the distance to the defender
				// this is the REAL geometric distance defined by the
				// Pythagorean equation, NOT the path that will later be
				// calculated. We do this because distances and penalties are
				// computed as a real geometric spehere around the player,
				// while the path that needs to be drawn for the flight of the
				// thrown object is tile-based.
				int _da_X = ((GameObject)attacker).getX() - ((GameObject)defender).getX();
				int _da_Y = ((GameObject)attacker).getY() - ((GameObject)defender).getY();
				
				int dist = (int)(Math.sqrt((_da_X * _da_X) + (_da_Y & _da_Y)));

				// set attacker roll
				attackerRoll = attacker.getSkill(t.getThrowSkill()).roll() + attackerWeapon.getRating();

				// set defender roll
				defenderRoll = (defender instanceof PC ?  ((PC)defender).getSkill(DODGE_SKILL).roll() : (defender instanceof Thing ? defender.getRating() : Skill.DEFAULT_RATING));

				// modify the penalty based on range 
				if (dist < t.getMinRange())
				{
					// can't hit--weapon sails past defender
					attackerRoll = -255;
				}
				else if (dist < t.getShortRange())
				{
					attackerRoll -= t.getShortRangePenalty();
				}
				else if (dist < t.getMedRange())
				{
					attackerRoll -= t.getMedRangePenalty();
				}
				else if (dist < t.getLongRange())
				{
					attackerRoll -= t.getLongRangePenalty();
				}
				else if (dist < t.getExtremeRange())
				{
					attackerRoll -= t.getExtremeRangePenalty();
				}
				else
				{
					// can't hit--defender too far
					attackerRoll = -255;
				}

				// did the defender avoid the attack completely?
				if (defenderRoll < attackerRoll)
				{
					// no
					attackResult |= HIT;

					// get a damage location (see attack() for details)
					damageLoc = Random.roll(100);

					// roll for damage
					damageRoll = attackerWeapon.getDamage();

					// check resistances (see attack() for details)
					checkResistance();
				}
				else
				{
					// miss
					attackResult |= MISS;
				}					
				
				// caculate the path
				int xa = ((GameObject)attacker).getX();
				int ya = ((GameObject)attacker).getY();
				int xd = ((GameObject)defender).getX();
				int yd = ((GameObject)defender).getY();

				// modify xd and yd if it was a miss
				// Since MISS is 0x00, any & would return MISS
				if (attackResult == MISS)
				{
					xd -= Random.roll(5);
					yd -= Random.roll(5);
				}
				
				Point [] path = getPath(xa, ya, xd, yd);
	
				// iterate over the path..update the weapon location
				for (int i = 0; i < path.length; i++)
				{
					try
					{
						Thread.sleep(25);	// sleep for 25 ms
					}
					catch (InterruptedException e)
					{
						//
					}
					
					((GameObject)attackerWeapon).move(Registry.getXY(path[i].x, path[i].y));
				}
				
				// hurt the target
				// was it a fumble?
				// you can only fumble on a miss that comes close to yourself
				if (attackResult == MISS)
				{
					if (xd > xa - 2 && xd < xa + 2 && yd > ya - 2 && yd < ya +
					2)
					{
						attackResult |= FUMBLE;
						attackerWeapon.doFumble();
					}
				}

				// apply damage to the defender if any
				defender.attack(this);
			}
		}
	}


	/**
	 *  Returns an array of the straight-line path (in tiles) between the given
	 *  points.
	 */
	public static Point [] getPath(int xa, int ya, int xd, int yd)
	{
		int rise = yd - ya;
		int run = xd - xa;
				
		int x = 0, y = 0;
		double rr;

		Point [] path;

		// I trust that this won't be a bug that someday gets tickled
		// if someone tries to throw something at an object inside
		// their current tile/location.

		if (rise == 0)
		{
			// horizontal
			path = new Point[Math.abs(run)];
			
			if (run > 0)
			{
				// positive increment
				for (x = xa; xa < xd; x++)
				{
					path[x - xa] = new Point(x, yd);
				}
			}
			else
			{
				// negative increment
				for (x = xa; xa > xd; x--)
				{
					path[xa - x] = new Point(x, yd);
				}
			}
		}
		else if (run == 0)
		{
			// vertical
			path = new Point[Math.abs(rise)];

			if (rise > 0)
			{
				// positive increment
				for (y = ya; ya < yd; y++)
				{
					path[y - ya] = new Point(xd, y);
				}
			}
			else
			{
				// negative increment
				for (y = ya; ya > yd; y--)
				{
					path[ya - y] = new Point(xd, y);
				}
			}
		}
		else if (Math.abs(rise) > Math.abs(run))
		{
			// step by y
			rr = (double)run / (double)rise;
			path = new Point[Math.abs(rise)];

			if (rise > 0)
			{
				// positive increment
				for (y = ya; y < yd; y++)
				{
					x = xa + (int)(rr * (double)(y - ya));
					path[y - ya] = new Point(x, y);
				}
			}
			else
			{
				// negative increment
				for (y = ya; y > yd; y--)
				{
					x = xa + (int)(rr * (double)(y - ya));
					path[ya - y] = new Point(x, y);
				}
			}
		}
		else
		{
			// last case, step by x
			rr = (double)rise / (double)run;
			path = new Point[Math.abs(run)];

			if (run > 0)
			{
				// positive increment
				for (x = xa; x < xd; x++)
				{
					y = ya + (int)(rr * (double)(x - xa));
					path[x - xa] = new Point(x, y);
				}
			}
			else
			{
				// negative increment
				for (x = xa; x > xd; x--)
				{
					x = xa + (int)(rr * (double)(x - xa));
					path[xa - x] = new Point(x, y);
				}
			}
		}
		
		return path;
	}
	
}

