/**
 * 
 */
package jp.sourceforge.nicoro;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.regex.Matcher;

import android.graphics.Canvas;
import android.graphics.Paint;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX_4_3;
import static jp.sourceforge.nicoro.AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3;
import static jp.sourceforge.nicoro.AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX_16_9;

/**
 * コメント (chat)
 */
public class MessageChat {
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;
	
	// コメントカラー
	public static final int COLOR_WHITE = 0xFFFFFFFF;
	public static final int COLOR_RED = 0xFFFF0000;
	public static final int COLOR_PINK = 0xFFFF8080;
	public static final int COLOR_ORANGE = 0xFFFFCC00;
	public static final int COLOR_YELLOW = 0xFFFFFF00;
	public static final int COLOR_GREEN = 0xFF00FF00;
	public static final int COLOR_CYAN = 0xFF00FFFF;
	public static final int COLOR_BLUE = 0xFF0000FF;
	public static final int COLOR_PURPLE = 0xFFC000FF;
	public static final int COLOR_BLACK = 0xFF000000;
	public static final int COLOR_NICONICOWRITE = 0xFFCCCC99;
	public static final int COLOR_WHITE2 = COLOR_NICONICOWRITE;
	public static final int COLOR_TRUERED = 0xFFCC0033;
	public static final int COLOR_RED2 = COLOR_TRUERED;
	public static final int COLOR_PASSIONORANGE = 0xFFFF6600;
	public static final int COLOR_ORANGE2 = COLOR_PASSIONORANGE;
	public static final int COLOR_MADYELLOW = 0xFF999900;
	public static final int COLOR_YELLOW2 = COLOR_MADYELLOW;
	public static final int COLOR_ELEMENTALGREEN = 0xFF00CC66;
	public static final int COLOR_GREEN2 = COLOR_ELEMENTALGREEN;
	public static final int COLOR_MARINEBLUE = 0xFF33FFFC;
	public static final int COLOR_BLUE2 = COLOR_MARINEBLUE;
	public static final int COLOR_NOBLEVIOLET = 0xFF6633CC;
	public static final int COLOR_PURPLE2 = COLOR_NOBLEVIOLET;
	
	// コメント フォントサイズ
	public static final int FONTSIZE_PX_BIG = 39;
	public static final int FONTSIZE_PX_MEDIUM = 24;
	public static final int FONTSIZE_PX_SMALL = 15;
	
	// コメント 位置
	public static final int POS_NAKA = 0;
	public static final int POS_UE = 1;
	public static final int POS_SHITA = 2;
	
	// コメント 方向
	public static final int DIR_NORMAL = 0;
	public static final int DIR_GYAKU = 1;
	
	/**
	 * コメント表示時間（10ms単位）
	 */
	public static final int DISPLAY_TIME_VPOS_NAKA = 400;
	public static final int DISPLAY_TIME_VPOS_UE = 300;
	public static final int DISPLAY_TIME_VPOS_SHITA = 300;
	
	private static final int TEXT_SIZE = 60 * 2;
	
	// XMLから設定するデータ
	private String mMail;
	private int mVpos;
	private String mText;
	
	private ArrayList<String> mSplitText;
	
	public int line;

	private int mWidth;
	private float mSpeed;
	
	private int mColor;
	private int mFontSize;
	private int mPos;
	private int mDir;
	private boolean mInvisible = false;
	private boolean mIsDefaultColor;
	private boolean mIsDefaultFontSize;
	private boolean mIsDefaultPos;
	/**
	 * コメントの表示時間、ニコスクリプトの実行時間
	 */
	protected int mTime = -1;
	private float mX;
	private float mY;
	private int mVirtualY;
	private int mHeight = 0;
	
	// 逆再生のため位置情報計算変更
	int mLastVpos;
	
	// 改行調整
	private static String PATTERN_REMOVE_EDGE_LF = "^\\n+(.*)\\n+$";
	private static String PATTERN_ADJUST_STRAIGHT_LF = "\\n{4,}";
	private static ThreadSoftReference<Matcher> mMatcherRemoveEdgeLF =
	    new ThreadSoftReference<Matcher>();
	private static ThreadSoftReference<Matcher> mMatcherAdjustStraightLF =
	    new ThreadSoftReference<Matcher>();
	
