import java.util.*;

public class Yukkuri {	
	private final static int ALARM_PERIOD = 300; // 30 seconds

	public static ArrayList<Body> bodyList = new ArrayList<Body>();
	public static ArrayList<Food> foodList = new ArrayList<Food>();
	public static ArrayList<Shit> shitList = new ArrayList<Shit>();
	public static ArrayList<Toilet>	toiletList = new ArrayList<Toilet>();

	private static Random rnd = new Random();
	private static int alarmPeriod = 0;
	private static boolean alarm = false;

	private int distance(int x, int y) {
		return x*x + y*y;
	}

	public static void saveState(java.io.File filename) throws java.io.IOException {
		java.io.ObjectOutputStream out =
				new java.io.ObjectOutputStream(
						new java.io.BufferedOutputStream(
								new java.io.FileOutputStream(filename)));
		try {
			out.writeUTF(Yukkuri.class.getCanonicalName());
			out.writeInt(alarmPeriod);
			out.writeBoolean(alarm);
			out.writeObject(rnd);
			out.writeObject(bodyList);
			out.writeObject(foodList);
			out.writeObject(shitList);
			out.writeObject(toiletList);
			out.flush();
		} finally {
			out.close();
		}
	}

	@SuppressWarnings("unchecked")
	public static void loadState(java.io.File filename) throws java.io.IOException, ClassNotFoundException {
		java.io.ObjectInputStream in =
				new java.io.ObjectInputStream(
						new java.io.BufferedInputStream(
								new java.io.FileInputStream(filename)
								));
		int alarmPeriod;
		boolean alarm;
		Random rnd;
		ArrayList<Body> bodyList;
		ArrayList<Food> foodList;
		ArrayList<Shit> shitList;
		ArrayList<Toilet> toiletList;
		try {
			String s = in.readUTF();
			if(!Yukkuri.class.getCanonicalName().equals(s)) {
				String errMsg = "Bad save: "+s;
				throw new java.io.IOException(errMsg);
			}
			alarmPeriod = in.readInt();
			alarm = in.readBoolean();
			rnd = (Random)in.readObject();
			bodyList = (ArrayList<Body>)in.readObject();
			foodList = (ArrayList<Food>)in.readObject();
			shitList = (ArrayList<Shit>)in.readObject();
			toiletList = (ArrayList<Toilet>)in.readObject();
		} finally {
			in.close();
		}
		Yukkuri.rnd = rnd;
		Yukkuri.alarmPeriod = alarmPeriod;
		Yukkuri.alarm = alarm;
		Yukkuri.bodyList = bodyList;
		Yukkuri.foodList = foodList;
		Yukkuri.shitList = shitList;
		Yukkuri.toiletList = toiletList;
	}

