package com.group_finity.mascot;

import java.awt.MenuItem;
import java.awt.Point;
import java.awt.PopupMenu;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

import com.group_finity.mascot.action.Action;
import com.group_finity.mascot.behavior.Behavior;
import com.group_finity.mascot.environment.MascotEnvironment;
import com.group_finity.mascot.exception.CantBeAliveException;
import com.group_finity.mascot.image.MascotImage;
import com.group_finity.mascot.image.TranslucentWindow;

/**
 * マスコットオブジェクト.
 *
 * マスコットは長期的で複雑な振る舞いをあらわす {@link Behavior} と、
 * 短期的で単調な動きを表す {@link Action} で動く.
 *
 * マスコットは内部的にタイマを持っていて、一定間隔ごとに {@link Action} を呼び出す.
 * {@link Action} は {@link #animate(Point, MascotImage, boolean)} メソッドなどを呼ぶことで
 * マスコットをアニメーションさせる.
 *
 * {@link Action} が終了したり、その他の特定のタイミングで {@link Behavior} が呼び出され、次の {@link Action} に移る.
 *
 */
public class Mascot {

	private static final long serialVersionUID = 1L;

	private static final Logger log = Logger.getLogger(Mascot.class.getName());

	private static AtomicInteger lastId = new AtomicInteger();

	private static boolean showSystemTrayMenu = false;

	public static void setShowSystemTrayMenu(boolean showSystemTrayMenu) {
		Mascot.showSystemTrayMenu = showSystemTrayMenu;
	}

	public static boolean isShowSystemTrayMenu() {
		return showSystemTrayMenu;
	}

	private final int id;

	/**
	 * マスコットを表示するウィンドウ.
	 */
	private final TranslucentWindow window = NativeFactory.getInstance().newTransparentWindow();

	/**
	 * マスコットを管理しているマネージャ.
	 */
	private Manager manager = null;

	/**
	 * マスコットの接地座標.
	 * たとえば足元や、ぶら下がっているときの手の部分など.
	 * ここが画像を表示するときの中心になる.
	 */
	private Point anchor = new Point(0, 0);

	/**
	 * 表示する画像.
	 */
	private MascotImage image = null;

	/**
	 * 右向きかどうか.
	 * オリジナル画像は左向きとして扱われるので、trueを設定すると反転して描画される.
	 */
	private boolean lookRight = false;

	/**
	 * 長期的な振る舞いをあらわすオブジェクト.
	 */
	private Behavior behavior = null;

	/**
	 * タイマーの1チックごとに増加する時刻.
	 */
	private int time = 0;

	/**
	 * アニメーション実行中かどうか.
	 */
	private boolean animating = true;

	private MascotEnvironment environment = new MascotEnvironment(this);

