// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.

package micropolisj.engine;

import static micropolisj.engine.TileConstants.*;

public abstract class Sprite
{
	Micropolis city;

	//TODO- enforce read-only nature of the following properties
	// (i.e. do not let them be modified directly by other classes)

	public SpriteKind kind;

	public int offx;
	public int offy;
	public int width = 32;
	public int height = 32;

	public int frame;
	public int x;
	public int y;

	public int lastX;
	public int lastY;

	int dir;

	protected Sprite(Micropolis engine, SpriteKind kind)
	{
		this.city = engine;
		this.kind = kind;
	}

	protected final int getChar(int x, int y)
	{
		int xpos = x / 16;
		int ypos = y / 16;
		if (city.testBounds(xpos, ypos)) {
			return (city.getTile(xpos, ypos) & LOMASK);
		} else {
			return -1;
		}
	}

	/**
	 * For subclasses to override. Actually does the movement and animation
	 * of this particular sprite. Setting this.frame to zero will cause the
	 * sprite to be unallocated.
	 */
	protected abstract void moveImpl();

	/**
	 * Perform this agent's movement and animation.
	 */
	public final void move()
	{
		lastX = x;
		lastY = y;
		moveImpl();
		city.fireSpriteMoved(this);
	}

	/**
	 * Tells whether this sprite is visible.
	 */
	public final boolean isVisible()
	{
		return this.frame != 0;
	}

	/**
	 * Computes direction from one point to another.
	 * @return integer between 1 and 8, with
	 *          1 == north,
	 *          3 == east,
	 *          5 == south,
	 *          7 == west.
	 */
	static final int getDir(int orgX, int orgY, int desX, int desY)
	{
		final int Gdtab [] = { 0, 3, 2, 1, 3, 4, 5, 7, 6, 5, 7, 8, 1 };
		int dispX = desX - orgX;
		int dispY = desY - orgY;

		int z = dispX < 0 ? (dispY < 0 ? 11 : 8) : (dispY < 0 ? 2 : 5);

		dispX = Math.abs(dispX);
		dispY = Math.abs(dispY);
		int absDist = dispX + dispY;

		if (dispX * 2 < dispY)      z++;
		else if (dispY * 2 < dispX) z--;

		if (z >= 1 && z <= 12) {
			return Gdtab[z];
		}
		else {
			assert false;
			return 0;
		}
	}

	/**
	 * Computes manhatten distance between two points.
	 */
	static final int getDis(int x0, int y0, int x1, int y1)
	{
		return Math.abs(x0-x1) + Math.abs(y0-y1);
	}

	/**
	 * Replaces this sprite with an exploding sprite.
	 */
	final void explodeSprite()
	{
		this.frame = 0;

		city.makeExplosionAt(x, y);
		int xpos = x/16;
		int ypos = y/16;

		switch (kind) {
		case AIR:
			city.crashLocation = new CityLocation(xpos, ypos);
			city.sendMessageAtPic(MicropolisMessage.PLANECRASH_REPORT, xpos, ypos);
			break;
		case SHI:
			city.crashLocation = new CityLocation(xpos, ypos);
			city.sendMessageAtPic(MicropolisMessage.SHIPWRECK_REPORT, xpos, ypos);
			break;
		case TRA:
		case BUS:
			city.crashLocation = new CityLocation(xpos, ypos);
			city.sendMessageAtPic(MicropolisMessage.TRAIN_CRASH_REPORT, xpos, ypos);
			break;
		case COP:
			city.crashLocation = new CityLocation(xpos, ypos);
			city.sendMessageAtPic(MicropolisMessage.COPTER_CRASH_REPORT, xpos, ypos);
			break;
		}

		city.makeSound(xpos, ypos, Sound.EXPLOSION_HIGH);
	}

	/**
	 * Checks whether another sprite is in collision ranges.
	 * @return true iff the sprite is in collision range
	 */
	final boolean checkSpriteCollision(Sprite otherSprite)
	{
		if (!isVisible()) return false;
		if (!otherSprite.isVisible()) return false;

		return (getDis(this.x, this.y, otherSprite.x, otherSprite.y) < 30);
	}

	/**
	 * Destroys whatever is at the specified location,
	 * replacing it with fire, rubble, or water as appropriate.
	 */
	final void destroyTile(int xpos, int ypos)
	{
		if (!city.testBounds(xpos, ypos))
			return;

		int z = city.getTile(xpos, ypos);
		int t = z & LOMASK;

		if (t >= TREEBASE) {
			if (TileConstants.isBridge(z)) {
				city.setTile(xpos, ypos, RIVER);
				return;
			}
			if ((z & BURNBIT) == 0) {
				return; //cannot destroy it
			}
			if (isZoneCenter(z)) {
				city.killZone(xpos, ypos, z);
				if (t > RZB) {
					city.makeExplosion(xpos, ypos);
				}
			}
			if (TileConstants.checkWet(t)) {
				city.setTile(xpos, ypos, RIVER);
			}
			else {
				city.setTile(xpos, ypos,
					(char) (TINYEXP | BULLBIT | ANIMBIT));
			}
		}
	}

	/**
	 * Helper function for rotating a sprite.
	 * @param p the sprite's current attitude (1-8)
	 * @param d the desired attitude (1-8)
	 * @return the new attitude
	 */
	static final int turnTo(int p, int d)
	{
		if (p == d)
			return p;
		if (p < d) {
			if (d - p < 4) p++;
			else           p--;
		}
		else {
			if (p - d < 4) p--;
			else           p++;
		}
		if (p > 8) return 1;
		if (p < 1) return 8;
		return p;
	}

}
