import java.util.Random;

public abstract class Body extends Obj implements java.io.Serializable {
	static final long serialVersionUID = 2L;
	
	/**
	 * Used for polymorphic identification.
	 *
	 * \return An integer value that identifies the yukkuri type.
	 * 
	 * For example, "getType() == Reimu.type" identifies this Body as a Reimu. This is significantly more modular than the old version; now adding new types does not require changing Body.
	 */
	public abstract int getType ();
	public int getBodyType () { return getType(); } // for backwards-compatibility with the old functions; deprecated
	
	// public variables	
	public static final int EATAMOUNT[] = {100*4, 100*6, 100*8};
	public enum AgeState { BABY, CHILD, ADULT };
	public enum Direction { LEFT, RIGHT };
	
	// private variables
	private static final int HUNGRYLIMIT[] = {100*24, 100*24*3, 100*24*7};
	private static final int SHITLIMIT[] = {100*12, 100*24, 100*24};
	private static final int DAMAGELIMIT[] = {100*24, 100*24*3, 100*24*7};
	private static final int BABYLIMIT = 100*24*7;
	private static final int CHILDLIMIT = 100*24*21;
	private static final int LIFELIMIT = 100*24*365*5;	
	private static final int HUNGRYNOT = 0;
	private static final int HUNGRYMUCH = 1;	
	private static final int DAMAGENOT = 0;
	private static final int DAMAGEMUCH = 1;
	private static final int STEP[] = {1, 2, 4};
	private static final int RELAXPERIOD = 100*1;
	private static final int EXCITEPERIOD = 100*3;
	private static final int PREGPERIOD = 100*24;
	private static final int SLEEPPERIOD = 100*3;
	private static final int ACTIVEPERIOD = 100*6;
	private static final int NEEDLE = 100;
	private static final int HAMMER = 100*24*2;
	private static final int HOLDMESSAGE = 15;	
	private static final int STAYLIMIT = 15;
	
	protected enum Language { JAPANESE, ENGLISH };
	protected static Language language = Language.JAPANESE;
	
	protected AgeState ageState;			//BABY, CHILD, ADULT
	protected String messageBuf;	
	protected int destX = -1, destY = -1;		//destination
	protected int damage = 0;					//counter indicating damage
	protected int damageState = DAMAGENOT;
	protected int hungry = 0;					//counter indicating how hungry
	protected int hungryState = HUNGRYNOT;
	protected int shit = 0;
	protected int noDamagePeriod = 0;
	protected int noHungryPeriod = 0;
	protected boolean hasAccessory = true;	//true if having accessory
	protected boolean hasBaby = false;		//having baby or not
	protected int babyType;
	protected int pregnant = 0;
	protected boolean dead = false;			// dead of alive
	protected boolean excitement = false; 	//
	protected int excite = 0;
	protected boolean relax = false;
	protected boolean sleeping = false;
	protected int sleep = 0;
	protected long wakeUpTime;
	protected Random rnd = new Random();
	protected int countX = 0, countY = 0;					// how many steps to same direction
	protected int dirX = 0, dirY = 0;						// direction to move on
	protected Direction direction = Direction.RIGHT;	// direction of body
	protected int messageCount = 0;
	protected boolean staying = false;
	protected int staycount = 0;
	protected int stayTime = STAYLIMIT;
	protected boolean toFood = false;
	protected boolean toSukkiri = false;
	protected boolean shitting = false;
	protected boolean birth = false;
	protected boolean angry = false;
	protected boolean furifuri = false;
	protected boolean strike = false;
	protected boolean eating = false;
	protected boolean peropero = false;
	protected boolean sukkiri = false;
	protected boolean scare = false;
	protected boolean dirty = false;
	protected int falldownDamage = 0;
	protected Body partner = null;
	protected Body parents[] = {null, null};
	protected boolean crashed = false;
	protected boolean sadness = false;	
	
	// private methods
	private AgeState checkAgeState() {
		AgeState state;
		if (age < BABYLIMIT) {
			state = AgeState.BABY;
		} else if (age < CHILDLIMIT) {
			state = AgeState.CHILD;
		} else {
			state = AgeState.ADULT;
		}
		return state;
	}
	
