package jp.kirikiri.tjs2;

import java.util.ArrayList;

public class StringStream {
	private static final String TAG = "StringStream";
	private static final boolean LOGD = false;

	private static final int BUFFER_CAPACITY = 1024;

	private final String mString;
	private final char[] mText;
	private int mOffset;
	private boolean mEOF;
	private int mStringStatus;
	private ArrayList<Integer> mLineVector;
	private ArrayList<Integer> mLineLengthVector;
	private int mLineOffset;

	static private final int CARRIAGE_RETURN = 13;
	static private final int LINE_FEED = 10;
	static private final int TAB = 0x09;
	static private final int SPACE = 0x20;

	static public final int NOT_COMMENT = 0;
	static public final int UNCLOSED_COMMENT = -1;
	static public final int CONTINUE = 1;
	static public final int ENDED = 2;

	static public final int NONE = 0;
	static public final int DELIMITER = 1;
	static public final int AMPERSAND = 2;
	static public final int DOLLAR = 3;

	public StringStream( final String str ) {
		mString = str;
		mText = new char[mString.length()];
		str.getChars( 0, mString.length(), mText, 0 );
		//mText = str.getBytes();
		mOffset = 0;
		mEOF = false;
		mLineOffset = 0;
	}
	public final void ungetC() {
		if( mOffset > 0 ) mOffset--;
	}
	public final int getC() {
		int retval = -1;
		if( mOffset < mText.length ) {
			retval = mText[mOffset];
			mOffset++;
		} else {
			mEOF = true;
		}
		return retval;
	}
	public final void incOffset() {
		if( mOffset < mText.length ) {
			mOffset++;
		} else {
			mEOF = true;
		}
	}
	public final int peekC() {
		int retval = -1;
		if( mOffset < mText.length ) {
			retval = mText[mOffset];
		}
		return retval;
	}
	public final int peekC(int offset) {
		int retval = -1;
		if( (mOffset+offset) < mText.length ) {
			retval = mText[mOffset+offset];
		}
		return retval;
	}
	// 改行コードを無視して取得する
	public final int next() {
		int retval = -1;
		if( mOffset < mText.length ) {
			retval = mText[mOffset];
			mOffset++;
			while( retval == CARRIAGE_RETURN || retval == LINE_FEED ) {
				if( mOffset < mText.length ) {
					retval = mText[mOffset];
					mOffset++;
				} else {
					retval = -1;
					mEOF = true;
					break;
				}
			}
			return retval;
		} else {
			mEOF = true;
		}
		return retval;
	}
	public final boolean isEOF() { return mEOF; }