	private boolean checkPartner(Body b) {
		if (b.isDead() || b.isSleeping() || b.isScare()) {
			return false;
		}
		boolean ret = false;
		Body found = null;
		int minDistance = Box.maxX * Box.maxY;
		if ( b.isExciting() && b.partner != null && !b.partner.isDead() && !b.isRaper() ) {
			found = b.partner;
			minDistance = distance(b.getX() - found.getX(),b.getY()-found.getY());
		}
		else {
			// find nearest neighbor
			for (Body partner:bodyList) {
				if (partner == b) {
					continue;
				}
				if (b.getZ() != partner.getZ()) {
					continue;
				}
				if (b.isExciting()) {
					if (partner.isDead()) {
						continue;
					}
					if (!b.isRaper() && (!partner.isAdult() || partner.isChild(b) || partner.isParent(b))) {
						continue;
					}
				}
				int dist, dx, dy;
				dx = b.getX() - partner.getX();
				dy = b.getY() - partner.getY();
				dist = distance(dx, dy);
				if (minDistance > dist) {
					found = partner;
					minDistance = dist;
				}
			}
		}
		if (found != null) {
			if (minDistance <= distance(b.getStep(), b.getStep())) {
				if (!found.isDead()) {
					if (b.isExciting()) {
						b.doSukkiri(found);
					}
					else if (!found.hasAccessory()) {
						b.showHateYukkuri();
						found.strikeByNeedle();
					}
					else if (b.isAdult() && !found.isAdult() && (found.isChild(b) || b.isMotherhood()) && found.isDirty()) {
						b.doPeropero(found);
					}
					else if (rnd.nextInt(20) == 0 && b.isParent(found)) {
						b.doPeropero(found);
					}
					else if (rnd.nextInt(20) == 0 && (found.isPartner(b) || found.isParent(b))) {
						b.doSurisuri(found);
					}
				}
				else {
					if (b.isAdult()) {
						if (b.isParent(found)) {
							b.showSadnessForChild();
						}
						else if (b.isPartner(found)) {
							b.showSadnessForPartner(found);
						}
					}
				}
			}
			else {
				if (!found.isDead()) {
					if (b.isExciting()) {
						// Exciting yukkuri goes to partner.
						b.moveToSukkiri(found.getX(), found.getY());
						ret = true;
					}
					else if (!found.hasAccessory() && rnd.nextInt(10) == 0) {
						// Yukkuris strikes a yukkuri without accessory.
						b.showHateYukkuri();
						b.moveTo(found.getX(), found.getY());
					}
					else if (!b.isAdult() && found.isParent(b)) {
						// Koyukkuris goes to Parents.
						b.moveTo(found.getX(), found.getY());
					}
					else if (b.isAdult() && !found.isAdult() && (found.isChild(b) || b.isMotherhood()) && found.isDirty()) {
						// Adult yukkuri takes care of dirty koyukkuri.
						b.moveTo(found.getX(), found.getY());
					}
				}
				else {
					if (rnd.nextInt(10) == 0) {
						if (b.isAdult()) {
							if (b.isParent(found) || b.isPartner(found)) {
								b.moveTo(found.getX(), found.getY());
							}
							else {
								b.lookTo(found.getX(), found.getY());
							}
						}
						else {
							b.runAway(found.getX(), found.getY());
						}
						b.showScare();
					}
				}
			}
		}
		return ret;
	}

	private boolean checkFood(Body b) {
		boolean ret = false;
		if (b.isSleeping() || b.isDead() || !b.isHungry()) {
			return false;
		}
		Food found = null;
		int minDistance = Box.maxX * Box.maxY;
		for (Food f:foodList) {
			if (f.isEmpty()) {
				continue;
			}
			if (b.getZ() != f.getZ()) {
				continue;
			}
			int distance, dx, dy;
			dx = b.getX() - f.getX();
			dy = b.getY() - f.getY();
			distance = distance(dx, dy);
			if (minDistance > distance) {
				found = f;
				minDistance = distance;
			}
		}
		if (found != null) {
			if (minDistance <= distance(b.getStep(), b.getStep())) {
				b.eatFood(found.getFoodType(), b.getEatAmount());
				found.eatFood(b.getEatAmount());
			} else {
				// go to nearest food
				b.moveToFood(found.getX(), found.getY());
				ret = true;
			}
		}
		else {
			b.showNoFood();
		}
		return ret;
	}

	private boolean checkShit(Body b) {
		boolean ret = false;
		Shit found = null;
		int minDistance = Box.maxX * Box.maxY;
		for (Shit s:shitList) {
			if (b.getZ() != s.getZ()) {
				continue;
			}
			int distance, dx, dy;
			dx = b.getX() - s.getX();
			dy = b.getY() - s.getY();
			distance = distance(dx, dy);
			if (minDistance > distance) {
				found = s;
				minDistance = distance;
			}
		}
		if (found != null) {
			if (minDistance <= distance(b.getStep(), b.getStep())) {
				if (b.isTooHungry()) {
					b.eatFood(Food.type.SHIT, b.getEatAmount());
					found.eatShit(b.getEatAmount());
				}
				else if (!b.isSleeping() && !b.isExciting()) {
					b.showHateShit();
				}
			}
			else {
				if (b.isTooHungry()) {
					b.moveTo(found.getX(), found.getY());
					ret = true;
				}
			}
		}
		return ret;
	}

	private boolean checkToilet(Body b) {
		boolean ret = false;
		Toilet found = null;
		int minDistance = Box.maxX * Box.maxY;
		for (Toilet t:toiletList) {
			int distance, dx, dy;
			dx = b.getX() - t.getX();
			dy = b.getY() - t.getY() - t.getSize()/6;
			distance = distance(dx, dy);
			if (minDistance > distance) {
				found = t;
				minDistance = distance;
			}
		}
		if (found != null) {
			if (b.wantToShit()) {
				b.moveTo(found.getX(), found.getY() - found.getSize()/6);
				ret = true;
			}
		}
		return ret;
	}

