package jp.sourceforge.nicoro;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;

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

import org.apache.http.protocol.HTTP;

public abstract class XmlLoader implements Runnable {
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;

	protected Thread mThread = new Thread(this);
	protected boolean mIsStarted = false;
	protected volatile boolean mIsFinish = false;
	
	public void startLoad() {
		if (mIsStarted) {
			Log.d(LOG_TAG, "it has started");
			return;
		}
		mIsStarted = true;
		mIsFinish = false;
		
		mThread.start();
	}

	public void finish() {
		mIsFinish = true;
		try {
			mThread.join(2000L);
		} catch (InterruptedException e) {
			Log.d(LOG_TAG, e.getMessage(), e);
		}
		mIsStarted = false;
	}
	
	protected abstract boolean createDataFromXml(String xmlBody);
	protected abstract void dispatchOnFinished();
	protected abstract void dispatchOnOccurredError(String errorMessage);
	
	protected abstract boolean readAndCreateData(InputStream inDownload)
	throws IOException;
	

	protected String readEntityAndDecode(InputStream inDownload)
	throws IOException {
		String xmlBody = readEntityAndDecodeDefault(inDownload);
//		String xmlBody = readEntityAndDecodeSpecial(inDownload);
		return xmlBody;
	}
	
	private String readEntityAndDecodeDefault(InputStream inDownload)
	throws IOException {
		InputStreamReader inReader = null;
		try {
			inReader = new InputStreamReader(inDownload, HTTP.UTF_8);
			
			StringBuilder stringBody = new StringBuilder(1024 * 4);
			char[] buffer = new char[1024*4];
			while (!mIsFinish) {
				int read = inReader.read(buffer, 0, buffer.length);
				if (read < 0) {
					break;
				}
				stringBody.append(buffer, 0, read);
			}
			
			String xmlBody = stringBody.toString();
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf().append("read xml=").append(xmlBody).toString());
			}
			return xmlBody;
		} finally {
			if (inReader != null) {
				try {
					inReader.close();
				} catch (IOException e) {
					Log.d(LOG_TAG, e.getMessage(), e);
				}
			}
		}
	}

	private String readEntityAndDecodeSpecial(InputStream inDownload)
	throws IOException {
		// 文字化け対策→効果無し？
		final CharsetDecoder decoder = Charset.forName(HTTP.UTF_8).newDecoder()
		.onMalformedInput(CodingErrorAction.REPLACE)
		.onUnmappableCharacter(CodingErrorAction.REPLACE);
//		.onMalformedInput(CodingErrorAction.REPORT)
//		.onUnmappableCharacter(CodingErrorAction.REPORT);
		
		final StringBuilder stringBody = new StringBuilder(1024 * 4);
		final CharBuffer charBuffer = CharBuffer.allocate(1024 * 4);
		final ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 8);
		final char[] charBufferArray = charBuffer.array();
		final byte[] byteBufferArray = byteBuffer.array();
		int lastByteOffset = 0;
		while (!mIsFinish) {
			final int readByte = inDownload.read(
					byteBufferArray,
					lastByteOffset,
					byteBufferArray.length - lastByteOffset);
			if (readByte < 0) {
				byteBuffer.position(0).limit(lastByteOffset);
				charBuffer.position(0);
				final CoderResult result = decoder.decode(byteBuffer, charBuffer, true);
				stringBody.append(charBufferArray, 0, charBuffer.position());
				break;
			}
			
			final int byteBufferSize = lastByteOffset + readByte; 
			
			int end;
			for (end = byteBufferSize - 1; end > 0; --end) {
				final byte b = byteBufferArray[end];
				if (b < 0x80 || b > 0xbf) {
					break;
				}
			}
			
			byteBuffer.position(0).limit(end);
			charBuffer.position(0);
			final CoderResult result = decoder.decode(byteBuffer, charBuffer, false);
			stringBody.append(charBufferArray, 0, charBuffer.position());
			final int byteBufferPositionAfterDecode = byteBuffer.position();
			if (byteBufferPositionAfterDecode != byteBufferSize) {
				lastByteOffset = byteBufferSize - byteBufferPositionAfterDecode;
				assert lastByteOffset <= byteBufferPositionAfterDecode : "lastByteOffset=" + lastByteOffset + " byteBufferPositionAfterDecode=" + byteBufferPositionAfterDecode;
				System.arraycopy(byteBufferArray, byteBufferPositionAfterDecode,
						byteBufferArray, 0,
						lastByteOffset);
			}
		}
		
		String xmlBody = stringBody.toString();
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, Log.buf().append("read xml=").append(xmlBody).toString());
		}
		return xmlBody;
	}
}
