package jp.sourceforge.nicoro;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HTTP;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Xml;

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

public class MessageLoader extends HttpXmlLoader implements MessageLoaderInterface {
	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 & false;

	protected int mResSize = 0;
	protected int mOldResSize = 0;
	protected int mNicosResSize = 0;

	private String mUrl;
	protected String mThreadId;
	private String mCookie;
	protected String mUserId;
	protected String mThreadKey;
	protected String mForce184;
	private int mVideoMinutes = 0;
	private Context mContext;
	private String mNicosId;
	private int mLastRes;
    private int mNicosLastRes;
    private String mTicket;
    private String mNicosTicket;

	private Vector<MessageChat> mChats;
    private Vector<MessageChat> mChatsFork;

	private MessageLoaderInterface.EventListener mEventListener = null;

	private static class ViewCounter {
	    public int video;
	    public int mylist;
	    public String id;
	}
	private ViewCounter mViewCounter;

	private static class Leaf {
	    @SuppressWarnings("unused")
        public String thread;
        @SuppressWarnings("unused")
	    public int leaf;
	    public int count;
	}
	private ArrayList<Leaf> mLeaf = new ArrayList<MessageLoader.Leaf>();

	public MessageLoader(String url, String threadId, String cookie,
			String userId, String threadKey, String force184,
			Context context, String nicosId) {
		mUrl = url;
		mThreadId = threadId;
		mCookie = cookie;
		mUserId = userId;
		mThreadKey = threadKey;
		mForce184 = force184;
		mContext = context;
		mNicosId = nicosId;
	}

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

	@Override
    public Vector<MessageChat> getChats() {
		return mChats;
	}

    @Override
    public Vector<MessageChat> getChatsFork() {
        return mChatsFork;
    }

	@Override
    public int getViewCounterVideo() {
	    if (mViewCounter == null) {
	        return 0;
	    } else {
	        return mViewCounter.video;
	    }
	}

    @Override
    public int getViewCounterMylist() {
        if (mViewCounter == null) {
            return 0;
        } else {
            return mViewCounter.mylist;
        }
    }

    @Override
    public String getViewCounterId() {
        if (mViewCounter == null) {
            return null;
        } else {
            return mViewCounter.id;
        }
    }

    @Override
    public int getAllLeafCount() {
        int count = 0;
        for (Leaf l : mLeaf) {
            count += l.count;
        }
        return count;
    }

    @Override
    public int getLastRes() {
        return mLastRes;
    }

    @Override
    public String getTicket() {
        return mTicket;
    }

    @Override
    public int getNicosLastRes() {
        return mNicosLastRes;
    }

    @Override
    public String getNicosTicket() {
        return mNicosTicket;
    }

    @Override
    public String getThreadId() {
        return mThreadId;
    }

    @Override
    public String getNicosId() {
        return mNicosId;
    }