	public MessageChat(String m, int v) {
		this(m, v, false);
	}
	
	protected MessageChat(String m, int v, boolean isFork) {
		mMail = m;
		setVpos(v);
		
		boolean setColor = false;
		boolean setFontSize = false;
		boolean setPos = false;
		if (mMail != null) {
			String[] mails = mMail.split(" ");
			for (String s : mails) {
				if (s.startsWith("#")) {
					// 色
					if (!setColor) {
						if (s.length() >= 1) {
							try {
								mColor = 0xFF000000 | Integer.parseInt(s.substring(1), 16);
								setColor = true;
							} catch (NumberFormatException e) {
								Log.d(LOG_TAG, Log.buf().append("Invalid color value=")
										.append(s).toString());
								assert setColor == false;
							}
						} else {
							Log.d(LOG_TAG, Log.buf().append("Invalid color value=")
									.append(s).toString());
						}
					}
				} else if (s.equals("white")) {
					if (!setColor) {
						mColor = COLOR_WHITE;
						setColor = true;
					}
				} else if (s.equals("red")) {
					if (!setColor) {
						mColor = COLOR_RED;
						setColor = true;
					}
				} else if (s.equals("pink")) {
					if (!setColor) {
						mColor = COLOR_PINK;
						setColor = true;
					}
				} else if (s.equals("orange")) {
					if (!setColor) {
						mColor = COLOR_ORANGE;
						setColor = true;
					}
				} else if (s.equals("yellow")) {
					if (!setColor) {
						mColor = COLOR_YELLOW;
						setColor = true;
					}
				} else if (s.equals("green")) {
					if (!setColor) {
						mColor = COLOR_GREEN;
						setColor = true;
					}
				} else if (s.equals("cyan")) {
					if (!setColor) {
						mColor = COLOR_CYAN;
						setColor = true;
					}
				} else if (s.equals("blue")) {
					if (!setColor) {
						mColor = COLOR_BLUE;
						setColor = true;
					}
				} else if (s.equals("purple")) {
					if (!setColor) {
						mColor = COLOR_PURPLE;
						setColor = true;
					}
				} else if (s.equals("black")) {
					if (!setColor) {
						mColor = COLOR_BLACK;
						setColor = true;
					}
				} else if (s.equals("niconicowhite")) {
					if (!setColor) {
						mColor = COLOR_NICONICOWRITE;
						setColor = true;
					}
				} else if (s.equals("white2")) {
					if (!setColor) {
						mColor = COLOR_WHITE2;
						setColor = true;
					}
				} else if (s.equals("truered")) {
					if (!setColor) {
						mColor = COLOR_TRUERED;
						setColor = true;
					}
				} else if (s.equals("red")) {
					if (!setColor) {
						mColor = COLOR_RED2;
						setColor = true;
					}
				} else if (s.equals("passionorange")) {
					if (!setColor) {
						mColor = COLOR_PASSIONORANGE;
						setColor = true;
					}
				} else if (s.equals("orange2")) {
					if (!setColor) {
						mColor = COLOR_ORANGE2;
						setColor = true;
					}
				} else if (s.equals("madyellow")) {
					if (!setColor) {
						mColor = COLOR_MADYELLOW;
						setColor = true;
					}
				} else if (s.equals("yellow2")) {
					if (!setColor) {
						mColor = COLOR_YELLOW2;
						setColor = true;
					}
				} else if (s.equals("elementalgreen")) {
					if (!setColor) {
						mColor = COLOR_ELEMENTALGREEN;
						setColor = true;
					}
				} else if (s.equals("green2")) {
					if (!setColor) {
						mColor = COLOR_GREEN2;
						setColor = true;
					}
				} else if (s.equals("marineblue")) {
					if (!setColor) {
						mColor = COLOR_MARINEBLUE;
						setColor = true;
					}
				} else if (s.equals("blue2")) {
					if (!setColor) {
						mColor = COLOR_BLUE2;
						setColor = true;
					}
				} else if (s.equals("nobleviolet")) {
					if (!setColor) {
						mColor = COLOR_NOBLEVIOLET;
						setColor = true;
					}
				} else if (s.equals("purple2")) {
					if (!setColor) {
						mColor = COLOR_PURPLE2;
						setColor = true;
					}
				} else if (s.equals("big")) {
					if (!setFontSize) {
						mFontSize = FONTSIZE_PX_BIG;
						setFontSize = true;
					}
				} else if (s.equals("medium")) {
					if (!setFontSize) {
						mFontSize = FONTSIZE_PX_MEDIUM;
						setFontSize = true;
					}
				} else if (s.equals("small")) {
					if (!setFontSize) {
						mFontSize = FONTSIZE_PX_SMALL;
						setFontSize = true;
					}
				} else if (s.equals("naka")) {
					if (!setPos) {
						mPos = POS_NAKA;
						setPos = true;
					}
				} else if (s.equals("ue")) {
					if (!setPos) {
						mPos = POS_UE;
						setPos = true;
					}
				} else if (s.equals("shita")) {
					if (!setPos) {
						mPos = POS_SHITA;
						setPos = true;
					}
				} else if (s.equals("invisible")) {
					mInvisible = true;
				} else if (s.equals("from_button")) {
					// TODO: 現状無視
				} else if (s.equals("is_button")) {
					// TODO: 現状無視
				} else if (s.equals("184")) {
					// 無視
				} else if (s.equals("sage")) {
					// 無視
				} else if (s.equals("docomo")) {
					// 無視
				} else if (isFork) {
					if (s.startsWith("@")) {
						if (s.length() > 1) {
							if (Character.isDigit(s.charAt(1))) {
								try {
									mTime = Integer.parseInt(s.substring(1));
								} catch (NumberFormatException e) {
									Log.d(LOG_TAG, Log.buf().append("Invalid time value=")
											.append(s).toString());
								}
							} else if (s.equals("@button")) {
								// TODO: 現状無視
							} else {
								Log.d(LOG_TAG, Log.buf().append("Unknown mail=")
										.append(s).toString());
							}
						} else {
							Log.d(LOG_TAG, Log.buf().append("Invalid @ value=")
									.append(s).toString());
						}
					} else if (s.equals("migi")) {
						// TODO: 現状無視
					} else if (s.equals("hidari")) {
						// TODO: 現状無視
					} else if (s.equals("hidden")) {
						// TODO: 現状無視
					} else if (s.equals("local")) {
						// TODO: 現状無視
					} else {
						Log.d(LOG_TAG, Log.buf().append("Unknown mail=")
								.append(s).toString());
					}
				} else {
					Log.d(LOG_TAG, Log.buf().append("Unknown mail=")
							.append(s).toString());
				}
			}
		}
		
		if (!setColor) {
			mColor = COLOR_WHITE;
		}
		mIsDefaultColor = !setColor;
		if (!setFontSize) {
			mFontSize = FONTSIZE_PX_MEDIUM;
		}
		mIsDefaultFontSize = !setFontSize;
		if (!setPos) {
			mPos = POS_NAKA;
		}
		mIsDefaultPos = !setPos;
		mDir = DIR_NORMAL;
	}
	
