/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.morilib.dc;

import java.io.IOException;
import java.io.Reader;
import java.util.Map;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/07/14
 */
public final class DcParser<E extends DcVisitor> {

	private static enum S1 {
		INIT, STR1, MINS, NUM1, NUM2, XCL1, XCL2, COMT
	}

	private Map<Character, DcCommand<E>> commands;
	private E env;

	/**
	 * 
	 * @param cmds
	 * @param env
	 */
	public DcParser(Map<Character, DcCommand<E>> cmds, E env) {
		this.commands = cmds;
		this.env = env;
	}

	void debug(int c) {
		if(c < 256 || Character.isLetterOrDigit(c)) {
			System.out.print((char)c);
		} else {
			System.out.print(c);
		}
	}

	//
	void parse(Reader reader, boolean tail) throws IOException {
		StringBuffer b = null;
		DcReader rd;
		S1 stat = S1.INIT;
		int c, cnt = 0;

		rd = new DcReader(new LookaheadReader(reader, 1));
		while(true) {
			c = rd.read();
//			debug(c);
			switch(stat) {
			case INIT:
				if(c < 0) {
					return;
				} else if(c == '[') {
					b = new StringBuffer();
					cnt++;
					stat = S1.STR1;
				} else if(c == '_') {
					b = new StringBuffer();
					b.append('-');
					stat = S1.MINS;
				} else if((c >= '0' && c <= '9') ||
						(c >= 'A' && c <= 'F')) {
					b = new StringBuffer();
					b.append((char)c);
					stat = S1.NUM1;
				} else if(c == '.') {
					b = new StringBuffer();
					b.append('0').append('.');
					stat = S1.NUM2;
				} else if(c == '!') {
					stat = S1.XCL1;
				} else if(c == '#') {
					stat = S1.COMT;
				} else if(Character.isWhitespace(c)) {
					// do nothing
				} else if(commands.containsKey((char)c)) {
					commands.get((char)c).parse(this, env, rd, tail);
				}
				break;
			case STR1:
				if(c < 0) {
					throw new DcSyntaxException();
				} else if(c == ']' && --cnt == 0) {
					env.push(b.toString());
					stat = S1.INIT;
				} else {
					b.append((char)c);
					if(c == '[')  cnt++;
				}
				break;
			case MINS:
				if(c < 0) {
					throw new DcSyntaxException();
				} else if((c >= '0' && c <= '9') ||
						(c >= 'A' && c <= 'F')) {
					b.append((char)c);
					stat = S1.NUM1;
				} else if(c == '.') {
					b.append('0').append('.');
					stat = S1.NUM2;
				} else {
					throw new DcSyntaxException();
				}
				break;
			case NUM1:
				if(c < 0) {
					env.pushNumber(b.toString());
					return;
				} else if((c >= '0' && c <= '9') ||
						(c >= 'A' && c <= 'F')) {
					b.append((char)c);
				} else if(c == '.') {
					b.append('.');
					stat = S1.NUM2;
				} else if(c == '!') {
					env.pushNumber(b.toString());
					stat = S1.XCL1;
				} else if(c == '#') {
					env.pushNumber(b.toString());
					stat = S1.COMT;
				} else if(Character.isWhitespace(c)) {
					env.pushNumber(b.toString());
					stat = S1.INIT;
				} else if(commands.containsKey((char)c)) {
					env.pushNumber(b.toString());
					commands.get((char)c).parse(this, env, rd, tail);
					stat = S1.INIT;
				}
				break;
			case NUM2:
				if(c < 0) {
					env.pushNumber(b.toString());
					return;
				} else if((c >= '0' && c <= '9') ||
						(c >= 'A' && c <= 'F')) {
					b.append((char)c);
				} else if(c == '!') {
					env.pushNumber(b.toString());
					stat = S1.XCL1;
				} else if(c == '#') {
					env.pushNumber(b.toString());
					stat = S1.COMT;
				} else if(Character.isWhitespace(c)) {
					env.pushNumber(b.toString());
					stat = S1.INIT;
				} else if(commands.containsKey((char)c)) {
					env.pushNumber(b.toString());
					commands.get((char)c).parse(this, env, rd, tail);
					stat = S1.INIT;
				}
				break;
			case XCL1:
				if(c < 0) {
					return;
				} else if(c == '<') {
					commands.get('大').parse(this, env, rd, tail);
					stat = S1.INIT;
				} else if(c == '>') {
					commands.get('小').parse(this, env, rd, tail);
					stat = S1.INIT;
				} else if(c == '=') {
					commands.get('不').parse(this, env, rd, tail);
					stat = S1.INIT;
				} else if(c == '\n') {
					stat = S1.INIT;
				} else {
					b = new StringBuffer();
					b.append((char)c);
					stat = S1.XCL2;
				}
				break;
			case XCL2:
				if(c < 0) {
					env.execShell(b.toString());
					break;
				} else if(c == '\n') {
					env.execShell(b.toString());
					stat = S1.INIT;
				}
				break;
			case COMT:
				if(c < 0) {
					return;
				} else if(c == '\n') {
					stat = S1.INIT;
				}
				break;
			}
		}
	}

	/**
	 * 
	 * @param reader
	 * @throws IOException
	 */
	public void parse(Reader reader) throws IOException {
		try {
			parse(reader, true);
		} catch(DcQuitSignalException e) {
			// do nothing
		}
	}

}