	private void parse(String body) {
		assert mResSize > 0;
        Vector<MessageChat> chats = mChats;
        Vector<MessageChat> chatsFork = mChatsFork;
        mChats = null;
        mChatsFork = null;
		if (chats == null) {
			chats = new Vector<MessageChat>(mResSize);
		} else {
			chats.clear();
		}
        if (chatsFork == null) {
            chatsFork = new Vector<MessageChat>(1000);
        } else {
            chatsFork.clear();
        }

		XmlPullParser pullParser = Xml.newPullParser();
		try {
			pullParser.setInput(new StringReader(body));

			int next;
			String name = null;
			MessageChat chat = null;

            @SuppressWarnings("unused")
            boolean isNicos = false;
            boolean isFork = false;
            Vector<MessageChat> currentChats = chats;
            boolean isForkChat = false;

			while ((next = pullParser.next()) != XmlPullParser.END_DOCUMENT) {
                if (DEBUG_LOGV_PARSE) {
                    Log.v(LOG_TAG, Log.buf().append("next=").append(next).toString());
                }

                boolean loggedParse = false;
				switch (next) {
				case XmlPullParser.START_TAG:
					name = pullParser.getName();
					if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logStartTag(pullParser, name);
                        loggedParse = true;
					}
					if ("chat".equals(name)) {
//						assert chat == null;
					    isForkChat = (Util.parseInt(
					            pullParser.getAttributeValue(null, "fork"), 0) == 1);
						chat = createMessageChat(/*isFork*/ isForkChat,
								pullParser.getAttributeValue(null, "mail"),
								Integer.parseInt(pullParser.getAttributeValue(null, "vpos")),
                                Integer.parseInt(pullParser.getAttributeValue(null, "no")),
                                Util.parseLong(pullParser.getAttributeValue(null, "date"), 0),
                                pullParser.getAttributeValue(null, "user_id"),
                                Util.parseInt(pullParser.getAttributeValue(null, "score"), 0));
					} else if ("thread".equals(name)) {
					    isNicos = false;
					    isFork = false;
					    currentChats = chats;

                        String fork = pullParser.getAttributeValue(null, "fork");
					    if (Util.parseInt(fork, 0) == 1) {
					        isFork = true;
					        currentChats = chatsFork;
					    }

					    String thread = pullParser.getAttributeValue(null, "thread");
					    if (thread != null && thread.equals(mNicosId)) {
					        isNicos = true;
					    }

					    int lastRes = Util.parseInt(
					            pullParser.getAttributeValue(null, "last_res"), -1);
					    String ticket = pullParser.getAttributeValue(null, "ticket");

					    if (!isFork) {
					        if (isNicos) {
					            mNicosLastRes = lastRes;
					            mNicosTicket = ticket;
					        } else {
                                mLastRes = lastRes;
                                mTicket = ticket;
					        }
					    }
                    } else if ("view_counter".equals(name)) {
                        if (mViewCounter == null) {
                            mViewCounter = new ViewCounter();
                        }

                        String video = pullParser.getAttributeValue(null, "video");
                        mViewCounter.video = Util.parseInt(video, 0);
                        String mylist = pullParser.getAttributeValue(null, "mylist");
                        mViewCounter.mylist = Util.parseInt(mylist, 0);
                        mViewCounter.id = pullParser.getAttributeValue(null, "id");
                    } else if ("leaf".equals(name)) {
                        Leaf leaf = new Leaf();
                        leaf.thread = pullParser.getAttributeValue(null, "thread");
                        leaf.leaf = Util.parseInt(
                                pullParser.getAttributeValue(null, "leaf"), 0);
                        leaf.count = Util.parseInt(
                                pullParser.getAttributeValue(null, "count"), 0);
                        mLeaf.add(leaf);
					} else {
						// その他のタグはとりあえず無視
					    if (DEBUG_LOGD && !loggedParse) {
	                        XmlLoader.logStartTag(pullParser, name);
					    }
					}
					break;
				case XmlPullParser.TEXT:
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logText(pullParser);
                        loggedParse = true;
                    }
                    String text = pullParser.getText();
					if (name != null) {
						if ("chat".equals(name)) {
							assert chat != null;
							chat.setText(text);
						} else {
							// その他のタグはとりあえず無視
	                        if (DEBUG_LOGD && !loggedParse) {
	                            XmlLoader.logText(pullParser);
	                        }
						}
					}
					break;
				case XmlPullParser.END_TAG:
					name = pullParser.getName();
					if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logEndTag(pullParser, name);
                        loggedParse = true;
					}
					if ("chat".equals(name)) {
						assert chat != null;
						currentChats.add(chat);
//						if (isForkChat) {
//						    chatsFork.add(chat);
//						} else {
//						    chats.add(chat);
//						}
						chat = null;
                    } else if ("thread".equals(name)) {
                        // 何もせず
                    } else if ("view_counter".equals(name)) {
                        // 何もせず
                    } else if ("leaf".equals(name)) {
                        // 何もせず
					} else {
						// その他のタグはとりあえず無視
                        if (DEBUG_LOGD && !loggedParse) {
                            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_PARSE) {
            Log.d(LOG_TAG, "normal chat log start");
			for (MessageChat chat : chats) {
				Log.d(LOG_TAG, chat.toString());
			}
            Log.d(LOG_TAG, "normal chat log end");

            Log.d(LOG_TAG, "fork chat log start");
            for (MessageChat chat : chatsFork) {
                Log.d(LOG_TAG, chat.toString());
            }
            Log.d(LOG_TAG, "fork chat log end");
		}

		mChats = chats;
		mChatsFork = chatsFork;
	}

	@Override
    public void setVideoLength(String length) {
		if (length != null) {
			Pattern pattern = Pattern.compile("([0-9]+:)?([0-9]+):([0-9]+)");
			Matcher matcher = pattern.matcher(length);
			if (matcher.find()) {
			    String hoursString = matcher.group(1);
                int minutes = Integer.parseInt(matcher.group(2));
                int seconds = Integer.parseInt(matcher.group(3));
				if (hoursString != null) {
					// 1時間以上
					mResSize = 1000;
					mOldResSize = 1000;
					mNicosResSize = 500;
					mVideoMinutes = minutes + Integer.parseInt(hoursString) * 60;
					if (seconds != 0) {
					    ++mVideoMinutes;
					}
					return;
				} else {
					if (minutes < 1) {
						mResSize = 100;
						mOldResSize = 100;
						mNicosResSize = 50;
					} else if (minutes < 5) {
						mResSize = 250;
						if (minutes < 2 || (minutes == 2 && seconds < 30)) {
                            mOldResSize = 100;
						} else {
	                        mOldResSize = 250;
						}
						mNicosResSize = 125;
					} else if (minutes < 10) {
						mResSize = 500;
						mOldResSize = 500;
						mNicosResSize = 250;
					} else {
						mResSize = 1000;
						mOldResSize = 1000;
						mNicosResSize = 500;
					}
					assert mResSize > 0;
					assert mOldResSize > 0;
                    mVideoMinutes = minutes;
                    if (seconds != 0) {
                        ++mVideoMinutes;
                    }
					return;
				}
			}
		}
		// エラー時は最低値
		Log.d(LOG_TAG, Log.buf().append("VideoLength parse failed: length=")
				.append(length).toString());
		mResSize = 100;
        mVideoMinutes = 0;
	}

	@Override
	protected HttpUriRequest createRequest() {
		HttpPost httpRequest = new HttpPost(mUrl);
		httpRequest.addHeader("Cookie", mCookie);
		SharedPreferences sharedPreference = Util.getDefaultSharedPreferencesMultiProcess(mContext);
		String userAgent = sharedPreference.getString(NicoroConfig.USER_AGENT, null);
		if (userAgent != null) {
			httpRequest.setHeader("User-Agent", userAgent);
		}
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, "MessageLoader HTTP request>");
			Util.logHeaders(LOG_TAG, httpRequest.getAllHeaders());
		}

