/*
 * 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.c.pre.directive;

import java.util.ArrayList;
import java.util.List;

import net.morilib.c.pre.CpreMacros;
import net.morilib.c.pre.CpreSyntaxException;
import net.morilib.c.pre.CpreUtils;

public class CpreDefine implements CpreDirective {

	static final String ERR001 =
			"macro identifier must be alphabets and numbers";
	static final String ERR002 =
			"macro argument must be alphabets and numbers";

	private static enum S0 {
		INIT, INIT_P, INIT_WS,
		ARGS, ARGS_2, ARGS_P, ARGS_P_WS, ARGS_WS,
		BODY
	}

	public CpreIfState execute(CpreMacros macros, String line,
			int ln, CpreIfState st) {
		List<String> l = null;
		StringBuffer b = new StringBuffer();
		String nm = null, s = line;
		S0 stat = S0.INIT;
		int c;

		if(!st.isProcessing())  return CpreIfState.STAY;
		for(int i = 0; i < s.length(); i += c > 0xffff ? 2 : 1) {
			c = line.codePointAt(i);
			switch(stat) {
			case INIT:
				if(!CpreUtils.isCMacroIdentifierStart(c)) {
					throw new CpreSyntaxException(
							ln, "macro identifier required");
				}
				b.appendCodePoint(c);
				stat = S0.INIT_P;
				break;
			case INIT_P:
				if(CpreUtils.isCMacroIdentifierPart(c)) {
					b.appendCodePoint(c);
				} else if(Character.isWhitespace(c)) {
					stat = S0.INIT_WS;
					nm = b.toString();
					b = new StringBuffer();
				} else if(c == '(') {
					l = new ArrayList<String>();
					nm = b.toString();
					b = new StringBuffer();
					stat = S0.ARGS;
				} else {
					throw new CpreSyntaxException(ln, ERR001);
				}
				break;
			case INIT_WS:
				if(c == '(') {
					l = new ArrayList<String>();
					nm = b.toString();
					b = new StringBuffer();
					stat = S0.ARGS;
				} else if(!Character.isWhitespace(c)) {
					stat = S0.BODY;
					b.appendCodePoint(c);
				}
				break;
			case ARGS:
				if(c == ')') {
					stat = S0.ARGS_WS;
				} else if(CpreUtils.isCMacroIdentifierStart(c)) {
					b.appendCodePoint(c);
					stat = S0.ARGS_P;
				} else if(!Character.isWhitespace(c)) {
					throw new CpreSyntaxException(ln, ERR002);
				}
				break;
			case ARGS_2:
				if(CpreUtils.isCMacroIdentifierStart(c)) {
					b.appendCodePoint(c);
					stat = S0.ARGS_P;
				} else if(!Character.isWhitespace(c)) {
					throw new CpreSyntaxException(ln, ERR002);
				}
				break;
			case ARGS_P:
				if(c == ')') {
					stat = S0.ARGS_WS;
					l.add(b.toString());  b = new StringBuffer();
				} else if(c == ',') {
					stat = S0.ARGS_2;
					l.add(b.toString());  b = new StringBuffer();
				} else if(CpreUtils.isCMacroIdentifierPart(c)) {
					b.appendCodePoint(c);
				} else if(Character.isWhitespace(c)) {
					stat = S0.ARGS_P_WS;
				} else {
					throw new CpreSyntaxException(ln, ERR002);
				}
				break;
			case ARGS_P_WS:
				if(c == ')') {
					stat = S0.ARGS_WS;
					l.add(b.toString());  b = new StringBuffer();
				} else if(c == ',') {
					stat = S0.ARGS_2;
					l.add(b.toString());  b = new StringBuffer();
				} else if(!Character.isWhitespace(c)) {
					throw new CpreSyntaxException(ln, ERR002);
				}
				break;
			case ARGS_WS:
				if(!Character.isWhitespace(c)) {
					stat = S0.BODY;
					b.appendCodePoint(c);
				}
				break;
			case BODY:
				b.appendCodePoint(c);
				break;
			}
		}
		macros.define(nm, b.toString(), l);
		return CpreIfState.STAY;
	}

}
