import java.util.ArrayList;

/** Represents the state of the moving user. */
public class User {
	private ArrayList<UserListener> listeners;
	private World world;
	private double x;
	private double y;
	private double theta;
	private double viewAngle; // angle of view

	/** Constructs a user in the given world, at coordinates
	 * (0,0) facing east (1,0). */
	public User(World world) {
		this.world = world;
		listeners = new ArrayList<UserListener>();
		x = 0;
		y = 0;
		theta = 0.0;
		viewAngle = Math.PI / 6.0;
	}

	/** Constructs a user identical to the given user.
	 * Subsequent movements by either user do not affect the
	 * other. */
	public User(User base) {
		this.listeners = new ArrayList<UserListener>();
		this.world = base.world;
		this.x = base.x;
		this.y = base.y;
		this.theta = base.theta;
		this.viewAngle = base.viewAngle;
	}

	/** Returns the world in which this user is moving. */
	public World getWorld() { return world; }

	/** Returns the x-coordinate of this user's current
	 * position. */
	public double getX() { return x; }

	/** Returns the y-coordinate of this user's current
	 * position. */
	public double getY() { return y; }

	/** Returns the angle of this user's current direction,
	 * measured in radians. A 0 return value represents east and
	 * Math.PI/2 represents north. */
	public double getTheta() { return theta; }

	/** Returns the angle that the user should be able to see
	 * vertically. */
	public double getViewAngle() { return viewAngle; }

	/** Adds a listener to the user's movements. */
	public void addUserListener(UserListener l) {
		listeners.add(l);
	}
	/** Removes a listener to the user's movements. */
	public void removeUserListener(UserListener l) {
		listeners.remove(l);
	}
	private void fireUserMoved() {
		UserEvent e = new UserEvent(this);
		for(int i = 0; i < listeners.size(); i++) {
			UserListener l = (UserListener) listeners.get(i);
			l.userMoved(e);
		}
	}

	/** Returns a ray emanating from the user's location
	 * through the window at fraction <code>sx</code> from center.
	 * The <code>sx</code> parameter should be 0.0 for center,
	 * 0.5 for being as far to the right as half the window's
	 * height, and -0.5 for being as far to the left as half
	 * the window's height.
	 *
	 * @param sx the distance from the window's center desired,
	 * measured in terms of the screen's height.
	 *
	 * @return a ray starting at the user's eye and going
	 * through the desired location on the window.
	 */
	public Ray getViewRay(double sx) {
	/*
		double dx = Math.cos(theta);
		double dy = Math.sin(theta);
		double px = dx + dy * sx;
		double py = dy - dx * sx;
		double scale = 1.0 / Math.sqrt(px * px + py * py);
		return Ray.create(x, y, px * scale, py * scale);
	*/
		double th = theta - sx * viewAngle;
		return Ray.create(x, y, Math.cos(th), Math.sin(th));
	}

	/** Turns the user and steps the user forward
	 * simulaneously.
	 *
	 * @param th the angle, in radians, to turn.
	 * @param dist the distance to go forward.
	 */
	public void turnAndStep(double th, double dist) {
		theta += th;
		if(dist != 0.0) stepForward(dist);
		else fireUserMoved();
	}

	private void stepForward(double dist) {
		Ray ray = getViewRay(0.0);
		if(dist < 0) ray = ray.getReverse();
		Intersection hit = world.getClosestIntersection(ray);
		if(hit != null && Math.abs(dist) >= hit.getDistance() - 0.001) {
			if(dist > 0) dist = hit.getDistance() - 0.001;
			else dist = -(hit.getDistance() - 0.001);
		}
		x += dist * Math.cos(theta);
		y += dist * Math.sin(theta);
		fireUserMoved();
	}
}