	public final void skipSpace() {
		if( mOffset < mText.length ) {
			int c = mText[mOffset];
			mOffset++;
			boolean skipToLast = false;
			while( c == CARRIAGE_RETURN || c == LINE_FEED || c == TAB || c == SPACE ) {
				if( mOffset < mText.length ) {
					c = mText[mOffset];
					mOffset++;
				} else {
					skipToLast = true;
					break;
				}
			}
			if( mOffset > 0 && skipToLast == false ) mOffset--;
		}
		if( mOffset >= mText.length ) mEOF = true;
	}
	public final void skipReturn() {
		if( mOffset < mText.length ) {
			int c = mText[mOffset];
			mOffset++;
			boolean skipToLast = false;
			while( c == CARRIAGE_RETURN || c == LINE_FEED ) {
				if( mOffset < mText.length ) {
					c = mText[mOffset];
					mOffset++;
				} else {
					skipToLast = true;
					break;
				}
			}
			if( mOffset > 0 && skipToLast == false ) mOffset--;
		}
		if( mOffset >= mText.length ) mEOF = true;
	}
	public final int skipComment() throws CompileException {
		int offset = mOffset;
		if( (offset+1) < mText.length ) {
			if( mText[offset] != '/' ) return NOT_COMMENT;
			if( mText[offset+1] == '/' ) {
				// ラインコメント
				mOffset += 2;
				int c = mText[mOffset];
				mOffset++;
				while( c != CARRIAGE_RETURN && c != LINE_FEED ) {
					if( mOffset < mText.length ) {
						c = mText[mOffset];
						mOffset++;
					} else {
						break;
					}
				}
				if( mOffset < mText.length ) {
					if( c == CARRIAGE_RETURN ) {
						if( mText[mOffset] == LINE_FEED ) {
							mOffset++;
						}
					}
				} else {
					mEOF = true;
					return ENDED;
				}
				skipSpace();
				if( mOffset >= mText.length ) {
					mEOF = true;
					return ENDED;
				}
				return CONTINUE;
			} else if( mText[offset+1] == '*' ) {
				// ブロックコメント
				mOffset += 2;
				int level = 0;
				while(true) {
					if( (mOffset+1) < mText.length ) {
						if( mText[mOffset] == '/' && mText[mOffset+1] == '*' ) {
							// コメントのネスト
							level++;
						} else if( mText[mOffset] == '*' && mText[mOffset+1] == '/' ) {
							if( level == 0 ) {
								mOffset += 2;
								break;
							}
							level--;
						}
						mOffset++;
					} else {
						throw new CompileException(Error.UnclosedComment);
					}
				}
				if( mOffset >= mText.length ) {
					mEOF = true;
					return ENDED;
				}
				skipSpace();
				if( mOffset >= mText.length ) {
					mEOF = true;
					return ENDED;
				}
				return CONTINUE;
			}
		}
		return NOT_COMMENT;
	}
	public final int getOffset() { return mOffset; }
	public final void setOffset( int offset ) {
		if( offset < mText.length ) {
			mOffset = offset;
		} else {
			mOffset = mText.length;
			mEOF = true;
		}
	}
	public final boolean equalString( final String value ) {
		final int count = value.length();
		if( (mText.length - mOffset) >= count ) {
			final int offset = mOffset;
			for( int i = 0; i < count; i++ ) {
				if( mText[offset+i] != value.charAt(i) ) {
					return false;
				}
			}
			mOffset += count;
			return true;
		} else {
			return false;
		}
	}
	static final public int unescapeBackSlash( int ch ) {
		switch(ch) {
		case 'a': return 0x07;
		case 'b': return 0x08;
		case 'f': return 0x0c;
		case 'n': return 0x0a;
		case 'r': return 0x0d;
		case 't': return 0x09;
		case 'v': return 0x0b;
		default: return ch;
		}
	}
	public final int countOctetTail() {
		if( mOffset < mText.length ) {
			int offset = mOffset;
			while( (offset+1) < mText.length ) {
				if( mText[offset] == '%' && mText[offset+1] == '>' )
					break;
				offset++;
			}
			return offset - mOffset;
		} else {
			return 0;
		}
	}