	private static ThreadLocal<StringBuilder> sStringBuilder =
		new ThreadLocalStringBuilder();
	
	@Override
	public String toString() {
		StringBuilder sb = sStringBuilder.get();
		sb.setLength(0);
		return sb.append("mail=").append(mMail)
			.append(" vpos=").append(mVpos)
			.append(" text=").append(mText).toString();
	}
	
//	static private SoftReference<float[]> sComputeWidthBuffer = null;
//	private static float[] getComputeWidthBuffer(final int textLength) {
//		float[] buffer;
//		if (sComputeWidthBuffer == null) {
//			buffer = new float[Math.max(TEXT_SIZE, textLength)];
//			sComputeWidthBuffer = new SoftReference<float[]>(buffer);
//		} else {
//			buffer = sComputeWidthBuffer.get();
//			if (buffer == null) {
//				buffer = new float[Math.max(TEXT_SIZE, textLength)];
//				sComputeWidthBuffer = new SoftReference<float[]>(buffer);
//			} else {
//				if (buffer.length < textLength) {
//					buffer = new float[textLength];
//					sComputeWidthBuffer = new SoftReference<float[]>(buffer);
//				}
//			}
//		}
//		return buffer;
//	}
	
	public void computeWidth(Paint paint) {
		if (mText == null) {
			mWidth = 0;
		} else {
			paint.setTextSize(mFontSize);
			if (mSplitText == null) {
	//			float[] widths = getComputeWidthBuffer(mText.length());
	//			int num = paint.getTextWidths(mText, widths);
	//			float w = 0.0f;
	//			for (int i = 0; i < num; ++i) {
	//				w += widths[i];
	//			}
	//			mWidth = (int) w;
				
				mWidth = (int) paint.measureText(mText);
			} else {
				float maxWidth = 0.0f;
				for (String t : mSplitText) {
					maxWidth = Math.max(paint.measureText(t), maxWidth);
				}
				mWidth = (int) maxWidth;
			}
			if (mPos == POS_SHITA || mPos == POS_UE) {
				if (mWidth > REAL_PLAYER_WIDTH_PX_4_3) {
					mFontSize = mFontSize * REAL_PLAYER_WIDTH_PX_4_3 / mWidth;
					mWidth = REAL_PLAYER_WIDTH_PX_4_3;
				}
			}
		}
	}
	
