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>();
	
	private static Random rnd = new Random();
	private static int language = Body.JAPANESE;
	private static int alarmPeriod = 0;
	private static boolean alarm = false;
	
	private int distance(int x, int y) {
		return x*x + y*y;
	}
	
	synchronized 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.flush();
		} finally {
			out.close();
		}
	}
	
	@SuppressWarnings("unchecked")
	synchronized 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;
		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();
		} finally {
			in.close();
		}
		Yukkuri.rnd = rnd;
		Yukkuri.alarmPeriod = alarmPeriod;
		Yukkuri.alarm = alarm;
		Yukkuri.bodyList = bodyList;
		Yukkuri.foodList = foodList;
		Yukkuri.shitList = shitList;
	}
	
	private boolean checkPartner(Body b) {
		if (b.isDead() || b.isSleeping()) {
			return false;
		}
		boolean ret = false;
		Body found = null;
		int minDistance = Box.maxX * Box.maxY;
		// find nearest neighborhood
		for (Body partner:bodyList) {
			if (partner == b) {
				continue;
			}
			if (b.getZ() != partner.getZ()) {
				continue;
			}
			if (partner.isDead() || ((b.isExciting() && !partner.isAdult()) || b.isScare())) {
				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 (b.isExciting()) {
					b.doSukkiri(found);
				}
				else if (!found.isAccessory()) {
					b.showHateYukkuri();
					found.strikeByNeedle();
				}
				else if (rnd.nextInt(20) == 0) {
					b.doSurisuri(found);
				}
			}
			else {
				if (b.isExciting()) {
					// Exciting yukkuri goes to partner.
					b.moveToSukkiri(found.getX(), found.getY());
					ret = true;
				}
				else if (!found.isAccessory() && rnd.nextInt(10) == 0) {
					// Yukkuris strikes a yukkuri without accessory.
					b.showHateYukkuri();
					b.moveTo(found.getX(), found.getY());
				}
				else if (!b.isAdult() && found.isAdult()) {
					// Koyukkuris goes to Parents.
					b.moveTo(found.getX(), found.getY());
				}
				else if (rnd.nextInt(20) == 0 && (b.isAdult() && b.getBodyType() == Body.REIMU) 
						&& (!found.isAdult() && (found.isDamaged() || found.isDirty()))) {
					// Reimu takes care of damaged or dirty koyukkuri.
					b.moveTo(found.getX() + rnd.nextInt(20) - 10, found.getY() + rnd.nextInt(20) - 10);
				}
			}
		}
		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(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.isSleeping() && !b.isExciting()) {
					b.showHateShit();
				}
			}
		}
		return ret;
	}
	
	synchronized public void addBody(int x, int y, int z, int type, int age, Body p1, Body p2, int language) {
		switch (type) {
		case Body.MARISA:
			bodyList.add(new Marisa(x, y, z, age, p1, p2, language));
			break;
		case Body.REIMU:
			bodyList.add(new Reimu(x, y, z, age, p1, p2, language));
			break;
		}
	}
	
	public void addBody(int x, int y, int z, int type, int age, Body p1, Body p2) {
		addBody(x, y, z, type, age, p1, p2, Body.JAPANESE);
	}
	
	synchronized public void addFood(int x, int y, int type) {
		foodList.add(new Food(x, y, type));
	}
	
	synchronized public void addYukkuriFood(int x, int y) {
		addFood(x, y, Food.YUKKURIFOOD);
	}
	
	synchronized public void addCake(int x, int y) {
		addFood(x, y, Food.CAKE);
	}
	
	synchronized public void addShit(int x, int y, int z, int ageState) {
		shitList.add(new Shit(x, y, z, ageState));
	}
	
	synchronized public void setLanguage(int lang) {
		switch (lang) {
		case 0:
			language = Body.JAPANESE;
			break;
		case 1:
			language = Body.ENGLISH;
			break;
		}
	}
	
	public int fontWidth() {
		int width = 13;
		switch (language) {
		case Body.JAPANESE:
			width = 13;
			break;
		case Body.ENGLISH:
			width = 7;
			break;
		}
		return width;
	}
	
	synchronized public static void setAlarm() {
		alarm = true;
		alarmPeriod = ALARM_PERIOD;
	}
	
	public static boolean getAlarm() {
		return alarm;
	}
	
	synchronized public void run() {
		if (alarmPeriod >= 0) {
			alarmPeriod--;
			if (alarmPeriod <= 0) {
				alarmPeriod = 0;
				alarm = false;
			}
		}
		int ret = 0;
		// Update food state.
		for (int i=0; i < foodList.size(); i++) {
			Food f = (Food)foodList.get(i);
			ret = f.clockTick();
			if (ret == Food.REMOVED) {
				foodList.remove(i);
			}
		}
		// Update shit state.
		for (int i=0; i < shitList.size(); i++) {
			Shit s = (Shit)shitList.get(i);
			ret = s.clockTick();
			if (ret == Shit.REMOVED) {
				shitList.remove(i);
			}
		}
		// update body state
		for (int i=0; i < bodyList.size(); i++) {
			Body b = (Body)bodyList.get(i);
			b.setLanguage(language);
			ret = b.clockTick();
			switch (ret) {
			case Body.DEAD:
				continue;
			case Body.BIRTHBABY: 
				addBody(b.getX(), b.getY(), b.getZ(), b.getBabyType(), 0, b, b.partner, language);
				break;
			case Body.DOSHIT:
				addShit(b.getX(), b.getY(), b.getZ(), b.getAgeState());
				break;
			case Body.REMOVED:
				bodyList.remove(i);
				continue;
			default:
				break;
			}

			// check Sukkiri
			if (!checkPartner(b)) {
				// check Food
				checkFood(b);
				checkShit(b);
			}
		}
	}
}