	public void addBody(int x, int y, int z, int type, Body.AgeState age, Body p1, Body p2) {
		switch (type) {
		case Marisa.type:
			bodyList.add(new Marisa(x, y, z, age, p1, p2));
			break;
		case Reimu.type:
			bodyList.add(new Reimu(x, y, z, age, p1, p2));
			break;
		case Alice.type:
			bodyList.add(new Alice(x, y, z, age, p1, p2));
			break;
		case WasaReimu.type:
			bodyList.add(new WasaReimu(x, y ,z, age, p1, p2));
			break;
		case MarisaReimu.type:
			bodyList.add(new MarisaReimu(x, y, z, age, p1, p2));
			break;
		case ReimuMarisa.type:
			bodyList.add(new ReimuMarisa(x, y, z, age, p1, p2));
			break;
		}
	}

	public void addBody(Body b) {
		bodyList.add(b);
	}

	public void addFood(int x, int y, Food.type type) {
		foodList.add(new Food(x, y, type));
	}

	public void addYukkuriFood(int x, int y) {
		addFood(x, y, Food.type.YUKKURIFOOD);
	}

	public void addCake(int x, int y) {
		addFood(x, y, Food.type.CAKE);
	}

	public void addShit(int x, int y, int z, Body.AgeState ageState) {
		shitList.add(new Shit(x, y, z, ageState));
	}

	public void addToilet(int x, int y) {
		toiletList.add(new Toilet(x, y));
	}

	public int fontWidth() {
		int width = 13;
		switch (Body.getLanguage()) {
		case JAPANESE:
			width = 13;
			break;
		case ENGLISH:
			width = 7;
			break;
		}
		return width;
	}

	public static void setAlarm() {
		alarm = true;
		alarmPeriod = ALARM_PERIOD;
	}

	public static boolean getAlarm() {
		return alarm;
	}

	public void run() {
		if (alarmPeriod >= 0) {
			alarmPeriod--;
			if (alarmPeriod <= 0) {
				alarmPeriod = 0;
				alarm = false;
			}
		}
		Obj.Event ret = Obj.Event.DONOTHING;
		// Update food state.
		for (int i=0; i < foodList.size(); i++) {
			Food f = (Food)foodList.get(i);
			ret = f.clockTick();
			if (ret == Obj.Event.REMOVED) {
				foodList.remove(i);
			}
		}
		// Update toilet state.
		for (int i=0; i < toiletList.size(); i++) {
			Toilet t = (Toilet)toiletList.get(i);
			ret = t.clockTick();
			if (ret == Obj.Event.REMOVED) {
				toiletList.remove(i);
			}
		}
		// Update shit state.
		for (int i=0; i < shitList.size(); i++) {
			Shit s = (Shit)shitList.get(i);
			ret = s.clockTick();
			if (ret == Obj.Event.REMOVED) {
				shitList.remove(i);
			}
		}
		// update body state
		for (int i=0; i < bodyList.size(); i++) {
			Body b = (Body)bodyList.get(i);
			b.putStress(bodyList.size()); // Yukkuri is getting stress according as number of bodies.
			ret = b.clockTick();
			switch (ret) {
			case DEAD:
				continue;
			case BIRTHBABY: 
				addBody(b.getX(), b.getY(), b.getZ(), b.getBabyType(), Body.AgeState.BABY, b, b.partner);
				break;
			case DOSHIT:
				addShit(b.getX(), b.getY(), b.getZ(), b.getAgeState());
				break;
			case REMOVED:
				bodyList.remove(i);
				continue;
			default:
				break;
			}
			if (!b.isRude()) {
				// check Toilet
				if (!checkToilet(b)) {
					// check Sukkiri
					if (!checkPartner(b)) {
						// check Food
						if (!checkFood(b)) {
							// check shit
							checkShit(b);
						}
					}
				}				
			}
			else {
				// check Sukkiri
				if (!checkPartner(b)) {
					// check Food
					if (!checkFood(b)) {
						// check shit
						if (!checkShit(b)) {
							// check toilet
							checkToilet(b);
						}
					}
				}
			}
		}
	}
}