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;

public class SimYukkuri extends JFrame {
	static final long serialVersionUID = 1L;
	static final String TITLE = "しむゆっくり/SimYukkuri";
	static final String VERSION = "Ver 1.11";
	static final Object lock = new Object();
	static boolean initialized = false;
	JLabel title;
	JPanel rootPane	= new JPanel();
	JPanel buttonPane = new JPanel();
	JComboBox s1;
	JLabel l1, l2, l3, l8;
	static JLabel l4, l5, l6, l7;
	JButton saveButton, loadButton, addYukkuriButton, languageButton;
	static myPane mypane  = new myPane();
	static Thread mythread;

	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();
		l1 = new JLabel();
		l2 = new JLabel(" ");
		l3 = new JLabel(" ");
		l4 = new JLabel(" ");
		l5 = new JLabel(" ");
		l6 = new JLabel(" ");
		l7 = 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);

		buttonPane.add(l1);
		buttonPane.add(s1);

		buttonPane.add(l2);
		ButtonListener buttonListener = new ButtonListener();

		addYukkuriButton.addActionListener(buttonListener);
		buttonPane.add(addYukkuriButton);
		saveButton.addActionListener(buttonListener);
		buttonPane.add(saveButton);
		loadButton.addActionListener(buttonListener);
		buttonPane.add(loadButton);

		buttonPane.add(l3);
		languageButton.addActionListener(buttonListener);
		buttonPane.add(languageButton);

		l8 = new JLabel(" ");
		buttonPane.add(l8);
		buttonPane.add(l4);
		buttonPane.add(l5);
		buttonPane.add(l6);
		buttonPane.add(l7);

		// setup my pane
		MyMouseListener ml = new MyMouseListener();
		mypane.addMouseListener(ml);
		mypane.addMouseMotionListener(ml);
		// 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);
	}

	//言語 / Language
	public void setLanguage(Body.Language language) {
		Body.setLanguage(language);
		if(language == Body.Language.ENGLISH) {
			title.setText("SimYukkuri " + VERSION + " ");
			final int selectedIndex = s1.getSelectedIndex();
			s1.setModel(new DefaultComboBoxModel(new String[]{"Needle", "Hammer", "Vibrator", "Juice", "Food", "Clean", "Accessory", "Move", "Pants", "Toilet"}));
			if(selectedIndex > -1)
				s1.setSelectedIndex(selectedIndex);
			l1.setText("Tools");
			addYukkuriButton.setText("Add Yukkuri");
			saveButton.setText("Save");
			loadButton.setText("Load");
			l3.setText("Language");
			languageButton.setText("English");
			l4.setText("Status of Yukkuri");
			l5.setText("　Stress: - ");
			l6.setText("　Damage: - ");
			l7.setText("　Hungry: - ");
		}
		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);
			l1.setText("道具");
			addYukkuriButton.setText("ゆっくりを追加");
			saveButton.setText("セーブ");
			loadButton.setText("ロード");
			l3.setText("言語");
			languageButton.setText("日本語");
			l4.setText("ゆっくりの状態");
			l5.setText("　ストレス: - ");
			l6.setText("　ダメージ: - ");
			l7.setText("　空腹度: - ");
		}
	}

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

	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 {
					Yukkuri.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 {
					Yukkuri.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;
		int startX = -1, startY = -1, startZ = -1;
		int oX = 0, oY = 0, altitude = 0;

		/*
		BufferedImage bi = ImageIO.read(JSpreadUtilities.getUrl(resourcename));
		  Cursor cursor = Toolkit.getDefaultToolkit().createCustomCursor(
					 bi, new Point(x, y), cursorname);
		 */

		public void mouseClicked(MouseEvent e){
			synchronized(lock) {
				Dimension size = mypane.getSize();
				int w = size.width, h = size.height;
				boolean hit = false;
				if (s1.getSelectedIndex() == 5) {//ホウキ
					for (Shit s:Yukkuri.shitList) {
						int offsetX = (ImgFlag.MAXSIZE - s.getSize())/2;
						int offsetY = (ImgFlag.MAXSIZE - s.getSize());
						int dx = e.getX() - (Translate.transX(s.getX(), s.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
						int dy = e.getY() - (Translate.transY(s.getX(), s.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
						if (dx >= 0 && dx <= s.getSize() && dy >= 0 && dy <= s.getSize()) {
							s.remove();
							hit = true;
						}
					}
					for (Food f:Yukkuri.foodList) {
						int offsetX = (ImgFlag.MAXSIZE - f.getSize())/2;
						int offsetY = (ImgFlag.MAXSIZE - f.getSize()/2);
						int dx = e.getX() - (Translate.transX(f.getX(), f.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
						int dy = e.getY() - (Translate.transY(f.getX(), f.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
						if (dx >= 0 && dx <= f.getSize() && dy >= 0 && dy <= f.getSize()/2) {
							f.remove();
							hit = true;
						}
					}
					for (Toilet t:Yukkuri.toiletList) {
						int offsetX = (ImgFlag.MAXSIZE - t.getSize())/2;
						int offsetY = (ImgFlag.MAXSIZE - t.getSize()*2/3);
						int dx = e.getX() - (Translate.transX(t.getX(), t.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
						int dy = e.getY() - (Translate.transY(t.getX(), t.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
						if (dx >= 0 && dx <= t.getSize() && dy >= 0 && dy <= t.getSize()) {
							t.remove();
							hit = true;
						}
					}
				}
				for (Body b:Yukkuri.bodyList) {
					int offsetX = (ImgFlag.MAXSIZE - b.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - b.getSize());
					int dx = e.getX() - (Translate.transX(b.getX(), b.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
					int dy = e.getY() - (Translate.transY(b.getX(), b.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
					if (dx >= 0 && dx <= b.getSize() && dy >= 0 && dy <= b.getSize()) {
						switch (s1.getSelectedIndex()) {
						case 0:
							//針
							b.strikeByNeedle();
							Yukkuri.setAlarm();
							break;
						case 1:
							//ハンマー
							b.strikeByHammer();
							Yukkuri.setAlarm();
							break;
						case 2:
							//バイブ
							b.forceSukkiri();
							break;
						case 3:
							//ジュース
							b.giveJuice();
							break;
						case 4:
							//えさ
							break;
						case 5:
							//ホウキ
							if (b.isDead())
								b.remove();
							else
								b.setDirty(false);
							break;
						case 6:
							//おかざり
							if (b.hasAccessory())
								b.takeAccessory();
							else
								b.giveAccessory();
							break;
						case 7:
							//移動
							break;
						case 8:
							//おくるみ
							if (b.hasPants())
								b.takePants();
							else
								b.givePants();
							break;
						case 9:
							//トイレ
							break;
						default:
							System.out.println("invalid tool");
						}
						hit = true;
					}
				}
				if (!hit) {
					switch (s1.getSelectedIndex()) {
					case 4: {
						//えさ
						int offsetX = (ImgFlag.MAXSIZE - Food.getSizeS())/2;
						int offsetY = (ImgFlag.MAXSIZE - Food.getSizeS()/2);
						int X = (e.getX() - offsetX)*Box.maxX/(w-ImgFlag.MAXSIZE);
						int Y = (e.getY() - offsetY)*Box.maxY/(h-ImgFlag.MAXSIZE);				
						int x = Translate.invX(X, Y, Box.maxX, Box.maxY);
						int y = Translate.invY(X, Y, Box.maxX, Box.maxY);
						if (x >= 0 && x <= Box.maxX && y >= 0 && y <= Box.maxY) {
							mypane.yukkuri.addYukkuriFood(x, y);
						}
					}
					break;
					case 9: {
						//トイレ
						int offsetX = (ImgFlag.MAXSIZE - Toilet.getSizeS())/2;
						int offsetY = (ImgFlag.MAXSIZE - Toilet.getSizeS()*2/3);
						int X = (e.getX() - offsetX)*Box.maxX/(w-ImgFlag.MAXSIZE);
						int Y = (e.getY() - offsetY)*Box.maxY/(h-ImgFlag.MAXSIZE);				
						int x = Translate.invX(X, Y, Box.maxX, Box.maxY);
						int y = Translate.invY(X, Y, Box.maxX, Box.maxY);
						if (x >= 0 && x <= Box.maxX && y >= 0 && y <= Box.maxY) {
							mypane.yukkuri.addToilet(x, y);
						}
					}
					break;
					default:
						break;
					}
				}
			}
		}

		public void mousePressed(MouseEvent e) {
			synchronized(lock) {
				if (s1.getSelectedIndex() != 7) { //移動
					return;
				}
				if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
					startZ = e.getY() + altitude;
					startY = e.getY();
				}
				if (grabbedObj != null) {
					return;
				}
				int maxY = -1;
				Dimension size = mypane.getSize();
				int w = size.width, h = size.height;
				for (Toilet t:Yukkuri.toiletList) {
					int offsetX = (ImgFlag.MAXSIZE - t.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - t.getSize()*2/3);
					int dx = e.getX() - (Translate.transX(t.getX(), t.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
					int dy = e.getY() - (Translate.transY(t.getX(), t.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
					if (dx >= 0 && dx <= t.getSize() && dy >= 0 && dy <= t.getSize()*2/3) {
						if (maxY < t.getY()) {
							grabbedObj = t;
							startX = e.getX();
							startY = e.getY();
							oX = dx;
							oY = dy;
							maxY = t.getY();
						}
					}
				}
				maxY = -1;  // force to put Toilet in the back
				for (Body b:Yukkuri.bodyList) {
					int offsetX = (ImgFlag.MAXSIZE - b.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - b.getSize());
					int dx = e.getX() - (Translate.transX(b.getX(), b.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
					int dy = e.getY() - (Translate.transY(b.getX(), b.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
					if (dx >= 0 && dx <= b.getSize() && dy >= 0 && dy <= b.getSize()) {
						if (maxY < b.getY()) {
							grabbedObj = b;
							startX = e.getX();
							startY = e.getY();
							oX = dx;
							oY = dy;
							maxY = b.getY();
						}
					}
				}
				for (Shit s:Yukkuri.shitList) {
					int offsetX = (ImgFlag.MAXSIZE - s.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - s.getSize());
					int dx = e.getX() - (Translate.transX(s.getX(), s.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
					int dy = e.getY() - (Translate.transY(s.getX(), s.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
					if (dx >= 0 && dx <= s.getSize() && dy >= 0 && dy <= s.getSize()) {
						if (maxY < s.getY()) {
							grabbedObj = s;
							startX = e.getX();
							startY = e.getY();
							oX = dx;
							oY = dy;
							maxY = s.getY();
						}
					}
				}
				for (Food f:Yukkuri.foodList) {
					int offsetX = (ImgFlag.MAXSIZE - f.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - f.getSize()/2);
					int dx = e.getX() - (Translate.transX(f.getX(), f.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX);
					int dy = e.getY() - (Translate.transY(f.getX(), f.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY);
					if (dx >= 0 && dx <= f.getSize() && dy >= 0 && dy <= f.getSize()/2) {
						if (maxY < f.getY()) {
							grabbedObj = f;
							startX = e.getX();
							startY = e.getY();
							oX = dx;
							oY = dy;
							maxY = f.getY();
						}
					}
				}
				if (grabbedObj != null) {
					grabbedObj.grab();
					if (grabbedObj.objType == Obj.Type.YUKKURI) {
						Body b = (Body)grabbedObj;
						int damage = 100 * b.getDamage() / b.getDamageLimit();
						int hungry = 100 * b.getHungry() / b.getHungryLimit();
						if (Body.getLanguage() == Body.Language.ENGLISH) {
							l6.setText("　Damage: " + damage + "%");
							l7.setText("　Hungry: " + hungry + "%");
						}
						else {
							l6.setText("　ダメージ: " + damage + "%");
							l7.setText("　空腹度: " + hungry + "%");
						}
					}
				}
			}
		}

		public void mouseReleased(MouseEvent e) {
			synchronized(lock) {
				if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) {
					startZ = e.getY() + altitude;
					startY = e.getY();
				}
				if ((e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK|MouseEvent.BUTTON3_DOWN_MASK)) != 0) {
					return;
				}
				if (grabbedObj != null) {
					grabbedObj.release();
					grabbedObj = null;
					startX = -1;
					startY = -1;
					startZ = -1;
					oX = 0;
					oY = 0;
					altitude = 0;
				}
			}
		}

		public void mouseDragged(MouseEvent e) {
			synchronized(lock) {
				if (grabbedObj != null) {
					int button = 1;
					if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0) {
						button = 2;
					}
					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 = (ImgFlag.MAXSIZE - b.getSize())/2;
						offsetY = (ImgFlag.MAXSIZE - b.getSize());
						break;
					}
					case SHIT: {
						Shit s = (Shit)grabbedObj;
						offsetX = (ImgFlag.MAXSIZE - s.getSize())/2;
						offsetY = (ImgFlag.MAXSIZE - s.getSize());
						break;
					}
					case FOOD:
						Food f = (Food)grabbedObj;
						offsetX = (ImgFlag.MAXSIZE - f.getSize())/2;
						offsetY = (ImgFlag.MAXSIZE - f.getSize()/2);
						break;
					case TOILET:
						Toilet t = (Toilet)grabbedObj;
						offsetX = (ImgFlag.MAXSIZE - t.getSize())/2;
						offsetY = (ImgFlag.MAXSIZE - t.getSize()*2/3);
						break;
					default:
						break;
					}

					if (button == 2) {
						int X = (e.getX() - oX - offsetX)*Box.maxX/(w-ImgFlag.MAXSIZE);
						int Y = (startY	- oY - offsetY)*Box.maxY/(h-ImgFlag.MAXSIZE);				
						int x = Translate.invX(X, Y, Box.maxX, Box.maxY);
						grabbedObj.setX(x);
						if (startZ - e.getY() > 0) {
							altitude = startZ - e.getY();
							grabbedObj.setZ((startZ - e.getY())*Box.maxZ/h);
						}
					}
					else {
						int X = (e.getX() - oX - offsetX)*Box.maxX/(w-ImgFlag.MAXSIZE);
						int Y = (e.getY() - oY - offsetY + altitude)*Box.maxY/(h-ImgFlag.MAXSIZE);				
						int x = Translate.invX(X, Y, Box.maxX, Box.maxY);
						int y = Translate.invY(X, Y, Box.maxX, Box.maxY);
						grabbedObj.setX(x);
						grabbedObj.setY(y);					
					}
				}
			}
		}

		public void mouseMoved(MouseEvent e) {
			synchronized(lock) {
				if (s1.getSelectedIndex() == 0 || s1.getSelectedIndex() == 1) { // 針 or ハンマー
					if (Yukkuri.getAlarm() == false) {
						return;
					}
					Dimension size = mypane.getSize();
					int w = size.width, h = size.height;
					for (Body b:Yukkuri.bodyList) {
						int offsetX = (ImgFlag.MAXSIZE - b.getSize())/2;
						int offsetY = (ImgFlag.MAXSIZE - b.getSize());
						int X = (e.getX() - offsetX)*Box.maxX/(w-ImgFlag.MAXSIZE);
						int Y = (e.getY() - offsetY)*Box.maxY/(h-ImgFlag.MAXSIZE);
						int x = Translate.invX(X, Y, Box.maxX, Box.maxY);
						int y = Translate.invY(X, Y, Box.maxX, Box.maxY);
						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 = 2L;
	public boolean isRunning = false;
	public Yukkuri yukkuri = new Yukkuri();
	private Image backGroundImage;
	private List <Obj>list4sort = new ArrayList<Obj>();

	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);
			// load body images
			Marisa.loadImages(loader);
			Reimu.loadImages(loader);
			WasaReimu.loadImages(loader);
			MarisaReimu.loadImages(loader);
			ReimuMarisa.loadImages(loader);
			Alice.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) {
			synchronized(SimYukkuri.lock) {
				int stress = 100 * Yukkuri.bodyList.size() / Body.getHeadageLimit();
				if (Body.getLanguage() == Body.Language.ENGLISH) {
					SimYukkuri.l5.setText("　Stress: " + stress + "%");
				}
				else {
					SimYukkuri.l5.setText("　ストレス: " + stress + "%");
				}
			}
			yukkuri.run();
			repaint();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e2) {e2.printStackTrace();}
		}
	}

	void initBodies ()
	{
		final int addYukkuriLimit = 8 - Yukkuri.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;

		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 && Math.random() < (1.0 / 3.0))//1/3 chance that Reimu is WasaReimu
				type = WasaReimu.type;
			bodies.add(Body.fromType(type, cb2.getSelectedIndex()));
			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) {
				yukkuri.addBody(b);
			}
		}
	}

	public void paint(Graphics g) {
		synchronized(SimYukkuri.lock) {
			list4sort.clear();
			list4sort.addAll(Yukkuri.bodyList);
			list4sort.addAll(Yukkuri.foodList);
			list4sort.addAll(Yukkuri.shitList);
			Collections.sort(list4sort, ObjComparator.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: Yukkuri.toiletList) {
				Toilet t = (Toilet)o;
				int offsetX = (ImgFlag.MAXSIZE - t.getSize())/2;
				int offsetY = (ImgFlag.MAXSIZE - t.getSize()*2/3);
				int drX = Translate.transX(t.getX(), t.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX;
				int drY = Translate.transY(t.getX(), t.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY;
				g.drawImage(t.getImage(), drX, drY, this);				
			}
			// 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 = (ImgFlag.MAXSIZE - b.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - b.getSize());
					int drX = Translate.transX(b.getX(), b.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX;
					int drY = Translate.transY(b.getX(), b.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + 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);
					drY -= b.getZ()*h/Box.maxZ; // considering z axis
					int direction = b.getDirection().ordinal();
					// draw body
					if ((!b.hasPants() && b.isShitting()) || b.isBirth()) {
						g.drawImage(b.getImage(ImgFlag.SHIT,ImgFlag.LEFT), drX, drY, this);
						if (b.hasPants())
							g.drawImage(b.getImage(ImgFlag.PANTS2,ImgFlag.LEFT), drX, drY, this);
					}
					else if (b.isFurifuri()) {
						int dir = ImgFlag.LEFT;
						if (b.getAge() % 8 <= 3) {
							dir = ImgFlag.LEFT;
						}
						else if (b.getAge() % 8 <= 7) {
							dir = ImgFlag.RIGHT;
						}
						g.drawImage(b.getImage(ImgFlag.ROLL,dir), drX, drY, this);
						if (b.hasPants()) {
							g.drawImage(b.getImage(ImgFlag.PANTS2_ROLL,dir), drX, drY, this);
						}
					}
					else if (b.isCrashed()) {
						g.drawImage(b.getImage(ImgFlag.CRUSHED,ImgFlag.LEFT), drX, drY, this);
					}
					else {
						// Selecting the face
						Image img;
						if (b.isDead()) {
							img = b.getImage(ImgFlag.DEAD,direction);
						}
						else if (b.isExciting()) {
							img = b.getImage(ImgFlag.EXCITING,direction);
							if (!b.isGrabbed() && b.getZ() == 0) {
								drY -= jump[(int)b.getAge() % 9]/jumpLevel[ageState.ordinal()];
							}
						}
						else if (b.isSleeping()) {
							img = b.getImage(ImgFlag.SLEEPING,direction);
						}
						else if (b.isPeroPero() || b.isEating()) {
							if (b.hasAccessory())
								img = b.getImage(ImgFlag.SMILE, direction);
							else
								img = b.getImage(ImgFlag.TIRED, direction);
						}
						else if (b.isEatingShit()) {
							img = b.getImage(ImgFlag.TIRED,direction);
						}
						else if (b.isSukkiri()) {
							img = b.getImage(ImgFlag.REFRESHED,direction);
						}
						else if (b.isDamaged()) {
							img = b.getImage(ImgFlag.TIRED,direction);
						}
						else {
							if (b.getZ() != 0)
								img = b.getImage(ImgFlag.CHEER,direction);
							else if (b.isStrike() || b.isOld())
								img = b.getImage(ImgFlag.CRYING,direction);
							else if (b.isAngry())
								img = b.getImage(ImgFlag.PUFF,direction);
							else if (b.isSad())
								img = b.getImage(ImgFlag.TIRED,direction);
							else if (b.isVerySad())
								img = b.getImage(ImgFlag.CRYING,direction);
							else if (b.isHappy())
								img = b.getImage(ImgFlag.SMILE, direction);
							else if (b.isTalking() && b.isRude())
								img = b.getImage(ImgFlag.RUDE,direction);
							else if (b.isTalking())
								img = b.getImage(ImgFlag.CHEER,direction);
							else
								img = b.getImage(ImgFlag.NORMAL,direction);
							if (!b.isGrabbed() && b.getZ() == 0)
								drY -= jump[(int)b.getAge() % 9]/2/jumpLevel[ageState.ordinal()];
						}
						// Draw body
						g.drawImage(b.getImage(ImgFlag.BODY,direction), drX, drY, this);
						// Draw accessory
						if (b.hasAccessory()) {
							g.drawImage(b.getImage(ImgFlag.ACCESSORY,direction), drX, drY, this);
						}
						// Draw face
						g.drawImage(img,  drX, drY,  this);
						if (b.isDamaged() || b.isOld()) {
							g.drawImage(b.getImage(ImgFlag.DAMAGED,direction), drX, drY, this);
						}
						// Draw pants and stain
						if (b.hasPants()) {
							g.drawImage(b.getImage(ImgFlag.PANTS,direction), drX, drY, this);
						}
						if (b.isDirty()) {
							g.drawImage(b.getImage(ImgFlag.STAIN,direction), drX, drY, this);
						}
						// Draw braid
						g.drawImage(b.getImage(ImgFlag.BRAID,direction), drX, drY, this);
						// Draw tongue
						if (b.isPeroPero() || b.isEating() || b.isEatingShit()) {
							g.drawImage(b.getImage(ImgFlag.LICK,direction), drX, drY, this);
						}
					}
					// draw script
					String message = b.getMessage();
					if (message != null)
					{
						int width = 160;
						int hight = ((message.length()*yukkuri.fontWidth()/width)+1)*16;
						g.setColor(Color.white);
						g.fillRoundRect(drX+14, drY-hight-4, width+8, hight+16, 8, 8);
						g.setColor(Color.black);
						g.drawRoundRect(drX+14, drY-hight-4, width+8, hight+16, 8, 8);
						drawStringMultiLine(g, message, drX+18, drY-hight, width, hight);
					}
				}
				break;
				case FOOD: {
					Food f = (Food)o;
					int offsetX = (ImgFlag.MAXSIZE - f.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - f.getSize()/2);
					int drX = Translate.transX(f.getX(), f.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX;
					int drY = Translate.transY(f.getX(), f.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY;
					g.drawImage(f.getShadowImage(), drX, drY, this);
					drY -= f.getZ()*h/Box.maxZ; // considering z axis
					g.drawImage(f.getImage(), drX, drY, this);				
				}
				break;
				case SHIT: {
					Shit s = (Shit)o;
					int offsetX = (ImgFlag.MAXSIZE - s.getSize())/2;
					int offsetY = (ImgFlag.MAXSIZE - s.getSize());
					int drX = Translate.transX(s.getX(), s.getY(), Box.maxX, Box.maxY)*(w-ImgFlag.MAXSIZE)/Box.maxX + offsetX;
					int drY = Translate.transY(s.getX(), s.getY(), Box.maxX, Box.maxY)*(h-ImgFlag.MAXSIZE)/Box.maxY + offsetY;
					g.drawImage(s.getShadowImage(), drX, drY, this);
					drY -= s.getZ()*h/Box.maxZ; // considering z axis
					g.drawImage(s.getImage(), drX, drY, this);
				}
				break;
				default:
					break;
				}
			}
		}
	}

	public void drawStringMultiLine(Graphics g, String str, int posX, int posY, int width, int hight) {
		Graphics2D g2d = (Graphics2D)g;
		AttributedString as = new AttributedString(str);
		as.addAttribute(TextAttribute.FONT, g.getFont());
		as.addAttribute(TextAttribute.FOREGROUND, g2d.getColor());
		//as.addAttribute(TextAttribute.BACKGROUND, g2d.getBackground());
		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();
			}
			layout.draw(g2d, drawPosX, drawPosY);
			drawPosY += layout.getDescent() + layout.getLeading();
		}
	}
}

final class ObjComparator implements Comparator<Obj> {
	final static ObjComparator INSTANCE = new ObjComparator();
	//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));
	}
}