	public void computeSpeed() {
//		switch (mPos) {
//		case POS_NAKA:
//			mSpeed = (float) -(REAL_PLAYER_WIDTH_PX_4_3 + mWidth)
//				/ (float) DISPLAY_TIME_VPOS_NAKA;
//			break;
//		case POS_SHITA:
//		case POS_UE:
//			mSpeed = 0;
//			break;
//		default:
//			assert false;
//			mSpeed = (float) -(REAL_PLAYER_WIDTH_PX_4_3 + mWidth)
//				/ (float) DISPLAY_TIME_VPOS_NAKA;
//			break;
//		}

		if (mPos == POS_SHITA || mPos == POS_UE) {
			mSpeed = 0;
		} else {
			assert mPos == POS_NAKA;
			mSpeed = (float) -(REAL_PLAYER_WIDTH_PX_4_3 + mWidth)
				/ (float) DISPLAY_TIME_VPOS_NAKA;
//			if (mDir == DIR_GYAKU) {
//				mSpeed = -mSpeed;
//			} else {
//				assert mDir == DIR_NORMAL;
//			}
		}
	}
	
	public void computeLineHeight(Paint paint) {
		paint.setTextSize(mFontSize);
		mHeight = paint.getFontMetricsInt(null);
	}
	
	public int getWidth() {
		return mWidth;
	}
	
	public int getColor() {
		return mColor;
	}
	public int getShadowColor() {
		if (mColor == COLOR_BLACK) {
			return 0xFFD0D0D0;
		} else {
			return 0xFF000000;
		}
	}
	
	public int getFontSize() {
		return mFontSize;
	}
	
	public float getSpeed() {
		return mSpeed;
	}
	
	public int getPos() {
		return mPos;
	}
	
	public int getStartX() {
//		switch (mPos) {
//		case POS_NAKA:
//			return REAL_PLAYER_WIDTH_PX_4_3;
//		case POS_SHITA:
//		case POS_UE:
//			return (REAL_PLAYER_WIDTH_PX_4_3 - mWidth) / 2;
//		default:
//			assert false;
//			return REAL_PLAYER_WIDTH_PX_4_3;
//		}

		if (mPos == POS_SHITA || mPos == POS_UE) {
			return (REAL_PLAYER_WIDTH_PX_4_3 - mWidth) / 2;
		} else {
			assert mPos == POS_NAKA;
			if (mDir == DIR_GYAKU) {
				return -mWidth;
			} else {
				assert mDir == DIR_NORMAL;
				return REAL_PLAYER_WIDTH_PX_4_3;
			}
		}
	}
	
