package ry;

//imports
import robocode.*;
import robocode.util.Utils;
import java.awt.geom.*; // for Point2D's
import java.awt.Color;

/*Released under the RoboWiki Public Code License
 Author:  Robert Ying
 Do NOT COPY WITHOUT GIVING CREDIT!

 Note:  Certain lines are commented out in order to save codesize, and can be put back in to improve the algorithms.
 */
public class LightningBug extends AdvancedRobot {
	// Variables
	private static final double BULLET_POWER = 1.9;
	private static double lateralDirection;
	private static double lastEnemyVelocity;
	static double prevEnergy = 100.0;
	static double enemyEnergy;
	// static double turnFactor = 0.2;
	// static int favDistance = 200;
	static int moveFactor = 20;
	static int vel;
	static int lastVel;
	static int[][] hitData = new int[16][4];

	public void run() {
		// Colors
		// setColors(Color.BLACK, Color.BLUE, Color.GRAY);
		// Settings
		lateralDirection = 1;
		lastEnemyVelocity = 0;
		setAdjustRadarForGunTurn(true);
		setAdjustGunForRobotTurn(true);
		// Infinity Lock Radar
		turnRadarRightRadians(Double.POSITIVE_INFINITY);
	}

	public void onScannedRobot(ScannedRobotEvent e) {
		// GuessFactor Targeting. RWPCL!
		double enemyAbsoluteBearing = getHeadingRadians() + e.getBearingRadians();
		double enemyDistance = e.getDistance();
		double enemyVelocity = e.getVelocity();
		if (enemyVelocity != 0) {
			lateralDirection = RYUtils.sign(enemyVelocity
					* Math.sin(e.getHeadingRadians() - enemyAbsoluteBearing));
		}
		Wave wave = new Wave(this);
		wave.gunLocation = new Point2D.Double(getX(), getY());
		Wave.targetLocation = RYUtils
				.project(wave.gunLocation, enemyAbsoluteBearing, enemyDistance);
		wave.lateralDirection = lateralDirection;
		wave.bulletPower = BULLET_POWER;
		wave.setSegmentations(enemyDistance, enemyVelocity, lastEnemyVelocity);
		lastEnemyVelocity = enemyVelocity;
		wave.bearing = enemyAbsoluteBearing;
		setTurnGunRightRadians(Utils.normalRelativeAngle(enemyAbsoluteBearing
				- getGunHeadingRadians() + wave.mostVisitedBearingOffset()));
		setFire(wave.bulletPower);
		// VelocitySurfing, Version 2
		if (getEnergy() >= BULLET_POWER) {
			addCustomEvent(wave);
		}
		if (enemyEnergy > (enemyEnergy = e.getEnergy())) {
			lastVel = vel + (4 * (int) (e.getDistance() / 267));
			int testVel = 3;
			do {
				if (hitData[lastVel][testVel] < hitData[lastVel][vel]) {
					vel = testVel;
				}
			} while (testVel-- > 0);
			if (vel < 2) {
				onHitWall(null);
			}
		}
		setMaxVelocity(8 * (vel - 1.5));
		setAhead(moveFactor);
		/*
		 * double smoothedDirection = getHeadingRadians() +
		 * e.getBearingRadians() - 1.48 * moveFactor; Rectangle2D fieldRect =
		 * new Rectangle2D.Double(18, 18, 800 - 36, 600 - 36); while
		 * (!fieldRect.contains(getX() + Math.sin(smoothedDirection) * 120,
		 * getY() + Math.cos(smoothedDirection) * 120)) { smoothedDirection +=
		 * moveFactor * turnFactor; } double turn =
		 * Utils.normalRelativeAngle(smoothedDirection - getHeadingRadians());
		 * if (Math.abs(turn) > Math.PI / 2) { turn =
		 * Utils.normalRelativeAngle(turn + Math.PI); setBack(20); } else
		 * setAhead(20); setTurnRightRadians(turn);
		 */
		// For example, this line replaces
		// setTurnRightRadians(e.getBearingRadians() + Math.PI / 2 - Math.PI / 2
		// * Math.random()
		// * (e.getDistance() > favDistance ? 1 : -1));
		// this line in order to allow for randomization in the movement
		// routine. Of course, you will need to uncomment favDistance above.
		setTurnRightRadians(e.getBearingRadians() + Math.PI / 2);
		// Infinity Lock Radar
		setTurnRadarLeftRadians(getRadarTurnRemainingRadians());

	}

