package jp.sourceforge.nicoro;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.os.Handler;
import android.util.Xml;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

public class ThumbInfo extends HttpXmlLoader implements ThumbInfoInterface {
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
	private static final boolean DEBUG_LOGV_PARSE = Release.IS_DEBUG & false;
	private static final boolean DEBUG_LOGD_PARSE = Release.IS_DEBUG & true;

	private static final String PATTERN_LENGTH = "([0-9]+):([0-9]+)";

	private String mVideoNumber;

	private ThumbInfoInterface.EventListener mEventListener = null;

	// XML解析データ
	private String mVideoId = null;
	private String mTitle = null;
	private String mDescription = null;
	private String mThumbnailUrl = null;
	private String mFirstRetrieve = null;
	private String mLength = null;
	private int mSizeHigh = -1;
	private int mSizeLow = -1;
	private int mViewCounter = -1;
	private int mCommentNum = -1;
	private int mMylistCounter = -1;
	private String mLastResBody = null;
	private String mWatchUrl = null;
	private String mThumbType = null;
	private String mMovieType = null;
	private HashMap<String, ArrayList<String>> mTagsMap = null;

	private String mParsedTitle = null;

	private String mFormattedLengthForPlayer = null;
	private int mLengthBySeconds = -1;
    private DateFormat mDateFormat = new SimpleDateFormat(
        "yyyy-MM-dd'T'HH:mm:ssZ");

	public ThumbInfo(String videoNumber) {
	    super();
		mVideoNumber = videoNumber;
	}
    public ThumbInfo(String videoNumber, Handler handler) {
        super(handler);
        mVideoNumber = videoNumber;
    }

    @Override
    public boolean isNull() {
        return false;
    }

	@Override
    public String getVideoNumber() {
		return mVideoNumber;
	}

	@Override
    public int getViewCounter() {
		return mViewCounter;
	}
	@Override
    public int getCommentNum() {
		return mCommentNum;
	}
	@Override
    public int getMylistCounter() {
		return mMylistCounter;
	}
	@Override
    public String getLength() {
		return mLength;
	}

	@Override
    public String getFormattedLengthForPlayer() {
		if (mFormattedLengthForPlayer == null) {
			if (mLength == null) {
				return null;
			}
			Pattern pattern = Pattern.compile(PATTERN_LENGTH);
			Matcher matcher = pattern.matcher(mLength);
			if (matcher.find()) {
				final String minutes = matcher.group(1);
				final String seconds = matcher.group(2);
				final int minutesLength = minutes.length();
				final int secondsLength = seconds.length();
				if (minutesLength == 3 && secondsLength == 2) {
					// そのまま
					mFormattedLengthForPlayer = mLength;
				} else {
					StringBuilder builder = new StringBuilder(16);
					if (minutesLength == 1) {
						builder.append("00");
					}
					else if (minutesLength == 2) {
						builder.append('0');
					}
					builder.append(minutes).append(':');
					if (secondsLength == 1) {
						builder.append('0');
					}
					builder.append(seconds);
					mFormattedLengthForPlayer = builder.toString();
				}
			} else {
				// 異常時はそのまま
				Log.d(LOG_TAG, Log.buf().append("parse failed:")
						.append(mLength).toString());
				mFormattedLengthForPlayer = mLength;
			}
		}
		return mFormattedLengthForPlayer;
	}

	@Override
    public int getLengthBySecond() {
		if (mLengthBySeconds < 0) {
			if (mLength == null) {
				return 0;
			}
			Pattern pattern = Pattern.compile(PATTERN_LENGTH);
			Matcher matcher = pattern.matcher(mLength);
			if (matcher.find()) {
				String minutes = matcher.group(1);
				String seconds = matcher.group(2);
				mLengthBySeconds = (Integer.parseInt(minutes) * 60
						+ Integer.parseInt(seconds));
			} else {
				// 異常時は0
				Log.d(LOG_TAG, Log.buf().append("parse failed:")
						.append(mLength).toString());
				mLengthBySeconds = 0;
			}
		}
		return mLengthBySeconds;
	}