	public float computeX(int vposCurrent) {
//		// TODO: ＠逆のためにはこれも現在地からの相対的な値で計算しないと駄目？
////		float x = (float) getStartX() + mSpeed * (vposCurrent - mVpos);
//		float x = (float) getStartX() + mSpeed * (vposCurrent - mVpos + 100);
//		return x;
		float distance;
		if (mDir == DIR_GYAKU) {
			distance = -mSpeed * (vposCurrent - mVpos + 100);
		} else {
			assert mDir == DIR_NORMAL;
			distance = mSpeed * (vposCurrent - mVpos + 100);
		}
		float x = (float) getStartX() + distance;
		return x;
	}
	
	public float computeXRelatively(int vposCurrent) {
//		mX += mSpeed * (vposCurrent - mLastVpos);
		float distance;
		if (mDir == DIR_GYAKU) {
			distance = -mSpeed * (vposCurrent - mLastVpos);
		} else {
			assert mDir == DIR_NORMAL;
			distance = mSpeed * (vposCurrent - mLastVpos);
		}
		mX += distance;
		mLastVpos = vposCurrent;
		return mX;
	}
	
	public void initializeX(int vposCurrent) {
////		mX = (float) getStartX() + mSpeed * (vposCurrent - mVpos);
//		mX = (float) getStartX() + mSpeed * (vposCurrent - mVpos + 100);
		float distance;
		if (mDir == DIR_GYAKU) {
			distance = -mSpeed * (vposCurrent - mVpos + 100);
		} else {
			assert mDir == DIR_NORMAL;
			distance = mSpeed * (vposCurrent - mVpos + 100);
		}
		mX = (float) getStartX() + distance;
		mLastVpos = vposCurrent;
	}

	/**
	 * 
	 * @param y nakaまたはueなら上端、shitaなら下端に当たる座標
	 * @param paint
	 * @return 次のChatが表示されるY座標。nakaまたはueなら下端、shitaなら上端の座標
	 */
	public int setY(int y, Paint paint) {
		mVirtualY = y;
		paint.setTextSize(mFontSize);
		switch (mPos) {
		case POS_NAKA:
		case POS_UE:
			mY = y - paint.ascent();
			return mVirtualY + getHeight();
		case POS_SHITA:
			mY = y - paint.descent();
			return mVirtualY - getHeight();
		default:
			assert false;
			return y;
		}
	}
	
	public int getY() {
		return mVirtualY;
	}
	
	public int getNextY() {
		switch (mPos) {
		case POS_NAKA:
		case POS_UE:
			return mVirtualY + getHeight();
		case POS_SHITA:
			return mVirtualY - getHeight();
		default:
			assert false;
			return mVirtualY + getHeight();
		}
	}
	
	public float getDrawX() {
		return mX;
	}
	
	public float getDrawY() {
		return mY;
	}
	
	public int computeShitaNextY(List<MessageChat> chats, Random random) {
		int proposeY = REAL_PLAYER_HEIGHT_PX_4_3;
		for (MessageChat shita : chats) {
			if ((proposeY - getHeight()) >= shita.getY()) {
				break;
			}
			proposeY = shita.getNextY();
		}
		if ((proposeY - getHeight()) < 0) {
			if (getHeight() >= REAL_PLAYER_HEIGHT_PX_4_3) {
				// TODO: 高さを縮める必要
				proposeY = REAL_PLAYER_HEIGHT_PX_4_3;
			} else {
				// ランダム
				proposeY = REAL_PLAYER_HEIGHT_PX_4_3
				- random.nextInt(REAL_PLAYER_HEIGHT_PX_4_3
						- getHeight());
			}
		}
		return proposeY;
	}
	
	public void addShitaOrder(List<MessageChat> chats) {
		int i = 0;
		for (MessageChat shita : chats) {
			if (getY() < shita.getY()) {
//			if (getNextY() < shita.getNextY()) {
				++i;
			} else {
				break;
			}
		}
		chats.add(i, this);
	}

	public int computeUeNextY(List<MessageChat> chats, Random random) {
		int proposeY = 0;
		for (MessageChat ue : chats) {
			if ((proposeY + getHeight()) <= ue.getY()) {
				break;
			}
			proposeY = ue.getNextY();
		}
		if ((proposeY + getHeight()) > REAL_PLAYER_HEIGHT_PX_4_3) {
			if (getHeight() >= REAL_PLAYER_HEIGHT_PX_4_3) {
				// TODO: 高さを縮める必要
				proposeY = 0;
			} else {
				// ランダム
				proposeY = random.nextInt(REAL_PLAYER_HEIGHT_PX_4_3
						- getHeight());
			}
		}
		return proposeY;
	}
	