	// Some Events, for movement. I need to find space for WallSmoothing
	public void onBulletHit(BulletHitEvent e) {
		enemyEnergy -= 11.4;
	}

	public void onHitByBullet(HitByBulletEvent e) {
		hitData[lastVel][vel]++;
	}

	public void onHitWall(HitWallEvent e) {
		moveFactor *= -1;
	}
}

class Wave extends Condition {
	static Point2D targetLocation;

	double bulletPower;
	Point2D gunLocation;
	double bearing;
	double lateralDirection;

	private static final double MAX_DISTANCE = 1000;
	private static final int dist_index = 5;
	private static final int vel_index = 5;
	private static final int BINS = 47;
	private static final int MIDDLE_BIN = (BINS - 1) / 2;
	private static final double MAX_ESCAPE_ANGLE = 0.7;
	private static final double BIN_WIDTH = MAX_ESCAPE_ANGLE / (double) MIDDLE_BIN;

	private static int[][][][] statBuffers = new int[dist_index][vel_index][vel_index][BINS];

	private int[] buffer;
	private AdvancedRobot robot;
	private double distanceTraveled;

	Wave(AdvancedRobot _robot) {
		this.robot = _robot;
	}

	public boolean test() {
		advance();
		if (hasArrived()) {
			buffer[currentBin()]++;
			robot.removeCustomEvent(this);
		}
		return false;
	}

	double mostVisitedBearingOffset() {
		return (lateralDirection * BIN_WIDTH) * (mostVisitedBin() - MIDDLE_BIN);
	}

	void setSegmentations(double distance, double velocity, double lastVelocity) {
		int distanceIndex = (int) (distance / (MAX_DISTANCE / dist_index));
		int velocityIndex = (int) Math.abs(velocity / 2);
		int lastVelocityIndex = (int) Math.abs(lastVelocity / 2);
		buffer = statBuffers[distanceIndex][velocityIndex][lastVelocityIndex];
	}

	private void advance() {
		distanceTraveled += RYUtils.bulletVelocity(bulletPower);
	}

	private boolean hasArrived() {
		return distanceTraveled > gunLocation.distance(targetLocation) - 18;
	}

	private int currentBin() {
		int bin = (int) Math.round(((Utils.normalRelativeAngle(RYUtils
				.absoluteBearing(gunLocation, targetLocation)
				- bearing)) / (lateralDirection * BIN_WIDTH))
				+ MIDDLE_BIN);
		return RYUtils.minMax(bin, 0, BINS - 1);
	}

	private int mostVisitedBin() {
		int mostVisited = MIDDLE_BIN;
		for (int i = 0; i < BINS; i++) {
			if (buffer[i] > buffer[mostVisited]) {
				mostVisited = i;
			}
		}
		return mostVisited;
	}
}

class RYUtils {
	static double bulletVelocity(double power) {
		return 20 - 3 * power;
	}

	static Point2D project(Point2D sourceLocation, double angle, double length) {
		return new Point2D.Double(sourceLocation.getX() + Math.sin(angle) * length, sourceLocation
				.getY()
				+ Math.cos(angle) * length);
	}

	static double absoluteBearing(Point2D source, Point2D target) {
		return Math.atan2(target.getX() - source.getX(), target.getY() - source.getY());
	}

	static int sign(double v) {
		return v < 0 ? -1 : 1;
	}

	static int minMax(int v, int min, int max) {
		return Math.max(min, Math.min(max, v));
	}
}

