import java.util.*;

public class Yukkuri {	
	private final static int ALARM_PERIOD = 300; // 30 seconds
	private final static int HEADAGELIMIT = 100; // 100 yukkuri is upper limit
	
	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 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;
		if ( b.isExciting() && b.partner != null && !b.partner.isDead() ) // makes yukkuri more monogamous
		{
			found = b.partner;
			minDistance = distance(b.getX() - found.getX(),b.getY()-found.getY());
		}
		else
		{
			// find nearest neighbour
			for (Body partner:bodyList) {
				if (partner == b) {
					continue;
				}
				if (b.getZ() != partner.getZ()) {
					continue;
				}
				if ((b.isExciting() && (!partner.isAdult() || partner.isDead() || partner.isChild(b) || partner.isParent(b))) || 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 (!found.isDead()) {
					if (b.isExciting()) {
						b.doSukkiri(found);
					}
					else if (!found.isAccessory()) {
						b.showHateYukkuri();
						found.strikeByNeedle();
					}
					else if (b.isAdult() && !found.isAdult() && found.isDirty()) {
						b.doPeropero(found);
					}
					else if (rnd.nextInt(20) == 0 && (b.isAdult() && !found.isAdult())) {
						b.doPeropero(found);
					}
					else if (rnd.nextInt(20) == 0 && (b.partner == found || b == found.partner || (!b.isAdult() && found.isAdult()))) {
						b.doSurisuri(found);
					}
				}
				else {
					if (b.isAdult()) {
						if (found.isParent(b)) {
							b.showSadnessForChild();
						}
						else if (found.partner == b) {
							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.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 (b.isAdult() && !found.isAdult() && found.isDirty()) {
						// Ault yukkuri takes care of dirty koyukkuri.
						b.moveTo(found.getX(), found.getY());
					}
				}
				else {
					if (rnd.nextInt(10) == 0) {
						if (b.isAdult()) {
							if (found.parents[0] == b || found.parents[1] == b || found.partner == b) {
								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());
				}
			}
		}
		return ret;
	}
	
	synchronized 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 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;
		}
	}
	
	synchronized public void addBody(Body b) { bodyList.add(b); }
	
	synchronized public void addFood(int x, int y, Food.type type) {
		foodList.add(new Food(x, y, type));
	}
	
	synchronized public void addYukkuriFood(int x, int y) {
		addFood(x, y, Food.type.YUKKURIFOOD);
	}
	
	synchronized public void addCake(int x, int y) {
		addFood(x, y, Food.type.CAKE);
	}
	
	synchronized public void addShit(int x, int y, int z, Body.AgeState ageState) {
		shitList.add(new Shit(x, y, z, ageState));
	}
	
	public int fontWidth() {
		int width = 13;
		switch (Body.getLanguage()) {
		case JAPANESE:
			width = 13;
			break;
		case ENGLISH:
			width = 7;
			break;
		}
		return width;
	}
	
	synchronized public static void setAlarm() {
		alarm = true;
		alarmPeriod = ALARM_PERIOD;
	}
	
	synchronized public static boolean getAlarm() {
		return alarm;
	}
	
	synchronized 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 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);
			if (bodyList.size() > HEADAGELIMIT) {
				// If number of yukkuri exceeds limit, yukkuri gets stress.
				b.stress();
			}
			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;
			}

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