package jp.sourceforge.nicoro;

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

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ConcurrentLinkedQueue;

import jp.gr.java_conf.shiseissi.commonlib.FileUtil;

import org.apache.http.protocol.HTTP;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.Context;

public class LiveMessageLoader extends XmlLoader implements LiveMessageLoaderInterface {
	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 int DEFAULT_TIMEOUT_MS = 180 * 1000;

	private String mUrl;
	private String mPort;
	private String mThreadId;
	private String mCookie;
	private String mUserId;
	private Context mContext;

	private Socket mSocket;

	private ConcurrentLinkedQueue<MessageChat> mChats =
		new ConcurrentLinkedQueue<MessageChat>();

	private LiveMessageLoaderInterface.EventListener mEventListener = null;

	public LiveMessageLoader(String url, String port,
			String threadId, String cookie, String userId,
			Context context) {
		mUrl = url;
		mPort = port;
		mThreadId = threadId;
		mCookie = cookie;
		mUserId = userId;
		mContext = context;
	}

	@Override
	public void run() {
        mNetworkIORetryCount = NETWORK_IO_RETRY_COUNT;

        while (!mIsFinish) {
            if (!loadMain()) {
                break;
            }
        }
	}

    /**
     * ロード処理本体
     * @return trueならリトライの可能性、falseなら完全に終了
     */
	private boolean loadMain() {
		InputStream in = null;
		OutputStream out = null;

		try {
			mSocket = new Socket(mUrl, Integer.parseInt(mPort));
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, mSocket.toString());
			}
			mSocket.setSoTimeout(DEFAULT_TIMEOUT_MS);
			in = mSocket.getInputStream();
			out = mSocket.getOutputStream();
			if (!mSocket.isConnected()) {
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, "Socket connect failed");
				}
	            --mNetworkIORetryCount;
	            if (mNetworkIORetryCount >= 0) {
	                // ネットワークエラーはひとまずリトライ
	                waitForNetworkError();
	                return true;
	            } else {
	                return false;
	            }
			}

            // ネットワーク接続成功したらカウントリセット
            mNetworkIORetryCount = NETWORK_IO_RETRY_COUNT;

			dispatchOnConnected();
			String request = createXmlForRequest();
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf().append("request: ")
						.append(request).toString());
			}
			out.write(request.getBytes(HTTP.UTF_8));
			out.write('\0');
			out.flush();
			boolean result = readAndCreateData(in);

			if (result) {
				dispatchOnFinished();
			} else {
				dispatchOnOccurredError(getXmlParseErrorString());
			}
		} catch (NumberFormatException e) {
			String errorMessage = e.toString();
			Log.d(LOG_TAG, errorMessage, e);
			dispatchOnOccurredError(errorMessage);
		} catch (UnknownHostException e) {
			String errorMessage = e.toString();
			Log.d(LOG_TAG, errorMessage, e);
            --mNetworkIORetryCount;
            if (mNetworkIORetryCount >= 0) {
                // ネットワークエラーはひとまずリトライ
                waitForNetworkError();
                return true;
            } else {
                dispatchOnOccurredError(errorMessage);
            }
		} catch (IOException e) {
			String errorMessage = e.toString();
			Log.d(LOG_TAG, errorMessage, e);
            --mNetworkIORetryCount;
            if (mNetworkIORetryCount >= 0) {
                // ネットワークエラーはひとまずリトライ
                waitForNetworkError();
                return true;
            } else {
    			if (mSocket == null || !mSocket.isOutputShutdown()) {
    				dispatchOnOccurredError(errorMessage);
    			}
            }
		} finally {
			if (in != null) {
			    FileUtil.closeIgnoreException(in);
			}
			if (out != null) {
			    FileUtil.closeIgnoreException(out);
			}
			if (mSocket != null) {
				try {
					mSocket.shutdownInput();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
				try {
					mSocket.shutdownOutput();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
				try {
					mSocket.close();
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("Close Socket: ")
                                .append(mSocket.toString()).toString());
                    }
				} catch (IOException e) {
					Log.d(LOG_TAG, e.toString(), e);
				}
				mSocket = null;
			}
		}
		return false;
	}

	@Override
	protected boolean createDataFromXml(String xmlBody) {
		// TODO 自動生成されたメソッド・スタブ
		return false;
	}

