import java.awt.Image;
import java.util.Random;
import java.lang.RuntimeException;

public abstract class Body extends Obj implements java.io.Serializable {
	static final long serialVersionUID = 4L;
	
	/**
	 * 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 ();
	/// \return the type identifier of hybrid offspring. If the pairing is not supported, return own type instead.
	public int getHybridType (int partnerType) { return getType(); }
	public boolean isHybrid () { return false; }
	
	public abstract String getNameJ ();
	public abstract String getNameE ();
	public abstract Body clone ();
	public abstract Image getImage (int type, int direction, AgeState age);
	public int getBodyType () { return getType(); } // for backwards-compatibility with the old functions; deprecated
	
	// public variables	
	public static final int EATAMOUNT[] = {100, 100*2, 100*4};
	public static final int maxSize = myPane.maxSize;
	public enum AgeState { BABY, CHILD, ADULT };
	public enum Direction { LEFT, RIGHT };
	
	// private variables
	protected enum Hunger { NONE, VERY };
	protected enum Damage { NONE, VERY }
	protected enum Happiness { VERY_HAPPY, HAPPY, AVERAGE, SAD, VERY_SAD };
	protected enum Attitude { VERY_NICE, NICE, AVERAGE, SHITHEAD, SUPER_SHITHEAD };
	
	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; // 100*24*7
	private static final int CHILDLIMIT = 100*24*4; // 100*24*21
	private static final int LIFELIMIT = 100*24*365*5;
	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*2; // 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;
	
	// Used in image loading.
	protected static final int babyHeight = maxSize/4, babyWidth = maxSize/4;
	protected static final int childHeight = maxSize/2, childWidth = maxSize/2;
	protected static final int babyIndex = Body.AgeState.BABY.ordinal();
	protected static final int childIndex = Body.AgeState.CHILD.ordinal();
	protected static final int adultIndex = Body.AgeState.ADULT.ordinal();
	
	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 Damage damageState = Damage.NONE;
	protected int hungry = 0;					//counter indicating how hungry
	protected Hunger hungryState = Hunger.NONE;
	protected int attitude = 0;				// counter indicating shithead/nicehead etc.
	protected int happiness = 0;
	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 Body partner = null;
	protected Body parents[] = {null, null};
	
	// there has to be a better way of handling this but I am at a loss
	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 boolean crashed = false;
	protected boolean sadness = false;	
	protected boolean eatingShit = false;
	
	// private methods
	private AgeState checkAgeState()
	{
		if (age < BABYLIMIT) { return AgeState.BABY; }
		else if (age < CHILDLIMIT) { return AgeState.CHILD; }
		return AgeState.ADULT;
	}
	
	private Hunger checkHungryState()
	{
		if (hungry < HUNGRYLIMIT[ageState.ordinal()]/4) { return Hunger.NONE; }
		return Hunger.VERY;
	}
	
	private Damage checkDamageState()
	{
		if (damage > DAMAGELIMIT[ageState.ordinal()]) { dead = true; return Damage.VERY; }
		if (damage >= DAMAGELIMIT[ageState.ordinal()]/2) { return Damage.VERY; }
		return Damage.NONE;
	}
	
	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 == Hunger.NONE && checkHungryState() == Hunger.NONE) {
			noHungryPeriod += TICK;
		} else {
			noHungryPeriod = 0;
		}
		hungryState = checkHungryState();
	}

	private void checkDamage() {
		if (hungryState == Hunger.NONE) { damage -= TICK; }
		else if (hungry >= HUNGRYLIMIT[ageState.ordinal()]) { damage += TICK; }
		
		if (damage < 0) { damage = 0; }
		
		Damage newDamageState = checkDamageState();
		if (damageState == Damage.NONE && newDamageState == Damage.NONE) { noDamagePeriod += TICK; }
		else { noDamagePeriod = 0; }
		damageState = newDamageState;
	}
	
	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 != Hunger.NONE || damageState != Damage.NONE || !hasAccessory)
		{
			relax = false;
			wakeup();
		}
	}
		
	private void checkMessage() {
		if (--messageCount <= 0) {
			messageCount = 0;
			messageBuf = null;
			furifuri = false;
			strike = false;
			eating = false;
			eatingShit = 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 = STEP[ageState.ordinal()];
		if (hasBaby || hungryState == Hunger.VERY || damageState == Damage.VERY) { step /= 2; }
		
		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.ordinal()][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.ordinal()][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.ordinal()][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.ordinal()][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.ordinal()][ageState.ordinal()]);
		eating = true;
		stay();
	}

	protected abstract String[] msgEatingShitJ ();
	protected abstract String[] msgEatingShitE ();
	
	protected void showEatingShit() {
		String messages[];
		switch (language) {
		case JAPANESE:
			messages = msgEatingShitJ();
			break;
		case ENGLISH:
			messages = msgEatingShitE();
			break;
		default:
			throw new LanguageException();
		}
		setMessage(messages[ageState.ordinal()]);
		eatingShit = 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.ordinal()][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.ordinal()][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.ordinal()][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 = Type.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;
	}
	
	public static Body fromType (int type, int state)
	{
		String[] ageState = {"BABY", "CHILD", "ADULT"};
		Random rnd = new Random();
		switch (type)
		{
		case Marisa.type:
			return new Marisa(rnd.nextInt(Box.maxX),rnd.nextInt(Box.maxY),0,AgeState.valueOf(ageState[state]),null,null);
		case Reimu.type:
			return new Reimu(rnd.nextInt(Box.maxX),rnd.nextInt(Box.maxY),0,AgeState.valueOf(ageState[state]),null,null);
		default:
			throw new RuntimeException("Unknown yukkuri type.");
		}
	}
	
	protected Body (Body b) {
		objType = b.objType;
		x = b.x;
		y = b.y;
		z = b.z;
		parents[0] = b.parents[0];
		parents[1] = b.parents[1];
		age = b.age;
		ageState = b.ageState;
		shit = b.shit;
		wakeUpTime = b.wakeUpTime;
		removed = b.removed;
	}
	
	@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()
	{
		return (!dead && hungry >= HUNGRYLIMIT[ageState.ordinal()]/8);
	}
	
	public boolean isTooHungry()
	{
		return (!dead && hungry >= HUNGRYLIMIT[ageState.ordinal()] && checkDamageState() == Damage.VERY);
	}
	
	public boolean isShitting() { return (!dead && shitting); }
	public boolean isExciting() { return (!dead && excitement); }
	public boolean isAngry() { return (!dead && angry); }
	public boolean isScare() { return (!dead && scare); }
	public boolean isSadness() { return (!dead && sadness); }
	public boolean isFurifuri() { return (!dead && furifuri); }
	public boolean isStrike() { return (!dead && strike); }
	public boolean isDamaged() { return (checkDamageState() == Damage.VERY); }
	public boolean isBirth() { return (!dead && birth); }
	public boolean isAccessory() { return hasAccessory; }
	public boolean isEating() { return (!dead && eating); }
	public boolean isEatingShit() { return (!dead && eatingShit); }
	public boolean isPeroPero() { return (!dead && peropero); }
	public boolean isSukkiri() { return (!dead && sukkiri); }
	public boolean isDirty() { return (dead || dirty); }
	public boolean isCrashed() { return crashed; }
	
	/// Returns true if other is one of this body's parents, otherwise false.
	public boolean isParent (Body other) { return ( parents[0] == other || parents[1] == other); }
	public boolean isChild (Body other) { return other.isParent(this); }
		
	public void doSukkiri(Body p) {
		if (dead) { return; }
		
		// 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) {
			int myType = getType();
			int partnerType = p.getType();
			boolean asHybrid = false;
			p.hasBaby = true;
			
			if ( isHybrid() ) // hybrids breed as a random one of their component types, or rarely their sort of hybrid
			{
				if (rnd.nextInt(50) == 0) { asHybrid = true; }
				else if ( rnd.nextInt(2) == 0 ) { myType = ((HybridBody) this).hybridType1(); }
				else { myType = ((HybridBody) this).hybridType2(); }
			}
			if ( p.isHybrid() )
			{
				if (rnd.nextInt(50) == 0) {  }
				else if ( rnd.nextInt(2) == 0 ) { partnerType = ((HybridBody) p).hybridType1(); }
				else { partnerType = ((HybridBody) p).hybridType2(); }
			}
			
			if (!asHybrid && rnd.nextInt(100) == 0) { p.babyType = p.getHybridType(myType); } // no hybrids of hybrids
			else if (rnd.nextInt(2) == 0) { p.babyType = myType; }
			else { p.babyType = partnerType; }
			
			if (isAdult() && p.isAdult())
			{
				p.partner = this;
				partner = p;
			}
		}
		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 || hasBaby || !hasAccessory) { return; }
		excitement = true;
		showExcite();
		stay();
	}
	
	public void doSurisuri(Body partner) {
		if (dead || partner.dead) { return; }
		
		showSurisuri();
		angry = false;
		partner.angry = false;
		scare = false;
		partner.scare = false;
		sadness = false;
		partner.sadness = false;
		//partner.stay();
	}
	
	public void doPeropero(Body partner) {
		if (dead || partner.dead) { return; }
		
		showPeroPero();
		partner.dirty = false;
		angry = false;
		partner.angry = false;
		scare = false;
		partner.scare = false;
		sadness = false;
		partner.sadness = false;
	}
	
	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(Food.type foodType, int amount) {
		if (dead) {
			return;
		}
		toFood = false;
		if (!isHungry()) {
			showFull();
			return;
		}
		hungry -= amount;
		if (hungry < 0) {
			hungry = 0;
		}
		if (foodType == Food.type.SHIT) {
			showEatingShit();
		}
		else {
			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 stress() {
		if (dead) {
			return;
		}
		damage += rnd.nextInt(NEEDLE);
		damageState = checkDamageState();
	}
	
	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 == Damage.NONE && hasAccessory) {
			angry = true;
			scare = false;
			excitement = false;
		}
	}
	
	public static void setLanguage(Language lang) { language = lang; }
	
	public static Language getLanguage() { return language; }
	
	// calling every tick
	@Override
	public Obj.Event clockTick()
	{
		// if removed, remove body
		if (removed) { return Obj.Event.REMOVED; }
		
		// if dead, do nothing.
		if (dead) {
			moveBody(true); // for falling the body
			checkMessage();
			return Obj.Event.DEAD;
		}
		
		Event retval = Event.DONOTHING;
		
		// check age
		age += TICK;
		if (age > LIFELIMIT) {
			dead = true;
			moveBody(true); // for falling the body
			checkMessage();
			return Obj.Event.DEAD;
		}
		ageState = checkAgeState();
		
		// check status
		checkHungry();
		checkDamage();
		
		// check events
		boolean dontMove = false;
		
		// check shit
		int oldShit = shit;
		if ( checkShit() ) { dontMove = true; }
		if ( oldShit != 0 && shit == 0 ) { retval = Obj.Event.DOSHIT; }
		
		// check pregnant
		boolean oldHasBaby = hasBaby;
		if ( checkPregnant() ) { dontMove = true; }
		if ( oldHasBaby == true && hasBaby == false ) { retval = Obj.Event.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;
	}
}
