/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.text.ext.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.text.rules.IWhitespaceDetector;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.Token;

/**
 * @author Jeremy
 *
 */
public abstract class TextScanner implements ITokenScanner {
	protected Reader reader;
	protected InputStream stream;
	
	protected int offset;
	protected int length;
	protected int position;
	private StringBuffer text = new StringBuffer();

	public TextScanner(InputStream stream) {
		this.stream = stream;
		this.reader = new BufferedReader(new InputStreamReader(stream));
	}
	
	public TextScanner(Reader reader) {
		this.stream = null;
		this.reader = reader;
	}

	protected void finalize() throws Exception {
		if (text != null) text.setLength(0);
		text = null;
	}

	public void setRange(IDocument document, int offset, int length) {;}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
	 */
	abstract public IToken nextToken() ;

	/*
	 * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
	 */
	public int getTokenOffset() {
		return offset;
	}

	/*
	 * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
	 */
	public int getTokenLength() {
		return length;
	}

	public static class TextToken extends Token {
		private String text ;
		
		public TextToken(String type, String text) {
			super(type);
			this.text = text;
		}
		public String getText() {
			return text;
		}
		public String getType() {
			return (String)getData();
		}
		public boolean equals (Object obj) {
			if (!(obj instanceof TextToken)) return false;
			TextToken tt = (TextToken)obj;
			if (getType() == null) {
				if (tt.getType() != null) return false;
			} else {
				if (!getType().equals(tt.getType())) return false;
			}
			if (getText() == null) {
				if (tt.getText() != null) return false;
			} else {
				if (!getText().equals(tt.getText())) return false;
			}
			return true;
		}
	}
	
	protected IToken getToken( String type ) {
		length = position - offset;

		if ( length == 0 ) return Token.EOF;
		if ( type == null ) return Token.UNDEFINED;

//		if (tokens == null) tokens = new HashMap();
//		IToken token = (IToken) tokens.get( type );
//		if ( token == null ) {
//			token = new TextToken( type, text.substring(offset, offset + length) );
//			tokens.put( type, token );
//		}

//		return token;
		return new TextToken( type, text.substring(offset, offset + length) );
	}


	protected int read() {
		int ch = -1;
		if (position < text.length()) {
			ch = text.charAt(position);
		} else {
			try {
				ch = reader.read();
			} catch (IOException x) {
				return ICharacterScanner.EOF;
			}
			if ( ch == -1 ) return ICharacterScanner.EOF;
			this.text.append((char)ch);
		}
		position++;
		return ch;
	}

	protected void unread() { if (position > 0) --position; }

	static class DecimalTokenDetector implements IWordDetector {
		public boolean isWordStart(char c) {
			return isWordPart(c);
		}

		public boolean isWordPart(char c) {
			switch (c){
				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7': case '8': case '9':
					return true;
			}
			return false;
		}
	}

	static class HexadecimalTokenDetector extends DecimalTokenDetector {
		public boolean isWordStart(char c) {
			return (c == 'x');
		}

		public boolean isWordPart(char c) {
			if (super.isWordPart(c)) return true;
			switch (c){
				case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
				case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
					return true;
			}
			return false;
		}
	}

	public static final IWhitespaceDetector WS_DETECTOR = new WhitespaceDetector();
	public static final IWordDetector NMTOKEN_DETECTOR = new NameTokenDetector();
	public static final IWordDetector DECIMAL_DETECTOR = new DecimalTokenDetector();
	public static final IWordDetector HEXADECIMAL_DETECTOR = new HexadecimalTokenDetector();

	public int skipWhitespaceToken() {
		int ch = read();
		int readsCount = 0;
		while (ch != ICharacterScanner.EOF) {
			if (!WS_DETECTOR.isWhitespace((char)ch)) {
				unread();
				return readsCount;
			}
			readsCount++;
			ch = read();
		}
		return readsCount;
	}

	protected void clearText() {
		if (this.text == null) this.text = new StringBuffer();
		this.text.setLength(0);
		this.offset = 0;
		this.position = 0;
		this.length = 0;
	}
	
	public static int calcTokenLength(char[] tokenChars, IDocument d, int start, int stop) {
		int count = 0;
		while (start + count < stop && count < tokenChars.length) {
			try {
				if (d.getChar(start+count) != tokenChars[count]) return 0;
			} catch (BadLocationException ex) {
				return 0;
			}
			count++;
		}
		return count;
	}

	public static int calcTokenLength(IWordDetector wd, IDocument d, int start, int stop) {
		int count = 0;
		try {
			if (!wd.isWordStart(d.getChar(start)))
				return 0;
			count++;
			while (start + count < stop && wd.isWordPart(d.getChar(start + count))) {
				count++;
			}
		} catch (BadLocationException ex) {
			//ignore
		}
		return count;
	}

	public static int calcLiteralTokenLength(IDocument d, int start, int stop, String escapeChars) {
		int count = 0;
		int pair;
		try {
			pair = d.getChar(start);
		} catch (BadLocationException ex) {
			return 0;
		}
		if (pair != '"' && pair != '\'') return 0;
		count++;
		while ( start + count < stop) {
			try {
                int character = d.getChar(start + count);
                if (escapeChars != null && escapeChars.indexOf(character) != -1) count++; // Skip next char
				else if (character == pair) return (count + 1);
			} catch (BadLocationException ex) {
				return 0;
			}
			count++;
		}
		return 0;
	}
	
	public static String rtrim(String text) {
		try {
			IDocument d = new Document(text);
			int length = d.getLength();
			while (length > 0 && TextScanner.WS_DETECTOR.isWhitespace(d.getChar(length - 1))) {
				length--;
			}
			return d.get(0, length);
		} catch (BadLocationException ex) {
			return text;
		}
	}
	
	static class NameTokenDetector implements IWordDetector {
		/**
		 * @see IWordDetector#isWordPart(char)
		 */
		public boolean isWordPart( char ch ) {
			if ( Character.isUnicodeIdentifierPart(ch) ) return true;

			switch ( ch ) {
				case '.' : case '-' : case '_' : case ':' :
					return true;
			};

			return false;
		}

		/**
		 * @see IWordDetector#isWordStart(char)
		 */
		public boolean isWordStart( char ch ) {
			return isWordPart( ch );
		}
	}
	
	static class WhitespaceDetector implements IWhitespaceDetector {
		/**
		 * @see IWhitespaceDetector#isWhitespace(char)
		 */
		public boolean isWhitespace( char ch ) {
			switch ( ch ) {
				case 0x09:
				case 0x0A:
				case 0x0D:
				case 0x20:
					return true;

				default :
					return false;
			}
		}
	}

}