	public void addUeOrder(List<MessageChat> chats) {
		int i = 0;
		for (MessageChat ue : chats) {
			if (getY() > ue.getY()) {
//			if (getNextY() > ue.getNextY()) {
				++i;
			} else {
    			break;
			}
		}
		chats.add(i, this);
	}

	public int computeNakaNextY(int vposCurrent, List<MessageChat> chats, Random random) {
		int proposeY = 0;
		int lastNakaY = 0;
		int lastNakaNextY = -1;
		for (MessageChat naka : chats) {
			assert naka.getY() >= lastNakaY;
			assert (naka.getY() == lastNakaY && lastNakaNextY >= 0) ? naka.getNextY() >= lastNakaNextY : true;
			lastNakaY = naka.getY();
			lastNakaNextY = naka.getNextY();
			
			if ((proposeY + getHeight()) > naka.getY()) {
				if (mDir == DIR_GYAKU) {
					float nakaXLeftStart = naka.computeXRelatively(mVpos);
					float thisXRightStart = computeX(mVpos) + getWidth();
					float nakaXLeftEnd = naka.computeXRelatively(mVpos + DISPLAY_TIME_VPOS_NAKA);
					float thisXRightEnd = computeX(mVpos + DISPLAY_TIME_VPOS_NAKA) + getWidth();
					if (nakaXLeftStart < thisXRightStart
							|| nakaXLeftEnd < thisXRightEnd) {
						// 横方向で衝突
						proposeY = naka.getNextY();
					}
				} else {
					assert mDir == DIR_NORMAL;
//					float nakaXRightStart = naka.computeX(mVpos) + naka.getWidth();
					float nakaXRightStart = naka.computeXRelatively(mVpos) + naka.getWidth();
					float thisXLeftStart = computeX(mVpos);
//					float nakaXRightEnd = naka.computeX(mVpos + DISPLAY_TIME_VPOS_NAKA) + naka.getWidth();
					float nakaXRightEnd = naka.computeXRelatively(mVpos + DISPLAY_TIME_VPOS_NAKA) + naka.getWidth();
					float thisXLeftEnd = computeX(mVpos + DISPLAY_TIME_VPOS_NAKA);
					if (nakaXRightStart > thisXLeftStart
							|| nakaXRightEnd > thisXLeftEnd) {
						// 横方向で衝突
						proposeY = naka.getNextY();
					}
				}
			}
		}
		if ((proposeY + getHeight()) > REAL_PLAYER_HEIGHT_PX_4_3) {
			if (getHeight() >= REAL_PLAYER_HEIGHT_PX_4_3) {
				// TODO: 高さを縮める必要
				proposeY = 0;
			} else {
				// ランダム
				proposeY = random.nextInt(REAL_PLAYER_HEIGHT_PX_4_3
						- getHeight());
			}
		}
		return proposeY;
	}
	
	public void addNakaOrder(List<MessageChat> chats) {
		int i = 0;
		for (MessageChat naka : chats) {
			if (getY() > naka.getY()) {
				++i;
			} else if (getY() == naka.getY()) {
				if (getNextY() > naka.getNextY()) {
					++i;
				} else {
					break;
				}
			} else {
    			break;
			}
		}
		chats.add(i, this);
	}
	