	@Override
    public int getLengthByVpos() {
		return getLengthBySecond() * 100;
	}

	@Override
    public String getTitle() {
		return mTitle;
	}

	@Override
	public String getParsedTitle() {
	    return mParsedTitle;
	}

	@Override
    public String getDescription() {
		return mDescription;
	}

	@Override
    public String getMovieType() {
		return mMovieType;
	}

	@Override
    public int getSizeHigh() {
		return mSizeHigh;
	}

	@Override
    public int getSizeLow() {
		return mSizeLow;
	}

	@Override
    public String getFirstRetrieve() {
	    return mFirstRetrieve;
	}

	@Override
    public Date getFirstRetrieveAsDate() {
	    if (mFirstRetrieve == null) {
	        return null;
	    }
	    try {
            return mDateFormat.parse(mFirstRetrieve);
        } catch (ParseException e) {
            Log.e(LOG_TAG, e.toString(), e);
            return null;
        }
	}

	@Override
    public String getThumbnailUrl() {
	    return mThumbnailUrl;
	}

    @Override
    public ArrayList<String> getTags() {
        if (mTagsMap == null || mTagsMap.size() == 0) {
            return null;
        }
        ArrayList<String> tags = mTagsMap.get("jp");
        if (tags == null) {
            // とりあえず先頭のを返す
            Iterator<String> it = mTagsMap.keySet().iterator();
            if (it.hasNext()) {
                return mTagsMap.get(it.next());
            } else {
                return null;
            }
        } else {
            return tags;
        }
    }

	@Override
	protected HttpUriRequest createRequest() {
		HttpGet httpRequest = new HttpGet(
				"http://ext.nicovideo.jp/api/getthumbinfo/" + mVideoNumber);
		return httpRequest;
	}

