/*******************************************************************************
 * Copyright (c) 2015 Bruno Medeiros and other Contributors.
 * All rights reserved. This program and the accompanying materials
 * are 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:
 *     Bruno Medeiros - initial API and implementation
 *******************************************************************************/
package melnorme.lang.tooling.parser.lexer;

import melnorme.lang.utils.parse.ICharacterReader;
import melnorme.lang.utils.parse.LexingUtils;

public class CharacterLexingRule extends LexingUtils implements IPredicateLexingRule {
	
	@Override
	public boolean doEvaluate(ICharacterReader reader) {
		if(!consumeStart(reader)) {
			return false;
		}
		if(!consumeBody(reader)) {
			return false;
		}
		if(!consumeEnd(reader)) {
			return false;
		}
		return true;
	}
	
	protected boolean consumeStart(ICharacterReader reader) {
		return reader.tryConsume('\'');
	}
	
	protected boolean consumeBody(ICharacterReader reader) {
		if(reader.tryConsume((char) 0x5c)) {
			return reader.tryConsume('\'') || consumeCommonEscape(reader) || consumeUnicodeEscapeSequence(reader);
		};
		
		if(reader.lookaheadIsEOS() || reader.lookahead() == '\'') {
			return false;
		}
		if(reader.lookahead() == 0) {
			return false; // Should the null character really not be allowed?
		}
		return reader.consumeAny();
	}
	
	protected boolean consumeEnd(ICharacterReader reader) {
		return reader.tryConsume('\'');
	}
	
	protected static char[] COMMON_ESCAPES = { 0x5c, 'n' , 'r' , 't' , '0' }; 
	
	protected boolean consumeCommonEscape(ICharacterReader reader) {
		for(char ch : COMMON_ESCAPES) {
			if(reader.tryConsume(ch)) {
				return true;
			}
		}
		
		if(consumeHexEscapeSequence(reader)) {
			return true;
		}
		return false;
	}
	
	protected boolean consumeHexEscapeSequence(ICharacterReader reader) {
		if(reader.lookahead(0) == 'x' 
				&& isHexDigit(reader.lookahead(1)) && isHexDigit(reader.lookahead(2))) {
			reader.consume(3);
			return true;
		}
		return false;
	}
	
	protected boolean consumeUnicodeEscapeSequence(ICharacterReader reader) {
		if(reader.tryConsume('u')) {
			
			while(true) {
				int la = reader.lookahead();
				
				// This is not accurate to any spec, but is good enough.
				if(la == '{' || la == '}' || isHexDigit(la)) {
					reader.consume();
				} else {
					break;
				}
			}
			
			return true;
		}
		return false;
	}
	
	public static boolean isHexDigit(int ch) {
		if(ch >= '0' && ch <= '9') {
			return true;
		}
		if((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
			return true;
		}
		return false;
	}
	
}