	public void setText(String text) {
		mText = text;
		if (text == null) {
			return;
		}
		if (text.length() == 0) {
			mText = null;
			return;
		}
		
		// テキスト先頭と末尾の改行文字削除
		Matcher matcherRemoveEdgeLF = mMatcherRemoveEdgeLF.getValue();
		boolean createMatcherRemoveEdgeLF = (matcherRemoveEdgeLF == null);
		matcherRemoveEdgeLF = Util.getMatcher(
				matcherRemoveEdgeLF, PATTERN_REMOVE_EDGE_LF, text);
		if (createMatcherRemoveEdgeLF) {
			mMatcherRemoveEdgeLF.setValue(matcherRemoveEdgeLF);
		}
		if (matcherRemoveEdgeLF.find()) {
			text = matcherRemoveEdgeLF.group(0);
		}
		
		// ３行以上の空の改行を２行に
		Matcher matcherAdjustStraightLF = mMatcherAdjustStraightLF.getValue();
		boolean createMatcherAdjustStraightLF = (matcherAdjustStraightLF == null);
		matcherAdjustStraightLF = Util.getMatcher(
				matcherAdjustStraightLF, PATTERN_ADJUST_STRAIGHT_LF, text);
		if (createMatcherAdjustStraightLF) {
			mMatcherAdjustStraightLF.setValue(matcherAdjustStraightLF);
		}
		// TODO: 原因不明だがreplaceAll内でCrash errosが来たので例外対策
		// →何かの拍子でMessageLoaderのスレッドが２つ同時に動いたとか？
		try {
			text = matcherAdjustStraightLF.replaceAll("\n\n\n");
		} catch (Exception e) {
			Log.e(LOG_TAG, e.toString(), e);
		} finally {
			mText = text;
		}
		
		int indexLF = text.indexOf('\n');
		if (indexLF < 0) {
			// 改行文字無し
			return;
		}
		assert indexLF > 0;
		
		mSplitText = new ArrayList<String>();
		int indexStart = 0;
		assert indexLF > indexStart;
		
		while (true) {
			mSplitText.add(text.substring(indexStart, indexLF));
			
			indexStart = indexLF + 1;
			indexLF = text.indexOf('\n', indexStart);
			if (indexLF < 0) {
				mSplitText.add(text.substring(indexStart));
				break;
			}
		}
	}

	public String getText() {
		return mText;
	}
	
	/**
	 * コメントを描画
	 * @param vpos 動画の現在時刻
	 * @param canvas 描画対象
	 * @param paint 描画に使用する{@link android.graphics.Paint}
	 * @param controller コメント管理のコントローラ
	 */
	public void draw(int vpos, Canvas canvas, Paint paint,
			MessageChatController controller) {
		if (mText == null) {
			return;
		}
		
		paint.setTextSize(getFontSize());
//		mDir = controller.getDir(isFork());
//		float x = computeX(vpos);
		float x = computeXRelatively(vpos);
		float y = getDrawY();
		
		paint.setShadowLayer(1.5f, 0.0f, 0.0f, getShadowColor());
		paint.setColor(getColor());
		if (mSplitText == null) {
			canvas.drawText(mText, x, y, paint);
		} else {
			for (String text : mSplitText) {
				canvas.drawText(text, x, y, paint);
				y += getFontSize();
			}
		}
	}
	
	private int getHeight() {
		assert mHeight > 0;
		if (mSplitText == null) {
			return mHeight;
		} else {
			return mHeight * mSplitText.size();
		}
	}
	
	public int getSingleLineHeight() {
		return mHeight;
	}
	
	public int getVpos() {
		return mVpos;
	}
	public void setVpos(int vpos) {
		mVpos = vpos;
		mLastVpos = vpos;
	}
	
	/**
	 * コメントの有効時間の取得
	 * @return
	 */
	public int getTime() {
	    return mTime;
	}
	/**
	 * コメントの有効時間を外部から設定<br>
	 * {@link #setText}の後に呼ぶこと
	 * @param time
	 */
	public void setTime(int time) {
	    mTime = time;
	}
	
	public boolean isDisplayedTiming(int vpos) {
		int myVpos;
		switch (mPos) {
		case POS_NAKA:
//            myVpos = mVpos - 100;
			// 16:9考慮して１秒より早めに表示
			myVpos = mVpos - 100 - 100;
			break;
		case POS_UE:
		case POS_SHITA:
            myVpos = mVpos;
			break;
		default:
			assert false;
            myVpos = mVpos - 100 - 100;
			break;
		}
		return vpos >= myVpos;
	}
	