//	@Override
//	protected HttpUriRequest createRequest() {
//		URI uri = null;
//		try {
//			uri = new URI("http", null, mUrl, Integer.parseInt(mPort), null, null, null);
//		} catch (NumberFormatException e1) {
//			Log.d(LOG_TAG, e1.getMessage(), e1);
//		} catch (URISyntaxException e1) {
//			Log.d(LOG_TAG, e1.getMessage(), e1);
//		}
//		if (uri == null) {
//			return null;
//		}
//		if (DEBUG_LOGD) {
//			Log.d(LOG_TAG, uri.toString());
//		}
//
//		HttpPost httpRequest = new HttpPost(uri);
//		httpRequest.addHeader("Cookie", mCookie);
//		SharedPreferences sharedPreference = PreferenceManager.getDefaultSharedPreferences(mContext);
//		String userAgent = sharedPreference.getString(NicoroConfig.USER_AGENT, null);
//		if (userAgent != null) {
//			httpRequest.setHeader("User-Agent", userAgent);
//		}
//		if (DEBUG_LOGD) {
//			Log.d(LOG_TAG, "LiveMessageLoader HTTP request>");
//			Util.logHeaders(LOG_TAG, httpRequest.getAllHeaders());
//		}
//
//		HttpEntity entity;
//		try {
//			entity = new StringEntity(createXmlForRequest(), HTTP.UTF_8);
//		} catch (UnsupportedEncodingException e) {
//			assert false;
//			Log.d(LOG_TAG, e.getMessage(), e);
//			return null;
//		}
//		httpRequest.setEntity(entity);
//
//		return httpRequest;
//	}

	@Override
	protected void dispatchOnFinished() {
		if (mEventListener != null) {
			mEventListener.onFinished(this);
		}
	}

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

	private void dispatchOnConnected() {
        if (mEventListener != null) {
            mEventListener.onConnected(this);
        }
	}

	@Override
	protected boolean readAndCreateData(InputStream inDownload)
			throws IOException {
		InputStreamReader inReader = null;
		try {
			inReader = new InputStreamReader(inDownload, HTTP.UTF_8);
//			inReader = new InputStreamReader(inDownload, HTTP.UTF_8) {
//				@Override
//				public int read(char[] buf, int offset, int length)
//				throws IOException {
//					int ret = super.read(buf, offset, length);
//					if (DEBUG_LOGD) {
//						if (ret >= 0) {
//							Log.d(LOG_TAG, Log.buf().append(buf, 0, ret)
//									.toString());
//						}
//					}
//					return ret;
//				}
//				@Override
//				public int read() throws IOException {
//					int ret = super.read();
//					if (DEBUG_LOGD) {
//						if (ret >= 0) {
//							Log.d(LOG_TAG, Log.buf().append(ret)
//									.toString());
//						}
//					}
//					return ret;
//				}
//			};
			XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
			factory.setNamespaceAware(true);
			XmlPullParser pullParser = factory.newPullParser();
			pullParser.setInput(inReader);

			String name = null;
			String text = null;
			MessageChat chat = null;

			final ConcurrentLinkedQueue<MessageChat> chats = mChats;

			int next = pullParser.getEventType();
			while (!mIsFinish &&
					next != XmlPullParser.END_DOCUMENT) {
				if (DEBUG_LOGV_PARSE) {
					Log.d(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 ("chat".equals(name)) {
						chat = createMessageChat(
                                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"));
					} else {
						// その他のタグはとりあえず無視
					}
					break;
				case XmlPullParser.TEXT:
					text = pullParser.getText();
                    if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logText(pullParser);
                    }
					if (name != null) {
						if ("chat".equals(name)) {
							assert chat != null;
							chat.setText(text);
						} else {
							// その他のタグはとりあえず無視
						}
					}
					break;
				case XmlPullParser.END_TAG:
					name = pullParser.getName();
					if (DEBUG_LOGD_PARSE) {
                        XmlLoader.logEndTag(pullParser, name);
					}
					if ("chat".equals(name)) {
						assert chat != null;
						chats.add(chat);
						chat = null;
						if (mEventListener != null) {
							mEventListener.onAddedMessage(this);
						}
					} else {
						// その他のタグはとりあえず無視
					}
					name = null;
					break;
				default:
					break;
				}
				next = pullParser.next();
			}

			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, "readAndCreateData end");
			}

			return true;
		} catch (XmlPullParserException e) {
			Log.d(LOG_TAG, e.toString(), e);
			return false;
		} finally {
			if (inReader != null) {
			    FileUtil.closeIgnoreException(inReader);
			}
		}
	}

	@Override
	protected void shutdownNetwork() {
		Socket socket = mSocket;
		if (socket != null) {
			try {
				socket.shutdownInput();
			} catch (IOException e) {
				Log.d(LOG_TAG, e.toString(), e);
			}
			try {
				socket.shutdownOutput();
			} catch (IOException e) {
				Log.d(LOG_TAG, e.toString(), e);
			}
		}
	}

	private String createXmlForRequest() {
		StringBuilder body = new StringBuilder(256)
			.append("<thread")
			.append(" thread=\"").append(mThreadId).append('"')
//			.append(" res_from=\"-").append(50).append('"')
			.append(" res_from=\"-").append(1).append('"')
			.append(" version=\"20061206\"")
//			.append(" user_id=\"").append(mUserId).append('"')
//			.append(" />");
			.append("/>");
		return body.toString();
	}

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

	@Override
    public MessageChat poll() {
		return mChats.poll();
	}

	private MessageChat createMessageChat(String mail, int vpos, int no,
            long date, String userId) {
        return new MessageChat(mail, vpos, no, date, userId, 0);
	}
	@Override
    public void setEventListener(LiveMessageLoaderInterface.EventListener eventListener) {
		mEventListener = eventListener;
	}

    private String getXmlParseErrorString() {
        return "Comment XML parse failed";
    }
}
