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

public abstract class Body extends Obj implements java.io.Serializable {
   static final long serialVersionUID = 3L;
   
   /**
    * 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 abstract String getNameJ ();
   public abstract String getNameE ();
   public abstract Body clone ();
   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 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;   
   protected boolean eatingShit = 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;
         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;
      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[] 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][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 = 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() {
      if (dead) {
         return false;
      }
      if (hungry >= HUNGRYLIMIT[ageState.ordinal()]/8) {
         return true;
      }
      return false;
   }
   
   public boolean isTooHungry() {
      if (dead) {
         return false;
      }
      if (hungry >= HUNGRYLIMIT[ageState.ordinal()] && checkDamageState() == DAMAGEMUCH) {
         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 isEatingShit() {
      if (dead) {
         return false;
      }
      return eatingShit;
   }
   
   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;
   }
   
   /// 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;
      }
      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();
         }
         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 == DAMAGENOT && 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;
   }
}