	public Mascot() {
		this.id = lastId.incrementAndGet();

		log.log(Level.INFO, "マスコット生成({0})", this);

		// 常に最善面に表示
		getWindow().asJWindow().setAlwaysOnTop(true);

		// マウスハンドラを登録
		getWindow().asJWindow().addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(final MouseEvent e) {
				Mascot.this.mousePressed(e);
			}

			@Override
			public void mouseReleased(final MouseEvent e) {
				Mascot.this.mouseReleased(e);
			}
		});

	}

	@Override
	public String toString() {
		return "マスコット" + this.id;
	}

	private void mousePressed(final MouseEvent event) {

		// マウスが押されたらドラッグアニメーションに切り替える
		if (getBehavior() != null) {
			try {
				getBehavior().mousePressed(event);
			} catch (final CantBeAliveException e) {
				log.log(Level.SEVERE, "行き続けることが出来ない状況", e);
				dispose();
			}
		}

	}

	private void mouseReleased(final MouseEvent event) {

		if (event.isPopupTrigger()) {
			SwingUtilities.invokeLater(new Runnable(){
				@Override
				public void run() {
					showPopup(event.getX(), event.getY());
				}
			});
		} else {
			if (getBehavior() != null) {
				try {
					getBehavior().mouseReleased(event);
				} catch (final CantBeAliveException e) {
					log.log(Level.SEVERE, "行き続けることが出来ない状況", e);
					dispose();
				}
			}
		}

	}

	private void showPopup(final int x, final int y) {
		final JPopupMenu popup = new JPopupMenu();

		popup.addPopupMenuListener(new PopupMenuListener() {
			@Override
			public void popupMenuCanceled(final PopupMenuEvent e) {
			}

			@Override
			public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
				setAnimating(true);
			}

			@Override
			public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
				setAnimating(false);
			}
		});

		final JMenuItem disposeMenu = new JMenuItem("ばいばい");
		disposeMenu.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				dispose();
			}
		});

		popup.add(disposeMenu);

		if (isShowSystemTrayMenu()) {

			popup.add(new JSeparator());

			// 「増やす」メニューアイテム
			final JMenuItem increaseMenu = new JMenuItem("増やす");
			increaseMenu.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent event) {
					Main.getInstance().createMascot();
				}
			});

			// 「あつまれ！」メニューアイテム
			final JMenuItem gatherMenu = new JMenuItem("あつまれ！");
			gatherMenu.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent event) {
					getManager().setBehaviorAll(Main.getInstance().getConfiguration(), Main.BEHAVIOR_GATHER);
				}
			});

			// 「一匹だけ残す」メニューアイテム
			final JMenuItem oneMenu = new JMenuItem("一匹だけ残す");
			oneMenu.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent event) {
					getManager().remainOne();
				}
			});

			// 「IEを元に戻す」メニューアイテム
			final JMenuItem restoreMenu = new JMenuItem("IEを元に戻す");
			restoreMenu.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent event) {
					NativeFactory.getInstance().getEnvironment().restoreIE();
				}
			});

			// 「全部ばいばい」メニューアイテム
			final JMenuItem closeMenu = new JMenuItem("全部ばいばい");
			closeMenu.addActionListener(new ActionListener() {
				public void actionPerformed(final ActionEvent e) {
					Main.getInstance().exit();
				}
			});

			popup.add(increaseMenu);
			popup.add(gatherMenu);
			popup.add(oneMenu);
			popup.add(restoreMenu);
			popup.add(new JSeparator());
			popup.add(closeMenu);
		}

		getWindow().asJWindow().requestFocus();

		popup.show(getWindow().asJWindow(), x, y);

	}

	void tick() {
		if (isAnimating()) {
			if (getBehavior() != null) {

				try {
					getBehavior().next();
				} catch (final CantBeAliveException e) {
					log.log(Level.SEVERE, "行き続けることが出来ない状況", e);
					dispose();
				}

				setTime(getTime() + 1);
			}
		}
	}

	public void apply() {
		if (isAnimating()) {

			// 表示できる画像が無ければ何も出来ない
			if (getImage() != null) {

				// ウィンドウの領域を設定
				getWindow().asJWindow().setBounds(getBounds());

				// 画像を設定
				getWindow().setImage(getImage().getImage());

				// 表示
				if (!getWindow().asJWindow().isVisible()) {
					getWindow().asJWindow().setVisible(true);
				}

				// 再描画
				getWindow().updateImage();
			} else {
				if (getWindow().asJWindow().isVisible()) {
					getWindow().asJWindow().setVisible(false);
				}
			}
		}
	}

	public void dispose() {
		log.log(Level.INFO, "マスコット破棄({0})", this);

		getWindow().asJWindow().dispose();
		if (getManager() != null) {
			getManager().remove(Mascot.this);
		}
	}

	public Manager getManager() {
		return this.manager;
	}

	public void setManager(final Manager manager) {
		this.manager = manager;
	}

	public Point getAnchor() {
		return this.anchor;
	}

	public void setAnchor(final Point anchor) {
		this.anchor = anchor;
	}

	public MascotImage getImage() {
		return this.image;
	}

	public void setImage(final MascotImage image) {
		this.image = image;
	}

	public boolean isLookRight() {
		return this.lookRight;
	}

	public void setLookRight(final boolean lookRight) {
		this.lookRight = lookRight;
	}

	public Rectangle getBounds() {

		// 接地座標と画像の中心座標からウィンドウの領域を求める.
		final int top = getAnchor().y - getImage().getCenter().y;
		final int left = getAnchor().x - getImage().getCenter().x;

		final Rectangle result = new Rectangle(left, top, getImage().getSize().width, getImage().getSize().height);

		return result;
	}

	public int getTime() {
		return this.time;
	}

	private void setTime(final int time) {
		this.time = time;
	}

	public Behavior getBehavior() {
		return this.behavior;
	}

	public void setBehavior(final Behavior behavior) throws CantBeAliveException {
		this.behavior = behavior;
		this.behavior.init(this);
	}

	public int getTotalCount() {
		return getManager().getCount();
	}

	private boolean isAnimating() {
		return this.animating;
	}

	private void setAnimating(final boolean animating) {
		this.animating = animating;
	}

	private TranslucentWindow getWindow() {
		return this.window;
	}

	public MascotEnvironment getEnvironment() {
		return environment;
	}
}