	public final String readString( int delimiter, boolean embexpmode ) throws CompileException {
		if( mOffset < mText.length ) {
			StringBuilder str = new StringBuilder(BUFFER_CAPACITY);
			mStringStatus = NONE;
			try {
				while( mOffset < mText.length ) {
					int c = mText[mOffset];
					mOffset++;
					while( c == CARRIAGE_RETURN || c == LINE_FEED ) { c = mText[mOffset]; mOffset++; }
					if( c == '\\' ) {
						// escape
						c = mText[mOffset];
						mOffset++;
						while( c == CARRIAGE_RETURN || c == LINE_FEED ) { c = mText[mOffset]; mOffset++; }
						if( c == 'x' || c == 'X' ) {	// hex
//							int num = 0;
							int code = 0;
							int count = 0;
							while( count < 4 ) {
								c = mText[mOffset];
								mOffset++;
								while( c == CARRIAGE_RETURN || c == LINE_FEED ) { c = mText[mOffset]; mOffset++; }

								int n = -1;
								if(c >= '0' && c <= '9') n = c - '0';
								else if(c >= 'a' && c <= 'f') n = c - 'a' + 10;
								else if(c >= 'A' && c <= 'F') n = c - 'A' + 10;
								if( n == -1 ) {
									mOffset--;
									break;
								}
								code <<= 4; // *16
								code += n;
								count++;
							}
							str.append( (char)code );
						} else if( c == '0' ) {	// octal
//							int num;
							int code = 0;
							while( true ) {
								c = mText[mOffset];
								mOffset++;
								while( c == CARRIAGE_RETURN || c == LINE_FEED ) { c = mText[mOffset]; mOffset++; }

								int n = -1;
								if( c >= '0' && c <= '7' ) n = c - '0';
								if( n == -1 ) {
									mOffset--;
									break;
								}
								code <<= 3; // * 8
								code += n;
							}
							str.append( (char)code );
						} else {
							str.append( (char)unescapeBackSlash(c) );
						}
					} else if( c == delimiter ) {
						if( mOffset >= mText.length ) {
							mStringStatus = DELIMITER;
							break;
						}
						int offset = mOffset;
						skipSpace();
						c = mText[mOffset];
						mOffset++;
						while( c == CARRIAGE_RETURN || c == LINE_FEED ) { c = mText[mOffset]; mOffset++; }
						if( c == delimiter ) {
							// sequence of 'A' 'B' will be combined as 'AB'
						} else {
							mStringStatus = DELIMITER;
							mOffset = offset;
							break;
						}
					} else if( embexpmode == true && c == '&' ) {
						if( mOffset >= mText.length ) break;
						mStringStatus = AMPERSAND;
						break;
					} else if( embexpmode == true && c == '$' ) {
						// '{' must be placed immediately after '$'
						int offset = mOffset;
						c = mText[mOffset];
						mOffset++;
						while( c == CARRIAGE_RETURN || c == LINE_FEED ) { c = mText[mOffset]; mOffset++; }
						if( mOffset >= mText.length ) break;
						if( c == '{' ) {
							if( mOffset >= mText.length ) break;
							mStringStatus = DOLLAR;
							break;
						} else {
							mOffset = offset;
							str.append( (char)c );
						}
					} else {
						str.append( (char)c );
					}
				}
			} catch(ArrayIndexOutOfBoundsException e) {
				mEOF = true;
				if( mStringStatus == NONE ) {
					throw new CompileException(Error.StringParseError);
				}
			}
			if( mStringStatus == NONE ) {
				throw new CompileException(Error.StringParseError);
			}
			return str.toString();
		}
		return null;
	}
	public final int getStringStatus() { return mStringStatus; }
	public final String substring( int beginIndex, int endIndex) {
		return mString.substring( beginIndex, endIndex );
	}
	private final void generateLineVector() {
		mLineVector = new ArrayList<Integer>();
		mLineLengthVector = new ArrayList<Integer>();
		int count = mText.length;
		int lastCR = 0;
		int i;
		for( i= 0; i < count; i++ ) {
			int c = mText[i];
			if( c == CARRIAGE_RETURN || c == LINE_FEED ) {
				mLineVector.add( Integer.valueOf( lastCR ) );
				mLineLengthVector.add( Integer.valueOf( i-lastCR ) );
				lastCR = i+1;
				if( (i+1) < count ) {
					c = mText[i+1];
					if( c == CARRIAGE_RETURN || c == LINE_FEED ) {
						i++;
						lastCR = i+1;
					}
				}
			}
		}
		if( i != lastCR ) {
			mLineVector.add( Integer.valueOf( lastCR ) );
			mLineLengthVector.add( Integer.valueOf( i-lastCR ) );
		}
	}
	public final int getSrcPosToLine( int pos ) {
		if( mLineVector == null ) {
			generateLineVector();
		}
		// 2分法によって位置を求める
		int s = 0;
		int e = mLineVector.size();
		while( true ) {
			if( (e-s) <= 1 ) return s + mLineOffset;
			int m = s + (e-s)/2;
			if( mLineVector.get(m) > pos )
				e = m;
			else
				s = m;
		}
	}
	public final int getLineToSrcPos( int pos ) {
		if( mLineVector == null ) {
			generateLineVector();
		}
		return mLineVector.get(pos);
	}
	public final String getLine( int line ) {
		if( mLineVector == null ) {
			generateLineVector();
		}
		int start = mLineVector.get(line);
		int length = mLineLengthVector.get(line);
		return mString.substring(start, start+length );
	}
	public final int getMaxLine() {
		if( mLineVector == null ) {
			generateLineVector();
		}
		return mLineVector.size();
	}
}