		assert mResSize > 0;

		HttpEntity entity;
		try {
		    String xmlForRequest = createXmlForRequest();
	        if (DEBUG_LOGD) {
	            Log.d(LOG_TAG, xmlForRequest);
	        }
			entity = new StringEntity(xmlForRequest, HTTP.UTF_8);
		} catch (UnsupportedEncodingException e) {
			assert false;
			Log.e(LOG_TAG, e.toString(), e);
			return null;
		}
		httpRequest.setEntity(entity);

		return httpRequest;
	}

	@Override
	protected boolean createDataFromXml(String xmlBody) {
		parse(xmlBody);
		Vector<MessageChat> chats = mChats;
        Vector<MessageChat> chatsFork = mChatsFork;
        mChats = null;
        mChatsFork = null;

		// 重複削除
		HashSet<MessageChat> tempSet = new HashSet<MessageChat>(
		        chats);
		chats.removeAllElements();
		chats.addAll(tempSet);

		tempSet.clear();
		tempSet.addAll(chatsFork);
        chatsFork.removeAllElements();
        chatsFork.addAll(tempSet);

		tempSet = null;

        // vpos順にソート
        Collections.sort(chats, MessageChat.getVposComparator());
        Collections.sort(chatsFork, MessageChat.getVposComparator());

        mChats = chats;
        mChatsFork = chatsFork;

		if (DEBUG_LOGD) {
		    Log.d(LOG_TAG, "Created MessageChat log start");
			for (MessageChat c : chats) {
				Log.d(LOG_TAG, c.toString());
			}
            Log.d(LOG_TAG, "Created MessageChat log end");

            Log.d(LOG_TAG, "Created MessageChat fork log start");
            for (MessageChat c : chatsFork) {
                Log.d(LOG_TAG, c.toString());
            }
            Log.d(LOG_TAG, "Created MessageChat fork log end");
		}

		return true;
	}

	@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 "Comment XML parse failed";
    }

	protected String createXmlForRequest() {
		StringBuilder body = new StringBuilder(256);
		body.append("<packet>");

        body.append("<thread version=\"20090904\" thread=\"")
        .append(mThreadId)
        .append("\" user_id=\"")
        .append(mUserId);
        if (mThreadKey != null) {
            body.append("\" threadkey=\"").append(mThreadKey);
        }
        if (mForce184 != null) {
            body.append("\" force_184=\"").append(mForce184);
        }
        body.append("\" scores=\"1\"/>");

        body.append("<thread_leaves thread=\"")
        .append(mThreadId)
        .append("\" user_id=\"")
        .append(mUserId);
        if (mThreadKey != null) {
            body.append("\" threadkey=\"").append(mThreadKey);
        }
        if (mForce184 != null) {
            body.append("\" force_184=\"").append(mForce184);
        }
        body.append("\" scores=\"1\">0-")
        .append(mVideoMinutes)
        .append(":100,").append(mOldResSize).append("</thread_leaves>");

        if (mNicosId != null) {
            // TODO nicos_idはthreadkeyとforce_184が必要ないかどうか？

            body.append("<thread res_from=\"-")
            .append(mNicosResSize)
            .append("\" version=\"20061206\" thread=\"")
            .append(mNicosId)
            .append("\" user_id=\"")
            .append(mUserId);
            body.append("\" scores=\"1\"/>");
        }

		body.append("<thread res_from=\"-")
		.append(mResSize)
		.append("\" version=\"20061206\" thread=\"")
		.append(mThreadId)
		.append("\" user_id=\"")
		.append(mUserId);
		if (mThreadKey != null) {
			body.append("\" threadkey=\"").append(mThreadKey);
		}
		if (mForce184 != null) {
			body.append("\" force_184=\"").append(mForce184);
		}
		body.append("\" scores=\"1\"/>");

        body.append("<thread res_from=\"-")
        .append(/*mResSize*/ 1000)
        .append("\" version=\"20061206\" thread=\"")
        .append(mThreadId)
        .append("\" user_id=\"")
        .append(mUserId);
        if (mThreadKey != null) {
            body.append("\" threadkey=\"").append(mThreadKey);
        }
        if (mForce184 != null) {
            body.append("\" force_184=\"").append(mForce184);
        }
        body.append("\" fork=\"1\" scores=\"1\"/>");

        body.append("</packet>");
		return body.toString();
	}

	protected MessageChat createMessageChat(boolean isFork, String mail, int vpos, int no,
	        long date, String userId, int score) {
	    if (isFork) {
	        return new MessageChatFork(mail, vpos, no, date, userId, score);
	    } else {
	        return new MessageChat(mail, vpos, no, date, userId, score);
	    }
	}

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