	private int checkHungryState() {
		int state;
		if (hungry < HUNGRYLIMIT[ageState.ordinal()]/4) {
			state = HUNGRYNOT;
		} else {
			state = HUNGRYMUCH;
		}
		return state;
	}
	
	private int checkDamageState() {
		int state;
		if (damage < DAMAGELIMIT[ageState.ordinal()]/2) {
			state = DAMAGENOT;
		} else {
			state = DAMAGEMUCH;
		}
		if (damage > DAMAGELIMIT[ageState.ordinal()]) {
			dead = true;
		}
		return state;
	}
	
	private void checkHungry() {
		if (excitement == true || hasBaby == true) {
			hungry += TICK*2;
		} else {
			hungry += TICK;
		}
		if (hungry > HUNGRYLIMIT[ageState.ordinal()]) {
			damage += (hungry - HUNGRYLIMIT[ageState.ordinal()]);
			hungry = HUNGRYLIMIT[ageState.ordinal()];
		}
		if (hungryState == HUNGRYNOT && checkHungryState() == HUNGRYNOT) {
			noHungryPeriod += TICK;
		} else {
			noHungryPeriod = 0;
		}
		hungryState = checkHungryState();
	}

	private void checkDamage() {
		if (hungryState == HUNGRYNOT) {
			damage -= TICK;
		}
		else if (hungry >= HUNGRYLIMIT[ageState.ordinal()]) {
			damage += TICK;
		}
		if (damage < 0) {
			damage = 0;
		}
		if (damageState == DAMAGENOT && checkDamageState() == DAMAGENOT) {
			noDamagePeriod += TICK;
		} else {
			noDamagePeriod = 0;
		}
		damageState = checkDamageState();
	}
	
	private boolean checkShit() {
		boolean cantMove = false;
		if (hungry < HUNGRYLIMIT[ageState.ordinal()]/4) {
			shit += TICK*2;
		}
		else if (hungry < HUNGRYLIMIT[ageState.ordinal()]/2) {
			shit += TICK;
		}
		else if (shitting) {
			shit = SHITLIMIT[ageState.ordinal()] + 1; // if she is shitting, force to shit.
		}
		if (shit > SHITLIMIT[ageState.ordinal()] - TICK*100) {
			cantMove = true;
			shitting = true;
			showShit();
		}
		if (shit > SHITLIMIT[ageState.ordinal()]) {
			shitting = false;
			shit = 0;
			if (ageState == AgeState.BABY) {
				setDirty(true);
			}
			showShit2();
		}
		return cantMove;
	}

	private boolean checkPregnant() {
		boolean cantMove = false;
		if (hasBaby) {
			pregnant += TICK;
			if (pregnant > PREGPERIOD - TICK*100) {
				cantMove = true;
				birth = true;
				showBreed();
			}
			if (pregnant > PREGPERIOD) {
				// Keep babyType for generating baby.
				pregnant = 0;
				birth = false;
				hasBaby = false;
				showBreed2();
			}
		}
		return cantMove;
	}
	
	private boolean checkSleep() {
		if (wakeUpTime + ACTIVEPERIOD < age && !excitement && relax && !scare) {
			toFood = false;
			sleeping = true;
			angry = false;
			scare = false;
			sadness = false;
			sleep += TICK;
			if (sleep > SLEEPPERIOD) {
				showWakeup();
				wakeup();
			}
		} else {
			sleep = 0;
			sleeping = false;
		}
		return sleeping;
	}
	
	private void wakeup() {
		sleep = 0;
		sleeping = false;
		wakeUpTime = age;
	}
	
