/* 
 *    Copyright 2013 Mimisuke
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;

import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import javax.imageio.*;

import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

public class SimYukkuri extends JFrame {
	static final long serialVersionUID = 1L;
	static final String TITLE = "しむゆっくり/SimYukkuri";
	static final String VERSION = "Ver 1.13";
	static final Object lock = new Object();
	static boolean initialized = false;
	static final int TOOL = 0, FOOD = 1, CLEAN = 2, ACCESSORY = 3, PICKUP = 4, PANTS = 5, TOILET = 6, BARRIER = 7, TOYS = 8;
	static final int PUNISH = 0, HAMMER = 1, VIBRATOR = 2, JUICE = 3, DECOPING = 4;
	static final int NORMAL = 0, BITTER = 1, SELFFEEDER = 2, LEMONPOP = 3, HOT = 4, VIYUGRA = 5;
	static final int INDIVIDUAL = 0, ALL = 1;
	static final int SET = 0, DEL = 1;
	static int selectedTool = 0, selectedFood = 0, selectedCleaner = 0, selectedBarrier = 0, selectedGameSpeed = 1;
	JLabel title;
	JPanel rootPane	= new JPanel();
	JPanel buttonPane = new JPanel();
	JComboBox s1, s2, s3;
	static JLabel l1, l2, l3, l4, l5, l6, l7, l8;
	JButton saveButton, loadButton, addYukkuriButton, languageButton;
	static myPane mypane  = new myPane();
	static Thread mythread;
	static int barrierSX = -1, barrierSY = -1;
	static int barrierEX = -1, barrierEY = -1;

	public SimYukkuri() {	
		super(TITLE);

		buttonPane.setLayout(new GridLayout(20, 1));
		buttonPane.setBorder(new LineBorder(Color.gray, 2));

		title = new JLabel();
		buttonPane.add(title);
		s1 = new JComboBox();
		s2 = new JComboBox();
		s3 = new JComboBox();
		l1 = new JLabel(" ");
		l2 = new JLabel(" ");
		l3 = new JLabel(" ");
		l4 = new JLabel(" ");
		l5 = new JLabel(" ");
		l6 = new JLabel(" ");
		l7 = new JLabel(" ");
		l8 = new JLabel(" ");
		saveButton = new JButton();
		loadButton = new JButton();
		addYukkuriButton = new JButton();
		languageButton = new JButton();
		if (java.util.Locale.getDefault().getLanguage().startsWith("en"))
			setLanguage(Body.Language.ENGLISH);
		else
			setLanguage(Body.Language.JAPANESE);
		MyItemListener mil = new MyItemListener();
		s1.addItemListener(mil);
		s2.addItemListener(mil);
		s3.addItemListener(mil);
		MyKeyListener mkl = new MyKeyListener();
		s1.addKeyListener(mkl);
		s2.addKeyListener(mkl);
		s3.addKeyListener(mkl);
		buttonPane.add(s1);
		buttonPane.add(s2);
		buttonPane.add(s3);
		ButtonListener buttonListener = new ButtonListener();
		addYukkuriButton.addActionListener(buttonListener);
		buttonPane.add(addYukkuriButton);
		saveButton.addActionListener(buttonListener);
		buttonPane.add(saveButton);
		loadButton.addActionListener(buttonListener);
		buttonPane.add(loadButton);
		languageButton.addActionListener(buttonListener);
		buttonPane.add(languageButton);
		buttonPane.add(l2);
		buttonPane.add(l3);
		buttonPane.add(l4);
		buttonPane.add(l5);
		buttonPane.add(l6);
		buttonPane.add(l7);
		buttonPane.add(l8);

		// setup my pane
		MyMouseListener mml = new MyMouseListener();
		mypane.addMouseListener(mml);
		mypane.addMouseMotionListener(mml);

		// setup root pane
		rootPane.setLayout(new BorderLayout());
		rootPane.add("Center", mypane);
		rootPane.add("East", buttonPane);
		// setup my frame
		setSize(1024, 600);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocation(new Point(100, 100));
		setContentPane(rootPane);
		setResizable(false);
		setVisible(true);
	}

	public static void main(String[] args) {
		new SimYukkuri();
		mypane.isRunning = true;
		mythread = new Thread(mypane);
		mythread.start();
	}

	//言語 / Language
	private void setLanguage(Body.Language language) {
		synchronized(lock) {
			Body.setLanguage(language);
		}
		if(language == Body.Language.ENGLISH) {
			title.setText("SimYukkuri " + VERSION + " ");
			final int selectedIndex = s1.getSelectedIndex();
			s1.setModel(new DefaultComboBoxModel(new String[]{"Tool", "Food", "Clean", "Accessory", "Pick up", "Pants", "Toilet", "Barrier", "Toys"}));
			if(selectedIndex > -1)
				s1.setSelectedIndex(selectedIndex);
			s3.setModel(new DefaultComboBoxModel(new String[]{"Pause", "Speed: x1", "Speed: x5", "Speed: x10", "Speed: MAX"}));
			s3.setSelectedIndex(selectedGameSpeed);
			addYukkuriButton.setText("Add Yukkuri");
			saveButton.setText("Save");
			loadButton.setText("Load");
			languageButton.setText("English");
			l2.setText("Status of Yukkuri");
			l3.setText(" Stress: - ");
			l4.setText(" Damage: - ");
			l5.setText(" Hungry: - ");
			l6.setText(" Shit: - ");
			l7.setText(" Rapist: - ");
			l8.setText(" Rude: - ");
		}
		else if(language == Body.Language.JAPANESE) {
			title.setText("しむゆっくり " + VERSION + " ");
			final int selectedIndex = s1.getSelectedIndex();
			s1.setModel(new DefaultComboBoxModel(new String[]{"道具", "えさ", "清掃", "おかざり", "持ち上げる", "おくるみ", "トイレ", "壁", "おもちゃ"}));
			if(selectedIndex > -1)
				s1.setSelectedIndex(selectedIndex);
			s3.setModel(new DefaultComboBoxModel(new String[]{"一時停止", "速度: x1", "速度: x5", "速度: x10", "速度: 最大"}));
			s3.setSelectedIndex(selectedGameSpeed);
			addYukkuriButton.setText("ゆっくりを追加");
			saveButton.setText("セーブ");
			loadButton.setText("ロード");
			languageButton.setText("日本語");
			l2.setText("ゆっくりの状態");
			l3.setText("　ストレス: - ");
			l4.setText("　ダメージ: - ");
			l5.setText("　空腹度: - ");
			l6.setText("　うんうん: - ");
			l7.setText("　レイパー: - ");
			l8.setText("　ゲス: - ");
		}
		setSubMenu(language);
	}
	
	private void setSubMenu(Body.Language language) {
		if (language == Body.Language.ENGLISH) {
			switch (s1.getSelectedIndex()) {
			case TOOL:
				s2.setModel(new DefaultComboBoxModel(new String[]{"Punish", "Hammer", "Vibrator", "Juice", "Snapping"}));
				s2.setSelectedIndex(selectedTool);
				break;
			case FOOD:
				s2.setModel(new DefaultComboBoxModel(new String[]{"Normal", "Bitter", "self-feeder", "Lemon pop", "Hot", "Viyugra"}));
				s2.setSelectedIndex(selectedFood);
				break;
			case CLEAN:
				s2.setModel(new DefaultComboBoxModel(new String[]{"individual", "all"}));
				s2.setSelectedIndex(selectedCleaner);
				break;
			case BARRIER:
				s2.setModel(new DefaultComboBoxModel(new String[]{"set", "delete"}));
				s2.setSelectedIndex(selectedBarrier);
				break;
			default:
				s2.setModel(new DefaultComboBoxModel(new String[]{" "}));
				s2.setSelectedIndex(0);
				break;
			}
		}
		else if (language == Body.Language.JAPANESE) {
			switch (s1.getSelectedIndex()) {
			case TOOL:
				s2.setModel(new DefaultComboBoxModel(new String[]{"おしおき", "ハンマー", "バイブ", "ジュース", "でこぴん"}));
				s2.setSelectedIndex(selectedTool);
				break;
			case FOOD:
				s2.setModel(new DefaultComboBoxModel(new String[]{"ふつう", "苦い", "自動給餌", "ラムネ", "辛い", "バイゆグラ"}));
				s2.setSelectedIndex(selectedFood);
				break;
			case CLEAN:
				s2.setModel(new DefaultComboBoxModel(new String[]{"個別", "全部"}));
				s2.setSelectedIndex(selectedCleaner);
				break;
			case BARRIER:
				s2.setModel(new DefaultComboBoxModel(new String[]{"設置", "撤去"}));
				s2.setSelectedIndex(selectedBarrier);
				break;
			default:
				s2.setModel(new DefaultComboBoxModel(new String[]{" "}));
				break;
			}
		}
	}
	
	private void showStatus(Body b) {
		int damage = 100 * b.getDamage() / b.getDamageLimit();
		int hungry = 100 * b.getHungry() / b.getHungryLimit();
		int shit = 100 * b.getShit() / b.getShitLimit();
		String rapist = (b.isRaper() ? "Yes" : "No");
		String rude = (b.isRude() ? "Yes" : "No");
		if (Body.getLanguage() == Body.Language.ENGLISH) {
			l4.setText(" Damage: " + damage + "%");
			l5.setText(" Hungry: " + hungry + "%");
			l6.setText(" Shit:   " + shit + "%");
			l7.setText(" Rapist: " + rapist);
			l8.setText(" Rude: " + rude);
		}
		else {
			l4.setText("　ダメージ: " + damage + "%");
			l5.setText("　空腹度: " + hungry + "%");
			l6.setText("　うんうん: " + shit + "%");
			l7.setText("　レイパー: " + rapist);
			l8.setText("　ゲス: " + rude);
		}
	}

	public class MyItemListener implements ItemListener {
		public void itemStateChanged(ItemEvent e) {
		    if (e.getStateChange() == ItemEvent.SELECTED){
		    	if (e.getSource() == s1) {
			    	setSubMenu(Body.getLanguage());
		    	}
		    	else if (e.getSource() == s2) {
		    		switch (s1.getSelectedIndex()) {
		    		case TOOL:
		    			selectedTool = s2.getSelectedIndex();
		    			break;
		    		case FOOD:
		    			selectedFood = s2.getSelectedIndex();
		    			break;
		    		case CLEAN:
		    			selectedCleaner = s2.getSelectedIndex();
		    			break;
		    		}
		    	}
		    	else if (e.getSource() == s3) {
		    		selectedGameSpeed = s3.getSelectedIndex();
		    	}
		    }
		}		
	}
	
	public class MyKeyListener implements KeyListener {
		private int savedGameSpeed;
	
		public void keyPressed(KeyEvent e) {
			switch (e.getKeyCode()) {
			case KeyEvent.VK_ESCAPE:
				synchronized(SimYukkuri.lock) {
					if (selectedGameSpeed != 0) {
						savedGameSpeed = selectedGameSpeed;
						selectedGameSpeed = 0;
					}
					else {
						selectedGameSpeed = savedGameSpeed;
					}
					s3.setSelectedIndex(selectedGameSpeed);
				}
				break;
			case KeyEvent.VK_DELETE:
				synchronized(SimYukkuri.lock) {
					Terrarium.cleanAll();
				}
				break;
			default:
				break;
			}
		}

		public void keyReleased(KeyEvent e) {
		}

		public void keyTyped(KeyEvent e) {
		}
	}

	public class ButtonListener implements ActionListener {
		final private JFileChooser fc = new JFileChooser();

		@Override
		public void actionPerformed(ActionEvent e) {
			synchronized(lock) {
				if (!initialized) {
					return;
				}
			}
			Object source = e.getSource();
			if(source.equals(saveButton)) {
				doSave();
			}
			else if(source.equals(loadButton)) {
				doLoad();
			}
			else if(source.equals(languageButton)) {
				setLanguage(Body.getLanguage() == Body.Language.JAPANESE
						? Body.Language.ENGLISH : Body.Language.JAPANESE);
			}
			else if(source.equals(addYukkuriButton)) {
				mypane.initBodies();
			}
		}

		public void doSave() {
			synchronized(lock) {
				int result = fc.showSaveDialog(SimYukkuri.this);
				if(result != JFileChooser.APPROVE_OPTION) return;
				File file = fc.getSelectedFile();
				try {
					Terrarium.saveState(file);
				} catch(IOException e) {
					System.out.println(e);
					JOptionPane.showMessageDialog(SimYukkuri.this, e.getLocalizedMessage(), SimYukkuri.TITLE, JOptionPane.ERROR_MESSAGE);
				}
			}
		}

		public void doLoad() {
			synchronized(lock) {
				int result = fc.showOpenDialog(SimYukkuri.this);
				if(result != JFileChooser.APPROVE_OPTION) return;
				File file = fc.getSelectedFile();
				try {
					Terrarium.loadState(file);
				} catch(IOException e) {
					System.out.println(e);
					JOptionPane.showMessageDialog(SimYukkuri.this, e.getLocalizedMessage(), SimYukkuri.TITLE, JOptionPane.ERROR_MESSAGE);
				} catch(ClassNotFoundException e) {
					System.out.println(e);
					JOptionPane.showMessageDialog(SimYukkuri.this, e.getLocalizedMessage(), SimYukkuri.TITLE, JOptionPane.ERROR_MESSAGE);
				}
			}
		}
	}

	public class MyMouseListener extends MouseAdapter {
		private Cursor cr = new Cursor(Cursor.HAND_CURSOR);
		private Cursor defCr = new Cursor(Cursor.DEFAULT_CURSOR);
		private Obj grabbedObj = null;
		private int startY = -1, startZ = -1;
		private int oX = 0, oY = 0, altitude = 0;
		
		private Obj getUpFront(MouseEvent e) {
			// Sort the objects according as Y position.
			List <Obj>list4sort = new ArrayList<Obj>();
			list4sort.addAll(Terrarium.bodyList);
			list4sort.addAll(Terrarium.shitList);
			list4sort.addAll(Terrarium.foodList);
			list4sort.addAll(Terrarium.toyList);
			Collections.sort(list4sort, ObjComp.INSTANCE);
			// Check whether hit or not.
			Dimension size = mypane.getSize();
			int w = size.width, h = size.height;
			Obj found = null;
			for (Obj o : list4sort) {
				switch (o.objType) {
				case YUKKURI:
				{
					Body b = (Body)o;
					if (b.getZ() != 0) {
						continue;
					}
					int offsetX = (Body.MAXSIZE - b.getSize())/2;
					int offsetY = (Body.MAXSIZE - b.getSize());
					int dx = e.getX() - (Translate.transX(b.getX(), b.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX);
					int dy = e.getY() - (Translate.transY(b.getX(), b.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY);
					if (dx >= 0 && dx <= b.getSize() && dy >= 0 && dy <= b.getSize()) {
						found = b;
						oX = dx;
						oY = dy;
					}
					break;
				}
				case SHIT:
				{
					Shit s = (Shit)o;
					if (s.getZ() != 0) {
						continue;
					}
					int offsetX = (Body.MAXSIZE - s.getSize())/2;
					int offsetY = (Body.MAXSIZE - s.getSize());
					int dx = e.getX() - (Translate.transX(s.getX(), s.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX);
					int dy = e.getY() - (Translate.transY(s.getX(), s.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY);
					if (dx >= 0 && dx <= s.getSize() && dy >= 0 && dy <= s.getSize()) {
						found = s;
						oX = dx;
						oY = dy;
					}
					break;
				}
				case FOOD:
				{
					Food f = (Food)o;
					if (f.getZ() != 0) {
						continue;
					}
					int offsetX = (Body.MAXSIZE - f.getSize())/2;
					int offsetY = (Body.MAXSIZE - f.getSize()/2);
					int dx = e.getX() - (Translate.transX(f.getX(), f.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX);
					int dy = e.getY() - (Translate.transY(f.getX(), f.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY);
					if (dx >= 0 && dx <= f.getSize() && dy >= 0 && dy <= f.getSize()/2) {
						found = f;
						oX = dx;
						oY = dy;
					}
					break;
				}
				case TOY:
				{
					Toy t = (Toy)o;
					if (t.getZ() != 0) {
						continue;
					}
					int offsetX = (Body.MAXSIZE - t.getSize())/2;
					int offsetY = (Body.MAXSIZE - t.getSize());
					int dx = e.getX() - (Translate.transX(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX);
					int dy = e.getY() - (Translate.transY(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY);
					if (dx >= 0 && dx <= t.getSize() && dy >= 0 && dy <= t.getSize()) {
						found = t;
						oX = dx;
						oY = dy;
					}					
					break;
				}
				default:
					break;
				}
			}
			if (found == null) { // Toilet has lowest priority.
				for (Toilet t:Terrarium.toiletList) {
					int offsetX = (Body.MAXSIZE - t.getSize())/2;
					int offsetY = (Body.MAXSIZE - t.getSize()*2/3);
					int dx = e.getX() - (Translate.transX(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX);
					int dy = e.getY() - (Translate.transY(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY);
					if (dx >= 0 && dx <= t.getSize() && dy >= 0 && dy <= t.getSize()*2/3) {
						found = t;
						oX = dx;
						oY = dy;
					}
				}
			}
			return found;
		}

		public void mouseClicked(MouseEvent e){
			if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) {
				return;
			}
			synchronized(lock) {
				if ((s1.getSelectedIndex() == BARRIER) && (s2.getSelectedIndex() == DEL)) {
					Dimension size = mypane.getSize();
					int w = size.width, h = size.height;
					int X = (e.getX() - Body.MAXSIZE/2)*Terrarium.MAX_X/(w-Body.MAXSIZE);
					int Y = (e.getY() - Body.MAXSIZE)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
					int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
					int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
					Barrier found = Terrarium.getBarrier(x, y, 10);
					if (found != null) {
						Terrarium.clearBarrier(found);
					}
					return;
				}
				// If all is selected for clean, clean all shit, empty food and dead body.
				if ((s1.getSelectedIndex() == CLEAN) && (s2.getSelectedIndex() == ALL)) {
					Terrarium.cleanAll();
					return;
				}
				// get up-front object
				Obj found = getUpFront(e);
				// If found, apply action to it.
				if (found != null) {
					switch (s1.getSelectedIndex()) {
					case TOOL:
						switch (s2.getSelectedIndex()) {
						case PUNISH:
							if (found instanceof Body) { 
								((Body)found).strikeByPunish();
								Terrarium.setAlarm();
							}
							break;
						case HAMMER:
							if (found instanceof Body) { 
								Body b = (Body)found;
								b.strikeByHammer();
								if (!b.hasPants() && !b.isDead()) {
									mypane.terrarium.addCrushedShit(b.getX(), b.getY(), b.getZ(), b.getAgeState());
								}
								b.setDirty(true);
								Terrarium.setAlarm();
							}
							break;
						case VIBRATOR:
							if (found instanceof Body) { 
								((Body)found).forceToSukkiri();
							}
							break;
						case JUICE:
							if (found instanceof Body){
								((Body)found).giveJuice();
							}
							break;
						case DECOPING:
							found.kick();
							break;
						default:
							break;
						}
						break;
					case CLEAN:
						if (found instanceof Body) {
							if (((Body)found).isDead())
								found.remove();
							else
								((Body)found).setDirty(false);
						}
						else {
							found.remove();
						}
						break;
					case ACCESSORY:
						if (found instanceof Body) { 
							if (((Body)found).hasAccessory())
								((Body)found).takeAccessory();
							else
								((Body)found).giveAccessory();
						}
						break;
					case PANTS:
						if (found instanceof Body) { 
							if (((Body)found).hasPants())
								((Body)found).takePants();
							else
								((Body)found).givePants();
						}
						break;
					default://Other tool
						break;
					}
					if (found instanceof Body) {
						showStatus((Body)found);
					}
				}
				if (found == null) {
					Dimension size = mypane.getSize();
					int w = size.width, h = size.height;
					switch (s1.getSelectedIndex()) {
					case FOOD: {
						int offsetX = (Body.MAXSIZE - Food.getSizeS())/2;
						int offsetY = (Body.MAXSIZE - Food.getSizeS()/2);
						int X = (e.getX() - offsetX)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - offsetY)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						if (x >= 0 && x <= Terrarium.MAX_X && y >= 0 && y <= Terrarium.MAX_Y) {
							Food.type foodType;
							switch (s2.getSelectedIndex()) {
							case NORMAL:
							default:
								foodType = Food.type.YUKKURIFOOD;
								break;
							case SELFFEEDER:
								foodType = Food.type.SELFFEEDER;
								break;
							case BITTER:
								foodType = Food.type.BITTER;
								break;
							case LEMONPOP:
								foodType = Food.type.LEMONPOP;
								break;
							case HOT:
								foodType = Food.type.HOT;
								break;
							case VIYUGRA:
								foodType = Food.type.VIYUGRA;
								break;
							}
							mypane.terrarium.addFood(x, y, foodType);
						}
					}
					break;
					case TOILET: {
						int offsetX = (Body.MAXSIZE - Toilet.getSizeS())/2;
						int offsetY = (Body.MAXSIZE - Toilet.getSizeS()*2/3);
						int X = (e.getX() - offsetX)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - offsetY)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						if (x >= 0 && x <= Terrarium.MAX_X && y >= 0 && y <= Terrarium.MAX_Y) {
							mypane.terrarium.addToilet(x, y);
						}
					}
					break;
					case TOYS: {
						int offsetX = (Body.MAXSIZE - Toy.getSizeS())/2;
						int offsetY = (Body.MAXSIZE - Toy.getSizeS());
						int X = (e.getX() - offsetX)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - offsetY)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						if (x >= 0 && x <= Terrarium.MAX_X && y >= 0 && y <= Terrarium.MAX_Y) {
							mypane.terrarium.addToy(x, y);
						}						
					}
					break;
					default://Other tool
						break;
					}
				}
			}
		}

		public void mousePressed(MouseEvent e) {
			synchronized(lock) {
				if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
					if ((s1.getSelectedIndex() == BARRIER) && (s2.getSelectedIndex() == SET)) {
						Dimension size = mypane.getSize();
						int w = size.width, h = size.height;
						int X = (e.getX() - Body.MAXSIZE/2)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - Body.MAXSIZE)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						barrierSX = Math.max(0, Math.min(x, Terrarium.MAX_X));
						barrierSY = Math.max(0, Math.min(y, Terrarium.MAX_Y));
						barrierEX = barrierSX;
						barrierEY = barrierSY;
						return;
					}
					if ((s1.getSelectedIndex() != PICKUP)) {
						return;
					}
				}
				if (grabbedObj != null) {
					return;
				}
				// get up-front yukkuri
				grabbedObj = getUpFront(e);
				if (grabbedObj != null) {
					// yukkuri has been grabbed.
					startY = e.getY();
					startZ = e.getY();
					grabbedObj.grab();
					if (grabbedObj instanceof Body) {
						showStatus((Body)grabbedObj);
					}
				}
			}
		}

		public void mouseReleased(MouseEvent e) {
			synchronized(lock) {
				if ((e.getModifiers() & (MouseEvent.BUTTON1_MASK|MouseEvent.BUTTON3_MASK)) == 0) {
					return;
				}
				if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
					if ((s1.getSelectedIndex() == BARRIER) && (s2.getSelectedIndex() == SET)) {
						Dimension size = mypane.getSize();
						int w = size.width, h = size.height;
						int X = (e.getX() - Body.MAXSIZE/2)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - Body.MAXSIZE)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						barrierEX = Math.max(0, Math.min(x, Terrarium.MAX_X));
						barrierEY = Math.max(0, Math.min(y, Terrarium.MAX_Y));
						if ((barrierSX != barrierEX) || (barrierSY != barrierEY)) {
							Terrarium.setBarrier(barrierSX, barrierSY, barrierEX, barrierEY);
						}
						barrierSX = -1;
						barrierSY = -1;
						barrierEX = -1;
						barrierEY = -1;
						return;
					}
				}
				if (grabbedObj != null) {
					grabbedObj.release();
					grabbedObj = null;
					startY = -1;
					startZ = -1;
					altitude = 0;
				}
			}
		}

		public void mouseDragged(MouseEvent e) {
			synchronized(lock) {
				if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
					if ((s1.getSelectedIndex() == BARRIER) && (s2.getSelectedIndex() == SET)) {
						Dimension size = mypane.getSize();
						int w = size.width, h = size.height;
						int X = (e.getX() - Body.MAXSIZE/2)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - Body.MAXSIZE)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						barrierEX = Math.max(0, Math.min(x, Terrarium.MAX_X));
						barrierEY = Math.max(0, Math.min(y, Terrarium.MAX_Y));
						return;
					}
				}
				if (grabbedObj != null) {
					int button = 1;
					if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
						button = 1;
					}
					else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
						button = 2;
					}
					else {
						return;
					}
					Dimension size = mypane.getSize();
					int w = size.width, h = size.height;
					int offsetX = 0;
					int offsetY = 0;

					switch (grabbedObj.objType) {
					case YUKKURI: {
						Body b = (Body)grabbedObj;
						offsetX = (Body.MAXSIZE - b.getSize())/2;
						offsetY = (Body.MAXSIZE - b.getSize());
						break;
					}
					case SHIT: {
						Shit s = (Shit)grabbedObj;
						offsetX = (Body.MAXSIZE - s.getSize())/2;
						offsetY = (Body.MAXSIZE - s.getSize());
						break;
					}
					case FOOD: {
						Food f = (Food)grabbedObj;
						offsetX = (Body.MAXSIZE - f.getSize())/2;
						offsetY = (Body.MAXSIZE - f.getSize()/2);
						break;
					}
					case TOILET: {
						Toilet t = (Toilet)grabbedObj;
						offsetX = (Body.MAXSIZE - t.getSize())/2;
						offsetY = (Body.MAXSIZE - t.getSize()*2/3);
						break;
					}
					case TOY: {
						Toy t = (Toy)grabbedObj;
						offsetX = (Body.MAXSIZE - t.getSize())/2;
						offsetY = (Body.MAXSIZE - t.getSize());
						break;
					}
					default:
						break;
					}

					if ((button == 1) && (s1.getSelectedIndex() == PICKUP)) {
						int X = (e.getX() - oX - offsetX)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (startY	- oY - offsetY)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						grabbedObj.setX(x);
						if (startZ - e.getY() > 0) {
							altitude = startZ - e.getY();
							grabbedObj.setZ((startZ - e.getY())*Terrarium.MAX_Z/h);
						}
					}
					else if (button == 2) {
						int X = (e.getX() - oX - offsetX)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - oY - offsetY + altitude)*Terrarium.MAX_Y/(h-Body.MAXSIZE);				
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						grabbedObj.setX(x);
						grabbedObj.setY(y);					
					}
				}
			}
		}

		public void mouseMoved(MouseEvent e) {
			synchronized(lock) {
				if ((s1.getSelectedIndex() == TOOL) && (s2.getSelectedIndex() == HAMMER)) {
					if (Terrarium.getAlarm() == false) {
						return;
					}
					Dimension size = mypane.getSize();
					int w = size.width, h = size.height;
					for (Body b:Terrarium.bodyList) {
						int offsetX = (Body.MAXSIZE - b.getSize())/2;
						int offsetY = (Body.MAXSIZE - b.getSize());
						int X = (e.getX() - offsetX)*Terrarium.MAX_X/(w-Body.MAXSIZE);
						int Y = (e.getY() - offsetY)*Terrarium.MAX_Y/(h-Body.MAXSIZE);
						int x = Translate.invX(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						int y = Translate.invY(X, Y, Terrarium.MAX_X, Terrarium.MAX_Y);
						if (b.isAdult()) {
							b.setAngry();
							b.lookTo(x, y);
						}
						else {
							b.runAway(x, y);
						}
						if (b.isAngry()) {
							b.showAlarm();
						}
						else if (b.isScare()) {
							b.showScare();
						}
					}
				}
			}
		}

		public void mouseEntered(MouseEvent e) {
			setCursor(cr);
		}

		public void mouseExited(MouseEvent e) {
			setCursor(defCr);
		}
	}
}

class myPane extends JPanel implements Runnable {
	static final long serialVersionUID = 4L;
	public boolean isRunning = false;
	public Terrarium terrarium = new Terrarium();
	private Image backGroundImage;
	static final Random rnd = new Random();
	static final int PAUSE = -1, MAX = 1, DECUPLE = 10, QUINTUPLE = 20, NORMAL = 100;
	static final int gameSpeed[]={PAUSE, NORMAL, QUINTUPLE, DECUPLE, MAX};

	public void run() {
		//load images
		try {
			ClassLoader loader = this.getClass().getClassLoader();
			// load common images
			backGroundImage = ImageIO.read(loader.getResourceAsStream("images/back.jpg"));
			Food.loadImages(loader);
			Shit.loadImages(loader);
			Toilet.loadImages(loader);
			Toy.loadImages(loader);
			// load body images
			Marisa.loadImages(loader);
			Reimu.loadImages(loader);
			WasaReimu.loadImages(loader);
			MarisaReimu.loadImages(loader);
			ReimuMarisa.loadImages(loader);
			Alice.loadImages(loader);
			Tarinai.loadImages(loader);
			Body.loadShadowImages(loader);
		} catch (IOException e1) {
			System.out.println("File I/O error");
		}
		// make initial bodies
		initBodies();
		synchronized(SimYukkuri.lock) {
			SimYukkuri.initialized = true;
		}
		// run animation
		while (isRunning) {
			int stress, speed;
			Body.Language lang;
			synchronized(SimYukkuri.lock) {
				stress = 100 * Terrarium.bodyList.size() / Body.getHeadageLimit();
				speed = gameSpeed[SimYukkuri.selectedGameSpeed];
				lang = Body.getLanguage();
			}
			if (lang == Body.Language.ENGLISH) {
				SimYukkuri.l3.setText(" Stress: " + stress + "%");
			}
			else {
				SimYukkuri.l3.setText("　ストレス: " + stress + "%");
			}
			if(speed != PAUSE) {
				synchronized(SimYukkuri.lock) {
					terrarium.run();
				}
			}
			repaint();
			try {
				if (speed >= 0) {
					Thread.sleep(speed);
				}
				else {
					Thread.sleep(NORMAL);
				}
			} catch (InterruptedException e2) {
				e2.printStackTrace();
			}
		}
	}

	void initBodies ()
	{
		final int addYukkuriLimit = 8 - Terrarium.bodyList.size();
		if(addYukkuriLimit < 1) {
			String msg;
			if(Body.getLanguage() == Body.Language.ENGLISH)
				msg = "You cannot add more yukkuri right now.  If you want more, try breeding some.";
			else
				msg = "追加出来る上限を超えてます。すっきりさせて増やしてください。";
			JOptionPane.showMessageDialog(this, msg, SimYukkuri.TITLE, JOptionPane.INFORMATION_MESSAGE);
			return;
		}

		ArrayList<Body> bodies = new ArrayList<Body>();
		String[] names;
		String[] options;
		String[] ages;
		String mess1, mess2, mess3, mess4;
		final int BABY = 0, CHILD = 1, ADULT = 2;
		switch (Body.getLanguage()) {
		case JAPANESE:
		{
			String[] tempNames = {Marisa.nameJ, Reimu.nameJ, Alice.nameJ};
			names = tempNames;
			String[] tempAges = {"赤ちゃん", "子供", "大人"};
			ages = tempAges;
			String[] tempo = {"はい","いいえ"};
			options = tempo;
			mess1 = "どのゆっくりを追加しますか？";
			mess2 = "もっと追加しますか？";
			mess3 = "（あと";
			mess4 = "匹追加出来ます）";
			break;
		}
		case ENGLISH:
		{
			String[] tempNames = {Marisa.nameE, Reimu.nameE, Alice.nameE};
			names = tempNames;
			String[] tempAges = {"Baby", "Child", "Adult"};
			ages = tempAges;
			String[] tempo = {"Yes","No"};
			options = tempo;
			mess1 = "What kind of yukkuri?";
			mess2 = "More yukkuri?";
			mess3 = "(";
			mess4 = " yukkuris remain)";
			break;
		}
		default:
			throw new LanguageException();
		}
		for (int choice = 0, i = addYukkuriLimit - 1; choice == 0; i--) {
			JPanel panel = new JPanel();
			JPanel panel2 = new JPanel();
			JComboBox cb1 = new JComboBox(names);
			cb1.setSelectedIndex(0);
			panel2.add(cb1);
			JComboBox cb2 = new JComboBox(ages);
			cb2.setSelectedIndex(2);
			panel2.add(cb2);
			JLabel label = new JLabel(mess1);
			panel.add(label);
			panel.add(panel2);
			panel.setLayout(new GridLayout(2,1));
			int ret = JOptionPane.showConfirmDialog(this, panel, SimYukkuri.TITLE, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
			if (ret == 2) {
				break;
			}
			int type = cb1.getSelectedIndex();
			if(type == Reimu.type && rnd.nextBoolean())
				type = WasaReimu.type;
			Body.AgeState age;
			switch (cb2.getSelectedIndex()) {
			case BABY:
				age = Body.AgeState.BABY;
				break;
			case CHILD:
				age = Body.AgeState.CHILD;
				break;
			case ADULT:
			default:
				age = Body.AgeState.ADULT;
				break;
			}
			bodies.add(terrarium.makeBody(rnd.nextInt(Terrarium.MAX_X), rnd.nextInt(Terrarium.MAX_Y), 0, type, age, null, null));
			choice = 1;
			if (i != 0) { // At most 8 yukkuri can be added.
				choice = JOptionPane.showOptionDialog(this, mess2 + System.getProperty("line.separator") + mess3 + i + mess4, SimYukkuri.TITLE, JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
			}
		}
		for (Body b: bodies) {
			synchronized(SimYukkuri.lock) {
				terrarium.addBody(b);
			}
		}
	}

	public void paint(Graphics g) {
		synchronized(SimYukkuri.lock) {
			List <Obj>list4sort = new ArrayList<Obj>();
			list4sort.addAll(Terrarium.bodyList);
			list4sort.addAll(Terrarium.foodList);
			list4sort.addAll(Terrarium.shitList);
			list4sort.addAll(Terrarium.toyList);
			Collections.sort(list4sort, ObjComp.INSTANCE);
			Dimension size = getSize();
			int w = size.width, h = size.height;
			// draw background and toilet
			g.drawImage(backGroundImage,  0, 0, w, h, this);
			for (Obj o: Terrarium.toiletList) {
				Toilet t = (Toilet)o;
				int offsetX = (Body.MAXSIZE - t.getSize())/2;
				int offsetY = (Body.MAXSIZE - t.getSize()*2/3);
				int drX = Translate.transX(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX;
				int drY = Translate.transY(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY;
				g.drawImage(t.getImage(), drX, drY, this);				
			}
			// draw barriers
			for (Barrier b: Terrarium.getBarriers()) {
				int SX = Translate.transX(b.getSX(), b.getSY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + Body.MAXSIZE/2;
				int SY = Translate.transY(b.getSX(), b.getSY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + Body.MAXSIZE;
				int EX = Translate.transX(b.getEX(), b.getEY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + Body.MAXSIZE/2;
				int EY = Translate.transY(b.getEX(), b.getEY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + Body.MAXSIZE;
				Graphics2D g2 = (Graphics2D)g;
				BasicStroke BStroke = new BasicStroke(6.0f);
				Stroke DefStroke = g2.getStroke();
				g2.setStroke(BStroke);
				g2.setColor(Color.GRAY);
				g2.drawLine(SX, SY, EX, EY);
				BStroke = new BasicStroke(1.0f);
				g2.setStroke(DefStroke);
			}
			if ((SimYukkuri.barrierSX >= 0) && (SimYukkuri.barrierSY >= 0) && (SimYukkuri.barrierEX >= 0) && (SimYukkuri.barrierEY >= 0)) {
				int SX = Translate.transX(SimYukkuri.barrierSX, SimYukkuri.barrierSY, Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + Body.MAXSIZE/2;
				int SY = Translate.transY(SimYukkuri.barrierSX, SimYukkuri.barrierSY, Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + Body.MAXSIZE;
				int EX = Translate.transX(SimYukkuri.barrierEX, SimYukkuri.barrierEY, Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + Body.MAXSIZE/2;
				int EY = Translate.transY(SimYukkuri.barrierEX, SimYukkuri.barrierEY, Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + Body.MAXSIZE;
				Graphics2D g2 = (Graphics2D)g;
				BasicStroke BStroke = new BasicStroke(6.0f);
				Stroke DefStroke = g2.getStroke();
				g2.setStroke(BStroke);
				g2.setColor(Color.WHITE);
				g2.drawLine(SX, SY, EX, EY);
				BStroke = new BasicStroke(1.0f);
				g2.setStroke(DefStroke);
			}
			// draw yukkuri, food and shit
			for (Obj o: list4sort) {
				switch (o.objType) {
				case YUKKURI:
				{
					Body b = (Body)o;
					Body.AgeState ageState = b.getAgeState();
					int offsetX = (Body.MAXSIZE - b.getSize())/2;
					int offsetY = (Body.MAXSIZE - b.getSize());
					int drX = Translate.transX(b.getX(), b.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX;
					int drY = Translate.transY(b.getX(), b.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY;
					int jump[] = {0, 8, 12, 14, 15, 14, 12, 8, 0};
					int jumpLevel[] = {2, 2, 1};

					// draw shadow
					g.drawImage(b.getShadowImage(), drX, drY, this);
					// draw body
					drY -= b.getZ()*h/Terrarium.MAX_Z; // considering z axis
					int direction = b.getDirection().ordinal();
					if ((!b.hasPants() && b.isShitting()) || b.isBirth()) {
						g.drawImage(b.getImage(Body.SHIT,Body.LEFT), drX, drY, this);
						if (b.hasPants()) {
							g.drawImage(b.getImage(Body.PANTS2,Body.LEFT), drX, drY, this);
						}
						if (b.hasAccessory()) {
							g.drawImage(b.getImage(Body.ROLL_ACCESSORY,Body.LEFT), drX, drY, this);							
						}
					}
					else if (b.isFurifuri()) {
						int dir = Body.LEFT;
						if (b.getAge() % 8 <= 3) {
							dir = Body.LEFT;
						}
						else if (b.getAge() % 8 <= 7) {
							dir = Body.RIGHT;
						}
						g.drawImage(b.getImage(Body.ROLL_SHIT,dir), drX, drY, this);
						if (b.hasPants()) {
							g.drawImage(b.getImage(Body.PANTS2_ROLL,dir), drX, drY, this);
						}
						if (b.hasAccessory()) {
							g.drawImage(b.getImage(Body.ROLL_ACCESSORY,Body.LEFT), drX, drY, this);							
						}
					}
					else if (b.isCrashed()) {
						if (b.hasAccessory()) {
							g.drawImage(b.getImage(Body.CRUSHED,Body.LEFT), drX, drY, this);
						}
						else {
							g.drawImage(b.getImage(Body.CRUSHED2,Body.LEFT), drX, drY, this);							
						}
					}
					else {
						// Selecting the face
						Image img;
						if (b.isDead()) {
							img = b.getImage(Body.DEAD,direction);
						}
						else if (b.isExciting()) {
							img = b.getImage(Body.EXCITING,direction);
							if (!b.isGrabbed() && b.getZ() == 0) {
								drY -= jump[(int)b.getAge() % 9]/jumpLevel[ageState.ordinal()];
							}
						}
						else if (b.isSleeping()) {
							img = b.getImage(Body.SLEEPING,direction);
						}
						else if (b.isPeroPero() || b.isEating()) {
							if (b.isStrike() || b.isVerySad())
								img = b.getImage(Body.CRYING,direction);
							else if (b.isSad() || b.isEatingShit())
								img = b.getImage(Body.TIRED, direction);
							else
								img = b.getImage(Body.SMILE, direction);
						}
						else if (b.isSukkiri()) {
							img = b.getImage(Body.REFRESHED,direction);
						}
						else if (b.isDamaged() || b.isSick()) {
							if (b.isStrike() || b.isVerySad())
								img = b.getImage(Body.CRYING,direction);
							else
								img = b.getImage(Body.TIRED,direction);
						}
						else {
							if (b.getZ() != 0)
								img = b.getImage(Body.CHEER,direction);
							else if (b.isStrike() || b.isVerySad())
								img = b.getImage(Body.CRYING,direction);
							else if (b.isAngry())
								img = b.getImage(Body.PUFF,direction);
							else if (b.isSad() || b.isOld())
								img = b.getImage(Body.TIRED,direction);
							else if (b.isHappy())
								img = b.getImage(Body.SMILE, direction);
							else if (b.isTalking() && b.isRude())
								img = b.getImage(Body.RUDE,direction);
							else if (b.isTalking() && !b.isRude())
								img = b.getImage(Body.CHEER,direction);
							else
								img = b.getImage(Body.NORMAL,direction);
							if (!b.isGrabbed() && b.getZ() == 0)
								drY -= jump[(int)b.getAge() % 9]/2/jumpLevel[ageState.ordinal()];
						}
						// Draw body
						g.drawImage(b.getImage(Body.BODY,direction), drX, drY, this);
						// Draw accessory
						if (b.hasAccessory()) {
							g.drawImage(b.getImage(Body.ACCESSORY,direction), drX, drY, this);
						}
						// Draw face
						g.drawImage(img,  drX, drY,  this);
						if (b.isDamaged() || b.isOld()) {
							g.drawImage(b.getImage(Body.DAMAGED,direction), drX, drY, this);
						}
						// Draw pants and stain
						if (b.hasPants()) {
							g.drawImage(b.getImage(Body.PANTS,direction), drX, drY, this);
						}
						if (b.isDirty()) {
							g.drawImage(b.getImage(Body.STAIN,direction), drX, drY, this);
						}
						if (b.isSick()) {
							g.drawImage(b.getImage(Body.SICK,direction), drX, drY, this);
						}
						// Draw braid
						g.drawImage(b.getImage(Body.BRAID,direction), drX, drY, this);
						// Draw tongue
						if (b.isPeroPero() || b.isEating() || b.isEatingShit()) {
							if (b.getMessage() != null) {
								g.drawImage(b.getImage(Body.LICK,direction), drX, drY, this);
							}
						}
					}
					// draw script
					String message = b.getMessage();
					if (message != null)
					{	
						final int FONTWIDTH=12;
						final int NUMOFCHAR=12;
						g.setFont(new Font(Font.MONOSPACED, Font.PLAIN, FONTWIDTH));
						int width = Math.min(message.length(), NUMOFCHAR)*FONTWIDTH;
						int hight = drawStringMultiLine(g, message, 0, 0, width, false);
						g.setColor(new Color(255, 255, 255, 200)); // transparent white.
						g.fillRoundRect(drX+14, drY-hight-4, width+8, hight+8, 8, 8);
						g.setColor(new Color(0, 0, 0, 255)); // no transparent black.
						g.drawRoundRect(drX+14, drY-hight-4, width+8, hight+8, 8, 8);
						drawStringMultiLine(g, message, drX+18, drY-hight, width, true);
					}
				}
				break;
				case FOOD: {
					Food f = (Food)o;
					int offsetX = (Body.MAXSIZE - f.getSize())/2;
					int offsetY = (Body.MAXSIZE - f.getSize()/2);
					int drX = Translate.transX(f.getX(), f.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX;
					int drY = Translate.transY(f.getX(), f.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY;
					g.drawImage(f.getShadowImage(), drX, drY, this);
					drY -= f.getZ()*h/Terrarium.MAX_Z; // considering z axis
					g.drawImage(f.getImage(), drX, drY, this);				
				}
				break;
				case SHIT: {
					Shit s = (Shit)o;
					int offsetX = (Body.MAXSIZE - s.getSize())/2;
					int offsetY = (Body.MAXSIZE - s.getSize());
					int drX = Translate.transX(s.getX(), s.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX;
					int drY = Translate.transY(s.getX(), s.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY;
					g.drawImage(s.getShadowImage(), drX, drY, this);
					drY -= s.getZ()*h/Terrarium.MAX_Z; // considering z axis
					g.drawImage(s.getImage(), drX, drY, this);
				}
				break;
				case TOY: {
					Toy t = (Toy)o;
					int offsetX = (Body.MAXSIZE - t.getSize())/2;
					int offsetY = (Body.MAXSIZE - t.getSize());
					int drX = Translate.transX(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(w-Body.MAXSIZE)/Terrarium.MAX_X + offsetX;
					int drY = Translate.transY(t.getX(), t.getY(), Terrarium.MAX_X, Terrarium.MAX_Y)*(h-Body.MAXSIZE)/Terrarium.MAX_Y + offsetY;
					g.drawImage(t.getShadowImage(), drX, drY, this);
					drY -= t.getZ()*h/Terrarium.MAX_Z; // considering z axis
					g.drawImage(t.getImage(), drX, drY, this);					
				}
				break;
				default:
					break;
				}
			}
		}
	}

	private int drawStringMultiLine(Graphics g, String str, int posX, int posY, int width, boolean flag) {
		Graphics2D g2d = (Graphics2D)g;
		AttributedString as = new AttributedString(str);
		as.addAttribute(TextAttribute.FONT, g2d.getFont());
		AttributedCharacterIterator asiterator = as.getIterator();
		FontRenderContext context = g2d.getFontRenderContext();
		LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(as.getIterator(), context);
		float formatWidth = (float)width;
		float drawPosX = 0;
		float drawPosY = posY;
		int beginIndex = asiterator.getBeginIndex();
		int endIndex   = asiterator.getEndIndex();
		lineMeasurer.setPosition(beginIndex);
		while (lineMeasurer.getPosition() < endIndex) {
			TextLayout layout = lineMeasurer.nextLayout(formatWidth);
			drawPosY += layout.getAscent();
			if (layout.isLeftToRight()) {
				drawPosX = posX;
			}
			else {
				drawPosX = posX + formatWidth - layout.getAdvance();
			}
			if (flag) {
				layout.draw(g2d, drawPosX, drawPosY);
			}
			drawPosY += layout.getDescent() + layout.getLeading();
		}
		return (int)Math.ceil(drawPosY);
	}
}

final class ObjComp implements Comparator<Obj> {
	final static ObjComp INSTANCE = new ObjComp();
	//Painter's Algorithm / 画家のアルゴリズム
	@Override final public int compare(Obj o1, Obj o2) {
		int c = o1.y - o2.y;
		if(c == 0) {
			//Improve visibility: at the same y-coordinate, draw small
			//objects after large ones.
			c = (o2 instanceof Body ? ((Body)o2).getAgeState().ordinal() : 1) -
					(o1 instanceof Body ? ((Body)o1).getAgeState().ordinal() : 1);
		}
		return c;
	}
}

class Translate {
	static final private double m = 1.0 / 8.0;
	static final private double n = 7.0 / 8.0;

	static public int transX(int x, int y, int X, int Y) {
		return (int)(((n-m)*Y*x - m*X*y + m*X*Y)/((n-m-1)*y + Y));
	}

	static public int transY(int x, int y, int X, int Y) {
		return (int)(((n-m)*Y*y)/((n-m-1)*y + Y));
	}

	static public int invX(int x, int y, int X, int Y) {
		return (int)((Y*x + m*X*y - m*X*Y)/((n-m)*Y - (n-m-1)*y));
	}

	static public int invY(int x, int y, int X, int Y) {
		return (int)(Y*y/((n-m)*Y - (n-m-1)*y));
	}
}