	public boolean isRemovedTiming(int vpos) {
		boolean ret;
		if (mTime >= 0) {
			ret = (vpos > mVpos + mTime * 100);
		} else {
//			switch (mPos) {
//			case POS_NAKA:
//	//			ret = (vpos > mVpos - 100 + MessageChat.DISPLAY_TIME_VPOS_NAKA);
//	//			// 16:9考慮して１秒余分に表示
//	//			ret = (vpos > mVpos - 100 + 100 + DISPLAY_TIME_VPOS_NAKA);
//				float currentX = computeXRelatively(vpos);
//				ret = (currentX + getWidth() <
//						(REAL_PLAYER_WIDTH_PX_4_3 - REAL_PLAYER_WIDTH_PX_16_9) / 2);
//				break;
//			case POS_UE:
//				ret = (vpos > mVpos + DISPLAY_TIME_VPOS_UE);
//				break;
//			case POS_SHITA:
//				ret = (vpos > mVpos + DISPLAY_TIME_VPOS_SHITA);
//				break;
//			default:
//				assert false;
//				ret = (vpos > mVpos - 100 + 100 + DISPLAY_TIME_VPOS_NAKA);
//				break;
//			}
			
			if (mPos == POS_UE) {
				ret = (vpos > mVpos + DISPLAY_TIME_VPOS_UE);
			} else if (mPos == POS_SHITA) {
				ret = (vpos > mVpos + DISPLAY_TIME_VPOS_SHITA);
			} else {
				assert mPos == POS_NAKA;
				float currentX = computeXRelatively(vpos);
				if (mDir == DIR_GYAKU) {
					ret = (currentX >
						REAL_PLAYER_WIDTH_PX_16_9 - (REAL_PLAYER_WIDTH_PX_16_9 - REAL_PLAYER_WIDTH_PX_4_3) / 2);
				} else {
	//				ret = (vpos > mVpos - 100 + MessageChat.DISPLAY_TIME_VPOS_NAKA);
	//				// 16:9考慮して１秒余分に表示
	//				ret = (vpos > mVpos - 100 + 100 + DISPLAY_TIME_VPOS_NAKA);
					ret = (currentX + getWidth() <
						(REAL_PLAYER_WIDTH_PX_4_3 - REAL_PLAYER_WIDTH_PX_16_9) / 2);
				}
			}
		}
		return ret;
	}
	
	private static final Comparator<MessageChat> sVposComparator =
		new Comparator<MessageChat>() {
			@Override
			public int compare(MessageChat object1, MessageChat object2) {
				return object1.mVpos - object2.mVpos;
			}
		};
	public static Comparator<MessageChat> getVposComparator() {
		return sVposComparator;
	}
	
	public void setDefaultSetting(MessageChatController controller) {
		if (mIsDefaultColor) {
			mColor = controller.getDefaultColor();
		}
		if (mIsDefaultFontSize) {
			mFontSize = controller.getDefaultFontSize();
		}
		if (mIsDefaultPos) {
			mPos = controller.getDefaultPos();
		}
		
		mDir = controller.getDir(isFork());
	}
	
	/**
	 * コメント表示用コンテナに追加する際の前準備
	 * @param controller コメント管理のコントローラ
	 * @param paintText コメント表示に使用する{@link android.graphics.Paint}
	 * @param vpos 動画の現在時刻
	 */
	public void prepareAdd(MessageChatController controller,
			Paint paintText, int vpos) {
		setDefaultSetting(controller);
		computeWidth(paintText);
		computeSpeed();
		computeLineHeight(paintText);
		initializeX(vpos);
	}
	
	/**
     * コメント表示用コンテナから除去する際の前準備
	 * @param controller コメント管理のコントローラ
	 */
	public void prepareRemove(MessageChatController controller) {
	}
	
	/**
	 * コメントのスクロールする方向を設定
	 * @param controller コメント管理のコントローラ
	 */
	public void setDir(MessageChatController controller) {
		mDir = controller.getDir(isFork());
	}
	
	/**
	 * コメントがニコスクリプトかどうか判定
	 * @return
	 */
	protected boolean isScript() {
		return false;
	}
    /**
     * コメントが投稿者コメントかどうか判定
     * @return
     */
	protected boolean isFork() {
		return false;
	}
}