	private void checkExciteAndRelax() {
		if (noHungryPeriod > RELAXPERIOD && noDamagePeriod > RELAXPERIOD && !sleeping && hasAccessory) {
			if (excitement == false && relax == false) {
				int r = rnd.nextInt(24);
				if (ageState == AgeState.ADULT && hasBaby == false && r == 0) {
					excitement = true;
					relax = false;
					showExcite();
				}
				else {
					excitement = false;
					relax = true;
					showRelax();
				}
				angry = false;
				scare = false;
				sadness = false;
			}
			else {
				excite += TICK;
				if (excite > EXCITEPERIOD) {
					excite = 0;
					excitement = false;
					relax = false;
				}
			}
		}
		if (hungryState != HUNGRYNOT || damageState != DAMAGENOT || !hasAccessory) {
			relax = false;
			wakeup();
		}
	}
		
	private void checkMessage() {
		if (--messageCount <= 0) {
			messageCount = 0;
			messageBuf = null;
			furifuri = false;
			strike = false;
			eating = false;
			peropero = false;
			sukkiri = false;
		}
		if (dead) {
			showDead();
		} else if (messageBuf == null) {
			if (toFood) {
				showWantFood();
			}
			else if (toSukkiri) {
				showExcite();
			}
			else if (sleeping) {
				if (rnd.nextInt(10) == 0) {
					showSleep();
				}
			}
			else if (!hasAccessory) {
				showNoAccessory();
			}
			else if (isHungry()) {
				if (rnd.nextInt(15) == 0) {
					showHungry();
				}
			}
			else if (getZ() != 0) {
				showFlying();
			}
			else if (isDirty()) {
				showHateShit();
			}
		}
	}
	
	protected void stay() {
		staying = true;
		stayTime = STAYLIMIT;
	}
	
	protected void stay(int time) {
		staying = true;
		stayTime = time;
	}
	
	private void moveBody(boolean dontMove) {
		if (grabbed) {
			// if grabbed, cannot move.
			falldownDamage = 0;
			return;
		}
		if (z != 0) {
			// if falling down, cannot move to xy axis
			z -= 2;
			falldownDamage += 2;
			if (z <= 0) {
				z = 0;
				int jumpLevel[] = {2, 2, 1};
				if (falldownDamage >= 8/jumpLevel[ageState.ordinal()]) {
					strike(falldownDamage*100*24*4/Box.maxZ);
					if (dead) {
						crashed = true;
					}
				}
			}
			return;
		}
		if (dontMove) {
			return;
		}
		
		// moving
		int sameDest = 30 * STEP[ageState.ordinal()];
		int step;
		if (hasBaby || hungryState == HUNGRYMUCH || damageState == DAMAGEMUCH) {
			step = STEP[ageState.ordinal()]/2;
		} else {
			step = STEP[ageState.ordinal()];
		}
		if (step == 0) {
			step = 1;
		}
		int freq = STEP[AgeState.ADULT.ordinal()]/step;
		if (age % freq != 0) {
			return;
		}
		step = 1;
		// calculate x axis
		if (destX >= 0) {
			if (destX - x > step) {
				dirX = 1;
				x += step;
			} else if (x - destX > step) {
				dirX = -1;
				x -= step;
			} else {
				dirX = 0;
				destX = -1;
			}
		} else {
			if (countX++ == 0) {
				int r = rnd.nextInt(2);
				switch (dirX) {
				case 0: {
					if (r == 0) {
						dirX = 1;
					} else {
						dirX = -1;
					}
					break;
				}
				case 1: {
					if (r == 0) {
						dirX = 0;
					}
					break;
				}
				case -1: {
					if (r == 0) {
						dirX = 0;
					}
					break;
				}
				default:
					break;
				}
			} else {
				if (countX++ >= sameDest) {
					countX = 0;
				}
			}
			x += dirX * step;
		}
		if (x < 0) {
			x = 0;
			dirX *= -1;
		} else if (x > Box.maxX) {
			x = Box.maxX;
			dirX *= -1;
		}
		// calculate y axis
		if (destY >= 0) {
			if (destY - y > step) {
				dirY = 1;
				y += step;
			} else if (y - destY > step) {
				dirY = -1;
				y -= step;
			} else {
				dirY = 0;
				destY = -1;
			}
		} else {
			if (countY++ == 0) {
				int r = rnd.nextInt(2);
				switch (dirY) {
				case 0: {
					if (r == 0) {
						dirY = 1;
					} else {
						dirY = -1;
					}
					break;
				}
				case 1: {
					if (r == 0) {
						dirY = 0;
					}
					break;
				}
				case -1: {
					if (r == 0) {
						dirY = 0;
					}
					break;
				}
				default:
					break;
				}
			} else {
				if (countY++ >= sameDest) {
					countY = 0;
				}
			}
			y += dirY * step;
		}
		if (y < 0) {
			y = 0;
			dirY *= -1;
		} else if (y > Box.maxY) {
			y = Box.maxY;
			dirY *= -1;
		}
		if (dirX == -1) {
			direction = Direction.LEFT;
		} else if (dirX == 1) {
			direction = Direction.RIGHT;
		}
	}
	
