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";

    JPanel		rootPane	= new JPanel();
    JPanel		buttonPane	= new JPanel();
    JComboBox	s1;
    JLabel		l1, l2;
    JButton		saveButton, loadButton;
    static myPane	mypane  = new myPane();
    static Thread	mythread;

    public SimYukkuri() {	
	super(TITLE);
	String[] choices = {"日本語","English"};
	String picked = (String) JOptionPane.showInputDialog(this, "言語/Language", TITLE, JOptionPane.QUESTION_MESSAGE, null, choices, choices[0]);

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

	if ( picked == choices[1] ) {
	    JLabel title = new JLabel("SimYukkuri " + VERSION + " ");
	    buttonPane.add(title);

	    String[] list1 = {"Needle", "Hammer", "Vibrator", "Juice", "Food", "Sweep", "Accessory", "Move"};
	    s1 = new JComboBox(list1);

	    l1 = new JLabel("Tools");
	    l2 = new JLabel(" ");
	    saveButton = new JButton("Save");
	    loadButton = new JButton("Load");

	    Body.setLanguage(Body.Language.ENGLISH);
	}
	else {// default to Japanese
	    JLabel title = new JLabel("しむゆっくり " + VERSION + " ");
	    buttonPane.add(title);

	    String[] list1 = {"針", "ハンマー", "バイブ", "ジュース", "えさ", "ホウキ", "おかざり", "移動"};
	    s1 = new JComboBox(list1);

	    l1 = new JLabel("道具");
	    l2 = new JLabel(" ");
	    saveButton = new JButton("セーブ");
	    loadButton = new JButton("ロード");

	    Body.setLanguage(Body.Language.JAPANESE);
	}

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

	buttonPane.add(l2);
	SaveOrLoadListener saveOrLoadListener = new SaveOrLoadListener();

	saveButton.addActionListener(saveOrLoadListener);
	buttonPane.add(saveButton);
	loadButton.addActionListener(saveOrLoadListener);
	buttonPane.add(loadButton);
	// 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);
    }

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

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

	@Override
	public void actionPerformed(ActionEvent e) {
	    Object source = e.getSource();
	    if(source.equals(saveButton)) {
		doSave();
	    }
	    else if(source.equals(loadButton)) {
		doLoad();
	    }
	}

	public void doSave() {
	    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);
	    }
	}

	public void doLoad() {
	    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);
	    } catch(ClassNotFoundException e) {
		System.out.println(e);
	    }
	}
    }

    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);
	 */

	synchronized public void mouseClicked(MouseEvent e){
	    Dimension size = mypane.getSize();
	    int w = size.width, h = size.height;
	    boolean hit = false;
	    if (s1.getSelectedIndex() == 5) {//ホウキ
		for (Shit s:Yukkuri.shitList) {
		    Body.AgeState ageState = s.getAgeState();
		    int dia[] = {10, 20, 30};
		    int offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		    int offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		    int dx = e.getX() - (Translate.transX(s.getX(), s.getY(), Box.maxX, Box.maxY)*(w-myPane.maxSize)/Box.maxX + offsetX);
		    int dy = e.getY() - (Translate.transY(s.getX(), s.getY(), Box.maxX, Box.maxY)*(h-myPane.maxSize)/Box.maxY + offsetY);
		    if (dx >= 0 && dx <= dia[ageState.ordinal()] && dy >= 0 && dy <= dia[ageState.ordinal()]) {
			s.remove();
			hit = true;
		    }
		}
		for (Food f:Yukkuri.foodList) {
		    int offsetX = (myPane.maxSize - myPane.foodSize)/2;
		    int offsetY = (myPane.maxSize - myPane.foodSize/2);
		    int dx = e.getX() - (Translate.transX(f.getX(), f.getY(), Box.maxX, Box.maxY)*(w-myPane.maxSize)/Box.maxX + offsetX);
		    int dy = e.getY() - (Translate.transY(f.getX(), f.getY(), Box.maxX, Box.maxY)*(h-myPane.maxSize)/Box.maxY + offsetY);
		    if (dx >= 0 && dx <= myPane.foodSize && dy >= 0 && dy <= myPane.foodSize/2) {
			f.remove();
			hit = true;
		    }
		}
	    }
	    for (Body b:Yukkuri.bodyList) {
		Body.AgeState ageState = b.getAgeState();
		int dia[] = {myPane.maxSize/4, myPane.maxSize/2, myPane.maxSize};
		int offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		int offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		int dx = e.getX() - (Translate.transX(b.getX(), b.getY(), Box.maxX, Box.maxY)*(w-myPane.maxSize)/Box.maxX + offsetX);
		int dy = e.getY() - (Translate.transY(b.getX(), b.getY(), Box.maxX, Box.maxY)*(h-myPane.maxSize)/Box.maxY + offsetY);
		if (dx >= 0 && dx <= dia[ageState.ordinal()] && dy >= 0 && dy <= dia[ageState.ordinal()]) {
		    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(); }
			break;
		    case 6:
			//おかざり
			if (b.isAccessory()) { b.takeAccessory(); }
			else { b.giveAccessory(); }
			break;
		    case 7:
			//移動
			break;
		    default:
			System.out.println("invalid tool");
		    }
		    hit = true;
		}
	    }
	    if (!hit && s1.getSelectedIndex() == 4) { //えさ
		int offsetX = (myPane.maxSize - myPane.foodSize)/2;
		int offsetY = (myPane.maxSize - myPane.foodSize/2);
		int X = (e.getX() - offsetX)*Box.maxX/(w-myPane.maxSize);
		int Y = (e.getY() - offsetY)*Box.maxY/(h-myPane.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);
		}
	    }
	}

	synchronized public void mousePressed(MouseEvent e) {
	    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 (Body b:Yukkuri.bodyList) {
		Body.AgeState ageState = b.getAgeState();
		int dia[] = {myPane.maxSize/4, myPane.maxSize/2, myPane.maxSize};
		int offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		int offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		int dx = e.getX() - (Translate.transX(b.getX(), b.getY(), Box.maxX, Box.maxY)*(w-myPane.maxSize)/Box.maxX + offsetX);
		int dy = e.getY() - (Translate.transY(b.getX(), b.getY(), Box.maxX, Box.maxY)*(h-myPane.maxSize)/Box.maxY + offsetY);
		if (dx >= 0 && dx <= dia[ageState.ordinal()] && dy >= 0 && dy <= dia[ageState.ordinal()]) {
		    if (maxY < b.getY()) {
			grabbedObj = b;
			startX = e.getX();
			startY = e.getY();
			oX = dx;
			oY = dy;
			maxY = b.getY();
		    }
		}
	    }
	    for (Shit s:Yukkuri.shitList) {
		Body.AgeState ageState = s.getAgeState();
		int dia[] = {10, 20, 30};
		int offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		int offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		int dx = e.getX() - (Translate.transX(s.getX(), s.getY(), Box.maxX, Box.maxY)*(w-myPane.maxSize)/Box.maxX + offsetX);
		int dy = e.getY() - (Translate.transY(s.getX(), s.getY(), Box.maxX, Box.maxY)*(h-myPane.maxSize)/Box.maxY + offsetY);
		if (dx >= 0 && dx <= dia[ageState.ordinal()] && dy >= 0 && dy <= dia[ageState.ordinal()]) {
		    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 = (myPane.maxSize - myPane.foodSize)/2;
		int offsetY = (myPane.maxSize - myPane.foodSize/2);
		int dx = e.getX() - (Translate.transX(f.getX(), f.getY(), Box.maxX, Box.maxY)*(w-myPane.maxSize)/Box.maxX + offsetX);
		int dy = e.getY() - (Translate.transY(f.getX(), f.getY(), Box.maxX, Box.maxY)*(h-myPane.maxSize)/Box.maxY + offsetY);
		if (dx >= 0 && dx <= myPane.foodSize && dy >= 0 && dy <= myPane.foodSize/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();
	    }
	}

	synchronized public void mouseReleased(MouseEvent e) {
	    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;
	    }
	}

	synchronized public void mouseDragged(MouseEvent e) {
	    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.AgeState ageState = ((Body)grabbedObj).getAgeState();
		    int dia[] = {myPane.maxSize/4, myPane.maxSize/2, myPane.maxSize};
		    offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		    offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		    break;
		}
		case SHIT: {
		    Body.AgeState ageState = ((Shit)grabbedObj).getAgeState();
		    int dia[] = {10, 20, 30};
		    offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		    offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		    break;
		}
		case FOOD:
		    offsetX = (myPane.maxSize - myPane.foodSize)/2;
		    offsetY = (myPane.maxSize - myPane.foodSize/2);
		    break;
		default:
		    break;
		}

		if (button == 2) {
		    int X = (e.getX() - oX - offsetX)*Box.maxX/(w-myPane.maxSize);
		    int Y = (startY	- oY - offsetY)*Box.maxY/(h-myPane.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-myPane.maxSize);
		    int Y = (e.getY() - oY - offsetY + altitude)*Box.maxY/(h-myPane.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);					
		}
	    }
	}

	synchronized public void mouseMoved(MouseEvent e) {
	    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) {
		    Body.AgeState ageState = b.getAgeState();
		    int dia[] = {myPane.maxSize/4, myPane.maxSize/2, myPane.maxSize};
		    int offsetX = (myPane.maxSize - dia[ageState.ordinal()])/2;
		    int offsetY = (myPane.maxSize - dia[ageState.ordinal()]);
		    int X = (e.getX() - offsetX)*Box.maxX/(w-myPane.maxSize);
		    int Y = (e.getY() - offsetY)*Box.maxY/(h-myPane.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 = 1L;

    public boolean isRunning = false;
    public Yukkuri yukkuri = new Yukkuri();
    public static final int maxSize = 128;	// max size of icons
    public static final int foodSize = 64;	// size of food
    private Image[] foodImage = new Image[2];
    private Image[] shitImage = new Image[2];
    private Image shadowImage, foodShadow, shitShadow;
    private Image backGroundImage;
    private List <Obj>list4sort = new ArrayList<Obj>();

    public void run() {
	//load images
	try {
	    ClassLoader loader = this.getClass().getClassLoader();
	    // load common images
	    foodImage[0] = ImageIO.read(loader.getResourceAsStream("images/gohan1.png"));
	    foodImage[1] = ImageIO.read(loader.getResourceAsStream("images/gohan2.png"));
	    shitImage[0] = ImageIO.read(loader.getResourceAsStream("images/unun.png"));
	    shitImage[1] = ImageIO.read(loader.getResourceAsStream("images/unun2.png"));
	    shadowImage = ImageIO.read(loader.getResourceAsStream("images/shadow.png"));
	    foodShadow = ImageIO.read(loader.getResourceAsStream("images/gohan-shadow.png"));
	    shitShadow = ImageIO.read(loader.getResourceAsStream("images/unun-shadow.png"));
	    backGroundImage = ImageIO.read(loader.getResourceAsStream("images/back.jpg"));
	    // load body images
	    Marisa.loadImages(loader);
	    Reimu.loadImages(loader);
	    WasaReimu.loadImages(loader);
	    MarisaReimu.loadImages(loader);
	    ReimuMarisa.loadImages(loader);
	    Alice.loadImages(loader);
	} catch (IOException e1) {System.out.println("File I/O error");}
	// make initial bodies
	initBodies();
	// run animation
	while (isRunning) {
	    yukkuri.run();
	    repaint();
	    try {
		Thread.sleep(100);
	    } catch (InterruptedException e2) {e2.printStackTrace();}
	}
    }

    void initBodies ()
    {
	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 = 7; 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;
	    }
	    bodies.add(Body.fromType(cb1.getSelectedIndex(), 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 ) { yukkuri.addBody(b); }
    }

    synchronized public void paint(Graphics g) {
	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;
	g.drawImage(backGroundImage,  0, 0, w, h, this);
	for (Obj o : list4sort) {
	    switch (o.objType) {
	    case YUKKURI:
	    {
		Body b = (Body)o;
		Body.AgeState ageState = b.getAgeState();
		int dia[] = {maxSize/4, maxSize/2, maxSize};
		int offsetX = (maxSize - dia[ageState.ordinal()])/2;
		int offsetY = (maxSize - dia[ageState.ordinal()]);
		int drX = Translate.transX(b.getX(), b.getY(), Box.maxX, Box.maxY)*(w-maxSize)/Box.maxX + offsetX;
		int drY = Translate.transY(b.getX(), b.getY(), Box.maxX, Box.maxY)*(h-maxSize)/Box.maxY + offsetY;
		int jump[] = {0, 8, 12, 14, 15, 14, 12, 8, 0};
		int jumpLevel[] = {2, 2, 1};

		// draw shadow
		g.drawImage(shadowImage, drX, drY, dia[ageState.ordinal()], dia[ageState.ordinal()], this);
		drY -= b.getZ()*h/Box.maxZ; // considering z axis
		int direction = b.getDirection().ordinal();

		// draw body
		if (b.isShitting() || b.isBirth()) {
		    g.drawImage(b.getImage(ImgFlag.SHIT,ImgFlag.LEFT,ageState), 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,ageState), drX, drY, this);
		}
		else if (b.isCrashed()) {
		    g.drawImage(b.getImage(ImgFlag.CRUSHED,ImgFlag.LEFT,ageState), drX, drY, this);
		}
		else {
		    // Selecting the face
		    Image img;
		    if (b.isDead()) {
			img = b.getImage(ImgFlag.DEAD,direction,ageState);
		    }
		    else if (b.isExciting()) {
			img = b.getImage(ImgFlag.EXCITING,direction,ageState);
			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,ageState); }
		    else if (b.isPeroPero() || b.isEating()) {
			if (b.isAccessory()) { img = b.getImage(ImgFlag.SMILE,direction,ageState); }
			else { img = b.getImage(ImgFlag.TIRED,  direction,  ageState); }
		    }
		    else if (b.isEatingShit()) { img = b.getImage(ImgFlag.TIRED,direction,ageState); }
		    else if (b.isSukkiri()) { img = b.getImage(ImgFlag.REFRESHED,direction,ageState); }
		    else if (b.isDamaged()) { img = b.getImage(ImgFlag.TIRED,direction,ageState); }
		    else {
			if (b.isStrike() || !b.isAccessory() || b.isScare() || b.isSadness()) {
			    img = b.getImage(ImgFlag.CRYING,direction,ageState);
			}
			else if (b.isAngry()) {
			    img = b.getImage(ImgFlag.PUFF,direction,ageState);
			}
			else if (b.getZ() != 0){
			    img = b.getImage(ImgFlag.CHEER,direction,ageState);
			}
			else if (b.isRude()) {
			    img = b.getImage(ImgFlag.RUDE,direction,ageState);
			}
			else if (b.isHappy()) {
			    img = b.getImage(ImgFlag.SMILE, direction, ageState);
			}
			else {
			    img = b.getImage(ImgFlag.NORMAL,direction,ageState);
			}
			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,ageState), drX, drY, this);
		    
		    // Draw accessory
		    if (b.isAccessory())
		    {
			g.drawImage(b.getImage(ImgFlag.ACCESSORY,direction,ageState), drX, drY, this);
		    }
		    g.drawImage(b.getImage(ImgFlag.BRAID,direction,ageState), drX, drY, this);

		    // Draw face
		    g.drawImage(img,  drX, drY,  this);
		    if (b.isDamaged()) {
			g.drawImage(b.getImage(ImgFlag.DAMAGED,direction,ageState), drX, drY, this);
		    }
		    
		    // Draw tongue
		    if (b.isPeroPero() || b.isEating() || b.isEatingShit()) {
			g.drawImage(b.getImage(ImgFlag.LICK,direction,ageState), 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 = (maxSize - foodSize)/2;
		int offsetY = (maxSize - foodSize/2);
		int drX = Translate.transX(f.getX(), f.getY(), Box.maxX, Box.maxY)*(w-maxSize)/Box.maxX + offsetX;
		int drY = Translate.transY(f.getX(), f.getY(), Box.maxX, Box.maxY)*(h-maxSize)/Box.maxY + offsetY;
		g.drawImage(foodShadow, drX, drY, foodSize, foodSize/2, this);
		drY -= f.getZ()*h/Box.maxZ; // considering z axis
		if (f.isEmpty()) {
		    g.drawImage(foodImage[0], drX, drY, foodSize, foodSize/2, this);				
		}
		else {
		    g.drawImage(foodImage[1], drX, drY, foodSize, foodSize/2, this);
		}
	    }
	    break;
	    case SHIT: {
		Shit s = (Shit)o;
		Body.AgeState ageState = s.getAgeState();
		int dia[] = {10, 20, 30};
		int offsetX = (maxSize - dia[ageState.ordinal()])/2;
		int offsetY = (maxSize - dia[ageState.ordinal()]);
		int drX = Translate.transX(s.getX(), s.getY(), Box.maxX, Box.maxY)*(w-maxSize)/Box.maxX + offsetX;
		int drY = Translate.transY(s.getX(), s.getY(), Box.maxX, Box.maxY)*(h-maxSize)/Box.maxY + offsetY;
		g.drawImage(shitShadow, drX, drY, dia[ageState.ordinal()], dia[ageState.ordinal()], this);
		drY -= s.getZ()*h/Box.maxZ; // considering z axis
		g.drawImage(shitImage[s.getShitState()], drX, drY, dia[ageState.ordinal()], dia[ageState.ordinal()], 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));
    }
}