	@Override
	protected boolean createDataFromXml(String xmlBody) {
		XmlPullParser pullParser = Xml.newPullParser();
		try {
			pullParser.setInput(new StringReader(xmlBody));

			int next;
			String name = null;
			while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
				if (DEBUG_LOGV_PARSE) {
					Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
				}
				switch (next) {
				case XmlPullParser.START_TAG:
					name = pullParser.getName();
					if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logStartTag(pullParser, name);
					}
					if ("nicovideo_thumb_response".equals(name)) {
						String status = pullParser.getAttributeValue(
								null, "status");
						if ("ok".equals(status)) {
							// OK
						} else if ("fail".equals(status)) {
							return false;
						} else {
							Log.d(LOG_TAG, Log.buf().append("nicovideo_thumb_response: unknown attribute status=")
									.append(status).toString());
							return false;
						}
					} else if ("tags".equals(name)) {
					    String domain = pullParser.getAttributeValue(null, "domain");
					    parseTags(pullParser, domain);
					} else {
						// その他のタグはとりあえず無視
					}
					break;
				case XmlPullParser.TEXT:
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logText(pullParser);
                    }
                    String text = pullParser.getText();
                    if (name != null) {
    					try {
    						if ("video_id".equals(name)) {
    							mVideoId = text;
    						} else if ("title".equals(name)) {
    							mTitle = text;
    							mParsedTitle = Util.convertHtmlEscapedCharacter(text);
    						} else if ("description".equals(name)) {
    							mDescription = text;
    						} else if ("thumbnail_url".equals(name)) {
    							mThumbnailUrl = text;
    						} else if ("first_retrieve".equals(name)) {
    							mFirstRetrieve = text;
    						} else if ("length".equals(name)) {
    							mLength = text;
    						} else if ("size_high".equals(name)) {
    							mSizeHigh = Integer.parseInt(text);
    						} else if ("size_low".equals(name)) {
    							mSizeLow = Integer.parseInt(text);
    						} else if ("view_counter".equals(name)) {
    							mViewCounter = Integer.parseInt(text);
    						} else if ("comment_num".equals(name)) {
    							mCommentNum = Integer.parseInt(text);
    						} else if ("mylist_counter".equals(name)) {
    							mMylistCounter = Integer.parseInt(text);
    						} else if ("last_res_body".equals(name)) {
    							mLastResBody = text;
    						} else if ("watch_url".equals(name)) {
    							mWatchUrl = text;
    						} else if ("thumb_type".equals(name)) {
    							mThumbType = text;
    						} else if ("movie_type".equals(name)) {
    							mMovieType = text;
    						} else {
    							// その他のタグはとりあえず無視
    						}
    					} catch (NumberFormatException e) {
    						Log.d(LOG_TAG, Log.buf().append("parseInt failed:")
    								.append(text).toString());
    						// できる限り続行
    					}
                    }
					break;
				case XmlPullParser.END_TAG:
					name = pullParser.getName();
					if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logEndTag(pullParser, name);
					}
					name = null;
					// とりあえず無視
					break;
				default:
					break;
				}
			}
		} catch (XmlPullParserException e) {
			Log.d(LOG_TAG, e.toString(), e);
		} catch (IOException e) {
			Log.d(LOG_TAG, e.toString(), e);
		}

		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("ThumbInfo:")
					.append(" video_id=").append(mVideoId)
					.append(" title=").append(mTitle)
					.append(" description=").append(mDescription)
					.append(" thumbnail_url=").append(mThumbnailUrl)
					.append(" first_retrieve=").append(mFirstRetrieve)
					.append(" length=").append(mLength)
					.append(" size_high=").append(mSizeHigh)
					.append(" size_low=").append(mSizeLow)
					.append(" view_counter=").append(mViewCounter)
					.append(" comment_num=").append(mCommentNum)
					.append(" mylist_counter=").append(mMylistCounter)
					.append(" last_res_body=").append(mLastResBody)
					.append(" watch_url=").append(mWatchUrl)
					.append(" thumb_type=").append(mThumbType)
					.append(" movie_type=").append(mMovieType)
					.toString());
		}

		return true;
	}

    private void parseTags(XmlPullParser pullParser, String domain)
    throws XmlPullParserException, IOException {
        int next;
        String name = null;
        ArrayList<String> tags = new ArrayList<String>();
        while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
            if (DEBUG_LOGV_PARSE) {
                Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
            }
            switch (next) {
            case XmlPullParser.START_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logStartTag(pullParser, name);
                }
                // とりあえず無視
                break;
            case XmlPullParser.TEXT:
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logText(pullParser);
                }
                String text = pullParser.getText();
                if ("tag".equals(name)) {
                    tags.add(text);
                } else {
                    // その他のタグはとりあえず無視
                }
                break;
            case XmlPullParser.END_TAG:
                name = pullParser.getName();
                if (DEBUG_LOGD_PARSE) {
                    XmlLoader.logEndTag(pullParser, name);
                }
                if ("tags".equals(name)) {
                    if (mTagsMap == null) {
                        mTagsMap = new HashMap<String, ArrayList<String>>(1);
                    }
                    mTagsMap.put(domain, tags);
                    return;
                } else {
                    // その他のタグはとりあえず無視
                }
                name = null;
                break;
            default:
                break;
            }
        }
    }

	@Override
	protected void dispatchOnFinished() {
		if (mEventListener != null) {
			mEventListener.onFinished(this);
		}
	}
	@Override
	protected void dispatchOnOccurredError(String errorMessage) {
		if (mEventListener != null) {
			mEventListener.onOccurredError(this, errorMessage);
		}
	}

	@Override
	protected boolean readAndCreateData(InputStream inDownload)
	throws IOException {
		String xmlBody = readEntityAndDecode(inDownload);
		return createDataFromXml(xmlBody);
	}

    @Override
    protected String getXmlParseErrorString() {
        return "getthumbinfo: XML parse failed";
    }

	@Override
    public void setEventListener(ThumbInfoInterface.EventListener eventListener) {
		mEventListener = eventListener;
	}
}