	protected void setMessage(String message) {
		if (staying == true && messageBuf != null) {
			return;
		}
		messageCount = HOLDMESSAGE;
		messageBuf = message;
	}
	protected void setMessage(String message, int count) {
		if (staying == true && messageBuf != null) {
			return;
		}
		messageCount = count;
		messageBuf = message;
	}
	
	protected abstract String[] msgWantFoodJ();
	protected abstract String[] msgWantFoodE();
	
	protected void showWantFood() {
		String[] messages;
		switch (language) {
		case JAPANESE:
			messages = msgWantFoodJ();
			break;
		case ENGLISH:
			messages = msgWantFoodE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
	}
	
	protected abstract String[][] msgNoFoodJ();
	protected abstract String[][] msgNoFoodE();
	
	public void showNoFood() {
		if (toFood == false) {
			return;
		}
		toFood = false;
		
		String[][] messages;
		switch (language) {
		case JAPANESE:
			messages = msgNoFoodJ();
			break;
		case ENGLISH:
			messages = msgNoFoodE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
		stay();
	}
	
	protected abstract String msgExciteJ();
	protected abstract String msgExciteE();
	
	protected void showExcite() {
		if (excitement) {
			switch (language) {
			case JAPANESE:
				setMessage(msgExciteJ());
				break;
			case ENGLISH:
				setMessage(msgExciteE());
			}
		}
	}
	
	protected abstract String[] msgRelaxJ();
	protected abstract String[] msgFuriFuriJ();
	protected abstract String[] msgRelaxE();
	protected abstract String[] msgFuriFuriE();
	
	protected void showRelax() {
		String[] messages;
		if (rnd.nextInt(2) == 0) {
			switch (language) {
			case JAPANESE:
				messages = msgFuriFuriJ();
				break;
			case ENGLISH:
				messages = msgFuriFuriE();
				break;
			default:
				throw new LanguageException();
			}
			setMessage(messages[ageState.ordinal()], 30);
			furifuri = true;
			stay(30);
		}
		else {
			switch (language) {
			case JAPANESE:
				messages = msgRelaxJ();
				break;
			case ENGLISH:
				messages = msgRelaxE();
				break;
			default:
				throw new LanguageException();
			}
			setMessage(messages[ageState.ordinal()]);
			stay();
		}
	}
	
	protected abstract String[] msgWakeupJ();
	protected abstract String[] msgWakeupE();
	
	protected void showWakeup() {
		String[] messages;
		switch (language) {
		case JAPANESE:
			messages = msgWakeupJ();
			break;
		case ENGLISH:
			messages = msgWakeupE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
		stay();
	}
	
	protected abstract String msgSleepJ();
	protected abstract String msgSleepE();
	
	protected void showSleep() {
		if (sleep != 0) {
			switch (language) {
			case JAPANESE:
				setMessage(msgSleepJ());
				break;
			case ENGLISH:
				setMessage(msgSleepE());
				break;
			}
		}	
	}
	
	protected abstract String[][] msgScreamJ ();
	protected abstract String[][] msgScreamE ();
	
	protected void showScream() {
		String[][] messages;
		messageBuf = null; // show message immediately
		staying = false;
		switch (language) {
		case JAPANESE:
			messages = msgScreamJ();
			break;
		case ENGLISH:
			messages = msgScreamE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
		strike = true;
		stay();
	}
	
	protected abstract String[][] msgScareJ ();
	protected abstract String[][] msgScareE ();
	
	protected void showScare() {
		if (sleeping) {
			return;
		}
		String[][] messages;
		switch (language) {
		case JAPANESE:
			messages = msgScareJ();
			break;
		case ENGLISH:
			messages = msgScareE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
	}
	
	protected abstract String[][] msgAlarmJ ();
	protected abstract String[][] msgAlarmE ();
	
	protected void showAlarm() {
		if (sleeping) {
			return;
		}
		String messages[][];
		switch (language) {
		case JAPANESE:
			messages = msgAlarmJ(); 
			break;
		case ENGLISH:
			messages = msgAlarmE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
	}

	protected abstract String[] msgDyingJ ();
	protected abstract String[] msgDyingE ();
	
	protected void showDieing() {
		messageBuf = null;
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgDyingJ();
			break;
		case ENGLISH:
			messages = msgDyingE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
		stay();
	}
	
	protected abstract String[] msgDeadJ ();
	protected abstract String[] msgDeadE ();
	
	protected void showDead() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgDeadJ();
			break;
		case ENGLISH:
			messages = msgDeadE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
	}
	
	protected abstract String[][] msgEatingJ ();
	protected abstract String[][] msgEatingE ();
	
	protected void showEating() {
		String messages[][];
		switch (language) {
		case JAPANESE:
			messages = msgEatingJ();
			break;
		case ENGLISH:
			//Yoga Note* Check はふはふ
			messages = msgEatingE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[hungryState][ageState.ordinal()]);
		eating = true;
		stay();
	}
	
	protected abstract String[] msgFullJ ();
	protected abstract String[] msgFullE ();
	
	protected void showFull() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgFullJ();
			break;
		case ENGLISH:
			messages = msgFullE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
		stay();
	}
	
	protected abstract String[][] msgHealingJ ();
	protected abstract String[][] msgHealingE ();
	
	protected void showHealing() {
		String messages[][];
		switch (language) {
		case JAPANESE:
			messages = msgHealingJ();
			break;
		case ENGLISH:
			messages = msgHealingE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
		stay();
	}
	
	protected abstract String[] msgSukkiriJ ();
	protected abstract String[] msgSukkiriE ();
	
	protected void showSukkiri() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgSukkiriJ();
			break;
		case ENGLISH:
			messages = msgSukkiriE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()], 30);
		sukkiri = true;
		stay(30);
	}
	
	protected abstract String msgBirthJ ();
	protected abstract String msgBirthE ();
	
	protected void showBirth() {
		switch (language) {
		case JAPANESE:
			setMessage(msgBirthJ(), 30);
			break;
		case ENGLISH:
			setMessage(msgBirthE(), 30);
			break;
		}
		stay(30);
	}
	
	protected abstract String[] msgShitJ ();
	protected abstract String[] msgShitE ();
	
	protected void showShit() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgShitJ();
			break;
		case ENGLISH:
			messages = msgShitE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
	}
	
	protected abstract String[] msgShit2J ();
	protected abstract String[] msgShit2E ();
	
	protected void showShit2() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgShit2J();
			break;
		case ENGLISH:
			messages = msgShit2E();
			break;
		default:
			throw new LanguageException();
		}
		furifuri = true;
		setMessage(messages[ageState.ordinal()]);
		stay();
	}
	
	protected abstract String[] msgSuriSuriJ ();
	protected abstract String[] msgSuriSuriE ();
	
	protected void showSurisuri() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgSuriSuriJ();
			break;
		case ENGLISH:
			messages = msgSuriSuriE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
		stay();
	}
	
	protected abstract String[] msgPeroPeroJ ();
	protected abstract String[] msgPeroPeroE ();
	
	protected void showPeroPero() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgPeroPeroJ();
			break;
		case ENGLISH:
			messages = msgPeroPeroE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
		peropero = true;
		stay();
	}
	
	protected abstract String msgBreedJ ();
	protected abstract String msgBreedE ();
	
	protected void showBreed() {
		switch (language) {
		case JAPANESE:
			setMessage(msgBreedJ());
			break;
		case ENGLISH:
			setMessage(msgBreedE());
			break;
		}
	}
	
	protected abstract String msgBreed2J ();
	protected abstract String msgBreed2E ();
	
	protected void showBreed2() {
		messageBuf = null;
		switch (language) {
		case JAPANESE:
			setMessage(msgBreed2J());
			break;
		case ENGLISH:
			setMessage(msgBreed2E());
			break;
		}
		furifuri = true;
		stay();
	}
	
	protected abstract String[][] msgHateShitJ ();
	protected abstract String[][] msgHateShitE ();
	
	public void showHateShit() {
		String messages[][];
		switch (language) {
		case JAPANESE:
			messages = msgHateShitJ();
			break;
		case ENGLISH:
			messages = msgHateShitE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
	}
	
	protected abstract String[][] msgHungryJ ();
	protected abstract String[][] msgHungryE ();
	
	protected void showHungry() {
		String messages[][];
		switch (language) {
		case JAPANESE:
			messages = msgHungryJ();
			break;
		case ENGLISH:
			messages = msgHungryE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[damageState][ageState.ordinal()]);
		stay();
	}
	
	protected abstract String[] msgNoAccessoryJ ();
	protected abstract String[] msgNoAccessoryE ();
	
	protected void showNoAccessory() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgNoAccessoryJ();
			break;
		case ENGLISH: 
			messages = msgNoAccessoryE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
	}
	
	protected abstract String[] msgHateYukkuriJ ();
	protected abstract String[] msgHateYukkuriE ();
	
	public void showHateYukkuri() {
		String[] messages;
		switch (language) {
		case JAPANESE:
			messages = msgHateYukkuriJ();
			break;
		case ENGLISH:
			messages = msgHateYukkuriE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
	}
	
	protected abstract String[] msgFlyingJ ();
	protected abstract String[] msgFlyingE ();
	
	protected void showFlying() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgFlyingJ();
			break;
		case ENGLISH:
			messages = msgFlyingE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
	}

	protected abstract String msgSadnessForChildJ ();
	protected abstract String msgSadnessForChildE ();

	protected void showSadnessForChild() {
		String messages;
		switch (language) {
		case JAPANESE:
			messages = msgSadnessForChildJ();
			break;
		case ENGLISH:
			messages = msgSadnessForChildE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages);
		sadness = true;
	}

	protected abstract String msgSadnessForPartnerJ (Body partner);
	protected abstract String msgSadnessForPartnerE (Body partner);
	
	protected void showSadnessForPartner(Body partner) {
		String messages;
		switch (language) {
		case JAPANESE:
			messages = msgSadnessForPartnerJ(partner);
			break;
		case ENGLISH:
			messages = msgSadnessForPartnerE(partner);
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages);
		sadness = true;
	}

	// public methods
	public Body(int initX, int initY, int initZ, AgeState initAgeState, Body p1, Body p2) {
		objType = YUKKURI;
		x = initX;
		y = initY;
		z = initZ;
		parents[0] = p1;
		parents[1] = p2;
		switch (initAgeState) {
		case BABY:
			age = 0;
			showBirth();
			break;
		case CHILD:
			age = BABYLIMIT;
			break;
		case ADULT:
		default:
			age = CHILDLIMIT;
			break;
		}
		age += rnd.nextInt(100);
		ageState = checkAgeState();
		shit = rnd.nextInt(SHITLIMIT[ageState.ordinal()]);
		wakeUpTime = age;
		removed = false;
	}
	
	@Override
	public void remove() {
		removed = true;
		parents[0] = null;
		parents[1] = null;
		partner = null;
	}
		
	public int getBabyType() {
		return babyType;
	}
	
	public AgeState getAgeState() {
		return ageState;
	}
	
	public long getAge() {
		return age;
	}
	
	public int getEatAmount() {
		return EATAMOUNT[ageState.ordinal()];
	}
	
	public int getStep() {
		return STEP[ageState.ordinal()];
	}
	
	public String getMessage() {
		return messageBuf;
	}
	
	public Direction getDirection() {
		return direction;
	}
	
	public void setDirty(boolean flag) {
		dirty = flag;
	}
	
	public boolean isDead() {
		return dead;
	}
	
	public boolean isAdult() {
		if (ageState == AgeState.ADULT) {
			return true;
		}
		else {
			return false;
		}
	}
	
	public boolean isSleeping() {
		if (dead) {
			return false;
		}
		return sleeping;
	}
	
	public boolean isHungry() {
		if (dead) {
			return false;
		}
		if (hungry >= HUNGRYLIMIT[ageState.ordinal()]/8) {
			return true;
		}
		return false;
	}
	
	public boolean isShitting() {
		if (dead) {
			return false;
		}
		return shitting;
	}
	
	public boolean isExciting() {
		if (dead) {
			return false;
		}
		return excitement;
	}
	
	public boolean isAngry() {
		if (dead) {
			return false;
		}
		return angry;
	}
	
	public boolean isScare() {
		if (dead) {
			return false;
		}
		return scare;
	}
	
	public boolean isSadness() {
		if (dead) {
			return false;
		}
		return sadness;
	}
		
	public boolean isFurifuri() {
		if (dead) {
			return false;
		}
		return (furifuri);
	}
	
	public boolean isStrike() {
		if (dead) {
			return false;
		}
		return strike;
	}
	
	public boolean isDamaged() {
		if (checkDamageState() == DAMAGEMUCH) {
			return true;
		}
		return false;
	}
	
	public boolean isBirth() {
		if (dead) {
			return false;
		}
		if (birth) {
			return true;
		}
		return false;
	}
	
	public boolean isAccessory() {
		return hasAccessory;
	}
	
	public boolean isEating() {
		if (dead) {
			return false;
		}
		return eating;
	}
	
	public boolean isPeroPero() {
		if (dead) {
			return false;
		}
		return peropero;
	}
	
	public boolean isSukkiri() {
		if (dead) {
			return false;
		}
		return sukkiri;
	}
	
	public boolean isDirty() {
		if (dead) {
			return true;
		}
		return dirty;
	}
		
	public boolean isCrashed() {
		return crashed;
	}
		
	public void doSukkiri(Body p) {
		if (dead) {
			return;
		}
		toSukkiri = false;
		// show message
		showSukkiri();
		p.showSukkiri();
		// change state
		excitement = false;
		relax = false;
		angry = false;
		scare = false;
		sadness = false;
		noHungryPeriod = 0;
		hungry += 100*12;
		hungryState = checkHungryState();
		//change partner state
		if (!p.hasBaby) {
			p.hasBaby = true;
			if (rnd.nextInt(2) == 0) {
				p.babyType = getType();
			} else {
				p.babyType = p.getType();
			}
			p.partner = this;
		}
		p.excitement = false;
		p.relax = false;
		p.angry = false;
		p.scare = false;
		p.sadness = false;
		p.noHungryPeriod = 0;
		p.hungry += 100*24;
		p.hungryState = p.checkHungryState();
	}
	
	public void forceSukkiri() {
		if (dead || !hasAccessory) {
			return;
		}
		if (hasBaby == false) {
			excitement = true;
			showExcite();
		}
		stay();
	}
	
	public void doSurisuri(Body partner) {
		if (dead) {
			return;
		}
		if (ageState == AgeState.ADULT && partner.ageState != AgeState.ADULT) {
			partner.setDirty(false);
			showPeroPero();
		}
		else {
			showSurisuri();
		}
		angry = false;
		partner.angry = false;
		scare = false;
		partner.scare = false;
		sadness = false;
		partner.sadness = false;
		//partner.stay();
	}
	
	public void moveTo(int toX, int toY) {
		if (dead) {
			return;
		}
		if (toX <= 0) {
			destX = 0;
		} else if (toX >= Box.maxX) {
			destX = Box.maxX;
		} else {
			destX = toX;
		}
		if (toY <= 0) {
			destY = 0;
		} else if (toY >= Box.maxY) {
			destY = Box.maxY;
		} else {
			destY = toY;
		}
	}
	
	public void moveToFood(int toX, int toY) {
		toFood = true;
		toSukkiri = false;
		moveTo(toX, toY);
	}
	
	public void moveToSukkiri(int toX, int toY) {
		toSukkiri = true;
		toFood = false;
		moveTo(toX, toY);
	}
	
	public void lookTo(int toX, int toY) {
		if (dead || sleeping) {
			return;
		}
		if (toX > x) {
			direction = Direction.RIGHT;
		}
		else if (toX < x) {
			direction = Direction.LEFT;
		}
		stay();
	}
	
	public void eatFood(int amount) {
		if (dead) {
			return;
		}
		toFood = false;
		if (!isHungry()) {
			showFull();
			return;
		}
		hungry -= amount;
		if (hungry < 0) {
			hungry = 0;
		}
		showEating();
		hungryState = checkHungryState();
		angry = false;
		scare = false;
		sadness = false;
	}
	
	public void strike(int amount) {
		if (dead) {
			return;
		}
		damage += amount;
		showScream();
		damageState = checkDamageState();
		setAngry();
		wakeup();
	}
	
	public void strikeByNeedle() {
		strike(NEEDLE);
	}

	public void strikeByHammer() {
		if (dead) {
			return;
		}
		strike(HAMMER);
		if (damage > DAMAGELIMIT[ageState.ordinal()]) {
			showDieing();
			if (ageState != AgeState.ADULT) {
				crashed = true;
			}
		}
	}
	
	public void takeAccessory() {
		hasAccessory = false;
	}
	
	public void giveAccessory() {
		hasAccessory = true;
	}
	
	public void giveJuice(){
		if (dead) {
			return;
		}
		damage -= 100*100;
		if (damage < 0) {
			damage = 0;
		}
		hungry -= 100*100;
		if (hungry < 0) {
			hungry = 0;
		}
		showHealing();
		damageState = checkDamageState();
		hungryState = checkHungryState();
		angry = false;
		scare = false;
		sadness = false;
		toFood = false;
		excitement = false;
	}
	
	public void runAway(int fromX, int fromY) {
		if (dead || sleeping || excitement || angry) {
			return;
		}
		int toX, toY;
		if (x > fromX) {
			toX = Box.maxX;
		}
		else {
			toX = 0;
		}
		if (y > fromY) {
			toY = Box.maxY;
		}
		else {
			toY = 0;
		}
		moveTo(toX, toY);
		toFood = false;
		scare = true;
		excitement = false;
	}
	
	public void setAngry() {
		if (damageState == DAMAGENOT && hasAccessory) {
			angry = true;
			scare = false;
			excitement = false;
		}
	}
	
	public static void setLanguage(Language lang) {
		language = lang;
	}

	// calling every tick
	@Override
	public int clockTick() {
		int retval = DONOTHING;
		
		// if removed, remove body
		if (removed) {
			return REMOVED;
		}
		
		// if dead, do nothing.
		if (dead) {
			moveBody(true); // for falling the body
			checkMessage();
			return DEAD;
		}
		
		// check age
		age += TICK;
		if (age > LIFELIMIT) {
			dead = true;
		}
		ageState = checkAgeState();
		
		// check hungry
		checkHungry();
		
		// check damage
		checkDamage();
		
		// check events
		boolean dontMove = false;
		
		// check shit
		int oldShit = shit;
		if (checkShit()) {
			dontMove = true;
		}
		if (oldShit != 0 && shit == 0) {
			retval = DOSHIT;
		}
		
		// check pregnant
		boolean oldHasBaby = hasBaby;
		if (checkPregnant()) {
			dontMove = true;
		}
		if (oldHasBaby == true && hasBaby == false) {
			retval = BIRTHBABY;
		}
		
		// check sleep
		if (checkSleep()) {
			dontMove = true;
		}
		
		// check relax and excitement
		checkExciteAndRelax();
		
		if (staying) {
			staycount += TICK;
			if (staycount > stayTime) {
				staycount = 0;
				staying = false;
			} else {
				dontMove = true;
			}
		}

		// move to destination
		// if there is no destination, walking randomly.
		moveBody(dontMove);
		
		//
		checkMessage();
		
		return retval;
	}
}
