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

/**
 * コメント (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;
	
	/**
	 * コメント表示時間（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;
	public int vpos;
	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 boolean mInvisible = false;
	private float mX;
	private float mY;
	private int mVirtualY;
	private int mHeight = 0;
	
	// 改行調整
	private static String PATTERN_REMOVE_EDGE_LF = "^\\n+(.*)\\n+$";
	private static String PATTERN_ADJUST_STRAIGHT_LF = "\\n{4,}";
	private static SoftReference<Matcher> mMatcherRemoveEdgeLF;
	private static SoftReference<Matcher> mMatcherAdjustStraightLF;
	
	public MessageChat(String m, int v) {
		mMail = m;
		vpos = 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) {
						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 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 {
					Log.d(LOG_TAG, Log.buf().append("Unknown mail=").append(s).toString());
				}
				
				// TODO: ニコスクリプト
			}
		}
		
		if (!setColor) {
			mColor = COLOR_WHITE;
		}
		if (!setFontSize) {
			mFontSize = FONTSIZE_PX_MEDIUM;
		}
		if (!setPos) {
			mPos = POS_NAKA;
		}
	}
	
	@Override
	public String toString() {
		return "mail=" + mMail + " vpos=" + vpos + " text=" + mText;
	}
	
//	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 > AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX) {
					mFontSize = mFontSize * AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX / mWidth;
					mWidth = AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX;
				}
			}
		}
	}
	
	public void computeSpeed() {
		switch (mPos) {
		case POS_NAKA:
			mSpeed = (float) -(AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX + mWidth)
			/ (float) MessageChat.DISPLAY_TIME_VPOS_NAKA;
			break;
		case POS_SHITA:
		case POS_UE:
			mSpeed = 0;
			break;
		default:
			assert false;
			mSpeed = (float) -(AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX + mWidth)
			/ (float) MessageChat.DISPLAY_TIME_VPOS_NAKA;
			break;
		}
	}
	
	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 0xFF808080;
		} 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 AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX;
		case POS_SHITA:
		case POS_UE:
			return (AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX - mWidth) / 2;
		default:
			assert false;
			return AbstractNicoroPlayer.REAL_PLAYER_WIDTH_PX;
		}
	}
	
	public float computeX(int vposCurrent) {
		mX = (float) getStartX() + mSpeed * (vposCurrent - vpos);
		return mX;
	}
	
	/**
	 * 
	 * @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 getDrawY() {
		return mY;
	}
	
	public int computeShitaNextY(List<MessageChat> chats, Random random) {
		int proposeY = AbstractNicoroPlayer.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() > AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3) {
				// TODO: 高さを縮める必要
				proposeY = AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3;
			} else {
				// ランダム
				proposeY = AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3
				- random.nextInt(AbstractNicoroPlayer.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()) > AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3) {
			if (getHeight() > AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3) {
				// TODO: 高さを縮める必要
				proposeY = 0;
			} else {
				// ランダム
				proposeY = random.nextInt(AbstractNicoroPlayer.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()) {
				float nakaXRightStart = naka.computeX(vpos) + naka.getWidth();
				float thisXLeftStart = computeX(vpos);
				float nakaXRightEnd = naka.computeX(vpos + DISPLAY_TIME_VPOS_NAKA) + naka.getWidth();
				float thisXLeftEnd = computeX(vpos + DISPLAY_TIME_VPOS_NAKA);
				if (nakaXRightStart > thisXLeftStart || nakaXRightEnd > thisXLeftEnd) {
					// 横方向で衝突
					proposeY = naka.getNextY();
				}
			}
		}
		if ((proposeY + getHeight()) > AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3) {
			if (getHeight() > AbstractNicoroPlayer.REAL_PLAYER_HEIGHT_PX_4_3) {
				// TODO: 高さを縮める必要
				proposeY = 0;
			} else {
				// ランダム
				proposeY = random.nextInt(AbstractNicoroPlayer.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 == null) ? null
					: mMatcherRemoveEdgeLF.get();
		if (matcherRemoveEdgeLF == null) {
			mMatcherRemoveEdgeLF = null;
		}
		matcherRemoveEdgeLF = Util.getMatcher(
				matcherRemoveEdgeLF, PATTERN_REMOVE_EDGE_LF, text);
		if (mMatcherRemoveEdgeLF == null) {
			mMatcherRemoveEdgeLF = new SoftReference<Matcher>(
					matcherRemoveEdgeLF);
		}
		if (matcherRemoveEdgeLF.find()) {
			text = matcherRemoveEdgeLF.group(0);
		}
		
		// ３行以上の空の改行を２行に
		Matcher matcherAdjustStraightLF =
			(mMatcherAdjustStraightLF == null) ? null
					: mMatcherAdjustStraightLF.get();
		if (matcherAdjustStraightLF == null) {
			mMatcherAdjustStraightLF = null;
		}
		matcherAdjustStraightLF = Util.getMatcher(
				matcherAdjustStraightLF, PATTERN_ADJUST_STRAIGHT_LF, text);
		if (mMatcherAdjustStraightLF == null) {
			mMatcherAdjustStraightLF = new SoftReference<Matcher>(
					matcherAdjustStraightLF);
		}
		text = matcherAdjustStraightLF.replaceAll("\n\n\n");
		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;
	}
	
	public void draw(int vpos, Canvas canvas, Paint paint) {
		if (mText == null) {
			return;
		}
		
		paint.setTextSize(getFontSize());
		float x = computeX(vpos);
		float y = getDrawY();
		
		// TODO 影の表示はPaint#setShadowLayerを使った方がいい？
		
		if (mSplitText == null) {
			paint.setColor(getShadowColor());
			canvas.drawText(mText, 1.0f + x, 1.0f + y, paint);
			
			paint.setColor(getColor());
			canvas.drawText(mText, 0.0f + x, 0.0f + y, paint);
		} else {
			for (String text : mSplitText) {
				paint.setColor(getShadowColor());
				canvas.drawText(text, 1.0f + x, 1.0f + y, paint);
				
				paint.setColor(getColor());
				canvas.drawText(text, 0.0f + x, 0.0f + 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;
	}
	
	private static final Comparator<MessageChat> sVposComparator =
		new Comparator<MessageChat>() {
			@Override
			public int compare(MessageChat object1, MessageChat object2) {
				return object1.vpos - object2.vpos;
			}
		};
	public static Comparator<MessageChat> getVposComparator() {
		return sVposComparator;
	}
}