/*
 * 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.nina;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.morilib.nina.lint.LintCells;

/**
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/10/15
 */
public class Quadro implements NinaEvent {

	/**
	 * 
	 *
	 *
	 * @author MORIGUCHI, Yuichiro 2013/10/02
	 */
	public static enum Direction {
		WEST, EAST, NORTH, SOUTH, CR
	}

	//
	private static final String LOPT = "targetLanguage";
	private static final String EOG = "-- end of grammar --";
	private static final String ALLBYTE1 = "#notfullwidth";
	private static final Pattern PTN1 = Pattern.compile(
			"--[ \t]+(.+)[ \t]+--");
	private static final Pattern PTN2 = Pattern.compile("%.*\\{");
	private static final Pattern PTN3 = Pattern.compile(
			"grammar:[ \t]*--[ \t]+(.+)[ \t]+--");
	private static final Pattern PTN4 = Pattern.compile(
			"import:[ \t]*(.+)[ \t]+as[ \t]+(.+)[ \t]*");
	private static final Pattern PTN5 = Pattern.compile(
			"import-grammar:[ \t]*(.+)[ \t]+as[ \t]+(.+)[ \t]*");
	private static final Pattern CDLM = Pattern.compile("-+");

	//
	private static boolean iswarning = false;
	private static String tabs = "    ";
	public static PrintStream errst = null;

	//
	static final int EQ_TO_LEFT = -1;
	static final int BLANKX = -2;
	static final int BLANKY = -3;
	static final int E2 = -100;
	static final int W2 = -101;
	static final int N2 = -102;
	static final int S2 = -103;
	static final int E3 = -200;
	static final int W3 = -201;
	static final int N3 = -202;
	static final int S3 = -203;
	static final int DONE = -300;
	static final int ENTRY = -301;
	static final int WARP2 = -400;
	static final int WARP3 = -402;
	static final int FRAME_N = -501;
	static final int FRAME_A = -502;
	static final int BRANCH   = -5000000;
	static final int BRANCH_N = 1000;
	static final int BRANCH_E =  100;
	static final int BRANCH_S =   10;
	static final int BRANCH_W =    1;

	//
	Map<String, String> aliases = new HashMap<String, String>();
	Map<String, String> options;
	Map<String, StringBuffer> buffers =
			new HashMap<String, StringBuffer>();
	List<String> imports = new ArrayList<String>();
	String fragment, type, lexer, ltype;

	//
	private int[][] quadro;
	private Object[][] scratch, scratch2;
	private String[][] labels, ltypes;
	private String[][] visited;
	private String[][] visiting;
	private int curx = 0, cury = 0;
	private Object edge;
	private Map<String, Quadro> subquadro;
	private Map<String, LRObject> lrobjects;
	private LintCells lint;
	private List<String[]> ids;
	List<String[]> requires = new ArrayList<String[]>();
	Map<String, String> labelalias = new HashMap<String, String>();
	Map<String, String> defines = new HashMap<String, String>();
	Map<String, Integer> consts = new HashMap<String, Integer>();
	StringWriter license = new StringWriter();
	String rootResource;
	String rootPackage;
	String name;
	String valtype;
	String description;

	//
	private NinaLanguageOption langopt = null;

	//
	Quadro(String n, List<int[]> q, String f, Map<String, Quadro> sub,
			Map<String, LRObject> lros, Map<String, String> opts) {
		options   = opts;
		name      = n;
		fragment  = f;
		quadro    = new int[q.size()][];
		scratch   = new Object[q.size()][];
		scratch2  = new Object[q.size()][];
		labels    = new String[q.size()][];
		ltypes    = new String[q.size()][];
		visited   = new String[q.size()][];
		visiting  = new String[q.size()][];
		subquadro = sub;
		lrobjects = lros;
		for(int i = 0; i < q.size(); i++) {
			quadro[i]   = q.get(i);
			scratch[i]  = new Object[q.get(i).length];
			scratch2[i] = new Object[q.get(i).length];
			labels[i]   = new String[q.get(i).length];
			ltypes[i]   = new String[q.get(i).length];
			visited[i]  = new String[q.get(i).length];
			visiting[i] = new String[q.get(i).length];
		}
		lint = new LintCells(0, q);
	}

	//
	Quadro(String n, int[][] q, String f, Map<String, Quadro> sub,
			Map<String, LRObject> lros, Map<String, String> opts) {
		options   = opts;
		name      = n;
		fragment  = f;
		quadro    = new int[q.length][];
		scratch   = new Object[q.length][];
		scratch2  = new Object[q.length][];
		labels    = new String[q.length][];
		ltypes    = new String[q.length][];
		visited   = new String[q.length][];
		visiting  = new String[q.length][];
		subquadro = sub;
		lrobjects = lros;
		for(int i = 0; i < q.length; i++) {
			quadro[i]   = new int[q[i].length];
			scratch[i]  = new Object[q[i].length];
			scratch2[i] = new Object[q[i].length];
			labels[i]   = new String[q[i].length];
			ltypes[i]   = new String[q[i].length];
			visited[i]  = new String[q[i].length];
			visiting[i] = new String[q[i].length];
			for(int j = 0; j < q[i].length; j++) {
				quadro[i][j] = q[i][j];
			}
		}
		lint = new LintCells(0, q);
	}

	//
	Quadro(String n, List<int[]> q, String f,
			Map<String, LRObject> lros,
			Map<String, String> opts) {
		this(n, q, f, null, lros, opts);
	}

	/**
	 * 
	 * @param options
	 */
	public Quadro(Map<String, String> options) {
		this.options = options;
	}

	/**
	 * 
	 * @param x
	 */
	public static void setTab(int x) {
		StringBuffer b = new StringBuffer();

		if(x <= 0)  return;
		for(int k = 0; k < x; k++)  b.append(' ');
		tabs = b.toString();
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#getRootResource()
	 */
	public String getRootResource() {
		return rootResource;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#getRootPackage()
	 */
	public String getRootPackage() {
		return rootPackage;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#getName()
	 */
	public String getName() {
		return name;
	}

	/**
	 * 
	 * @param x
	 */
	public void setRootResource(String x) {
		rootResource = x;
	}

	/**
	 * 
	 * @param x
	 */
	public void setRootPackage(String x) {
		rootPackage = x;
	}

	/**
	 * @return the edge
	 */
	public Object getEdge() {
		return edge;
	}

	/**
	 * @param edge the edge to set
	 */
	public void setEdge(Object edge) {
		this.edge = edge;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isInBounds() {
		return (cury >= 0 && cury < quadro.length) &&
				(curx >= 0 && curx < quadro[cury].length);
	}

	/**
	 * 
	 * @return
	 */
	public int get() {
		if(cury < 0 || cury >= quadro.length) {
			return BLANKY;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return BLANKX;
		} else {
			return quadro[cury][curx];
		}
	}

	/**
	 * 
	 * @return
	 */
	public Object getScratch() {
		if(cury < 0 || cury >= quadro.length) {
			return null;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return null;
		} else {
			return scratch[cury][curx];
		}
	}

	/**
	 * 
	 * @return
	 */
	public Object getScratch2() {
		if(cury < 0 || cury >= quadro.length) {
			return null;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return null;
		} else {
			return scratch2[cury][curx];
		}
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBlankX() {
		return get() == BLANKX;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBlankY() {
		return get() == BLANKY;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isLetter() {
		int c = get();

		return c >= 0 && Character.isLetter(c);
	}

	/**
	 * 
	 * @return
	 */
	public boolean isDigit() {
		int c = get();

		return c >= 0 && Character.isDigit(c);
	}

	/**
	 * 
	 * @return
	 */
	public boolean isArrow() {
		return "^><v".indexOf(get()) >= 0;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isEntry() {
		return get() == ENTRY;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isDone() {
		return get() == DONE;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isFrameBefore() {
		return "*@=&".indexOf(get()) >= 0;
	}

	/**
	 * 
	 */
	public void transformFrame() {
		if(get() == '@' || get() == '&') {
			set(FRAME_A);
		} else if(get() == '*' || get() == '=') {
			set(FRAME_N);
		}
	}

	/**
	 * 
	 * @return
	 */
	public boolean isFrame() {
		return get() == FRAME_N || get() == FRAME_A;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isArrow2() {
		return get() <= E2 && get() >= S2;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isArrow3() {
		return get() <= E3 && get() >= S3;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBranch() {
		int x = get();

		return x == '+' || x < BRANCH;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBranchN() {
		int x = get();

		if(x == '+') {
			return true;
		} else if((x = -(x - BRANCH)) > 0) {
			return ((x / BRANCH_N) % 10) != 1;
		} else {
			return false;
		}
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBranchE() {
		int x = get();

		if(x == '+') {
			return true;
		} else if((x = -(x - BRANCH)) > 0) {
			return ((x / BRANCH_E) % 10) != 1;
		} else {
			return false;
		}
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBranchS() {
		int x = get();

		if(x == '+') {
			return true;
		} else if((x = -(x - BRANCH)) > 0) {
			return ((x / BRANCH_S) % 10) != 1;
		} else {
			return false;
		}
	}

	/**
	 * 
	 * @return
	 */
	public boolean isBranchW() {
		int x = get();

		if(x == '+') {
			return true;
		} else if((x = -(x - BRANCH)) > 0) {
			return ((x / BRANCH_W) % 10) != 1;
		} else {
			return false;
		}
	}

	/**
	 * 
	 * @return
	 */
	public boolean isWedgeN() {
		int x = get();

		return x < BRANCH && (-(x - BRANCH) / BRANCH_N) % 10 == 1;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isWedgeE() {
		int x = get();

		return x < BRANCH && (-(x - BRANCH) / BRANCH_E) % 10 == 1;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isWedgeS() {
		int x = get();

		return x < BRANCH && (-(x - BRANCH) / BRANCH_S) % 10 == 1;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isWedgeW() {
		int x = get();

		return x < BRANCH && (-(x - BRANCH) / BRANCH_W) % 10 == 1;
	}

	/**
	 * 
	 * @return
	 */
	public boolean isWhitespace() {
		return Character.isWhitespace(get());
	}

	/**
	 * 
	 * @param a
	 */
	public void set(int a) {
		if(isInBounds()) {
			quadro[cury][curx] = a;
		}
	}

	/**
	 * 
	 * @param a
	 */
	public void setScratch(Object a) {
		if(isInBounds()) {
			scratch[cury][curx] = a;
		}
	}

	/**
	 * 
	 * @param a
	 */
	public void setScratch2(Object a) {
		if(isInBounds()) {
			scratch2[cury][curx] = a;
		}
	}

	/**
	 * 
	 * @param d
	 * @return
	 */
	public Quadro move(Quadro.Direction d) {
		switch(d) {
		case WEST:
			curx = curx < 0 ? curx : curx - 1;
			return this;
		case EAST:
			curx = cury < 0 || cury >= quadro.length || curx > quadro[cury].length ?
					curx : curx + 1;
			return this;
		case NORTH:
			cury = cury < 0 ? cury : cury - 1;
			return this;
		case SOUTH:
			cury = cury > quadro.length ? cury : cury + 1;
			return this;
		case CR:
			curx = 0;
			cury = cury > quadro.length ? cury : cury + 1;
			return this;
		default:  throw new RuntimeException();
		}
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public Quadro move(int x, int y) {
		if(cury + y < 0) {
			cury = -1;
		} else if(cury + y > quadro.length) {
			cury = quadro.length;
		}

		if(curx + x < 0) {
			curx = -1;
		} else if(curx + x > quadro[cury].length) {
			curx = quadro[cury].length;
		}
		return this;
	}

	/**
	 * 
	 * @return
	 */
	public Quadro west() {
		return move(Quadro.Direction.WEST);
	}

	/**
	 * 
	 * @return
	 */
	public Quadro east() {
		return move(Quadro.Direction.EAST);
	}

	/**
	 * 
	 * @return
	 */
	public Quadro north() {
		return move(Quadro.Direction.NORTH);
	}

	/**
	 * 
	 * @return
	 */
	public Quadro south() {
		return move(Quadro.Direction.SOUTH);
	}

	/**
	 * 
	 * @return
	 */
	public Quadro cr() {
		return move(Quadro.Direction.CR);
	}

	/**
	 * 
	 * @param name
	 * @return
	 */
	public Quadro getSubQuadro(String name) {
		return subquadro != null ? subquadro.get(name) : null;
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, Quadro> getSubQuadros() {
		return subquadro != null ?
				Collections.unmodifiableMap(subquadro) :
					Collections.<String, Quadro>emptyMap();
	}

	/**
	 * 
	 * @param name
	 * @return
	 */
	public LRObject getLRObject(String name) {
		return lrobjects != null ? lrobjects.get(name) : null;
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, LRObject> getLRObjects() {
		return lrobjects != null ?
				Collections.unmodifiableMap(lrobjects) :
					Collections.<String, LRObject>emptyMap();
	}

	/**
	 * 
	 * @param q
	 * @return
	 */
	public Quadro prototype(Quadro q) {
		return new Quadro(name, quadro, fragment, q.getSubQuadros(),
				lrobjects, q.options);
	}

	/**
	 * 
	 * @return
	 */
	public String getLabel() {
		if(cury < 0 || cury >= quadro.length) {
			return null;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return null;
		} else {
			return labels[cury][curx];
		}
	}

	/**
	 * 
	 * @param a
	 */
	public void setLabel(String a) {
		if(isInBounds()) {
			labels[cury][curx] = a;
		}
	}

	/**
	 * 
	 * @return
	 */
	public String getLabelType() {
		if(cury < 0 || cury >= quadro.length) {
			return null;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return null;
		} else {
			return ltypes[cury][curx];
		}
	}

	/**
	 * 
	 * @param a
	 */
	public void setLabelType(String a) {
		if(isInBounds()) {
			ltypes[cury][curx] = a;
		}
	}

	/**
	 * 
	 * @param l
	 * @return
	 */
	public boolean isVisited(String l) {
		if(cury < 0 || cury >= quadro.length) {
			return false;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return false;
		} else if(visited[cury][curx] == null) {
			return false;
		} else {
			return visited[cury][curx].indexOf("(" + l + ")") >= 0;
		}
	}

	/**
	 * 
	 * @param l
	 */
	public void addVisited(String l) {
		if(!isInBounds()) {
			// do nothing
		} else if(visited[cury][curx] == null) {
			visited[cury][curx] = "(" + l + ")";
		} else {
			visited[cury][curx] += "(" + l + ")";
		}
	}

	/**
	 * 
	 * @return
	 */
	public String getVisiting() {
		if(cury < 0 || cury >= quadro.length) {
			return null;
		} else if(curx < 0 || curx >= quadro[cury].length) {
			return null;
		} else {
			return visiting[cury][curx];
		}
	}

	/**
	 * 
	 * @param a
	 */
	public void setVisiting(String a) {
		if(isInBounds()) {
			visiting[cury][curx] = a;
		}
	}

	/**
	 * 
	 * @param l
	 * @return
	 */
	public Object getStateByLabel(String l) {
		for(int y = 0; y < labels.length; y++) {
			for(int x = 0; x < labels[y].length; x++) {
				if(l.equals(labels[y][x])) {
					return scratch[y][x];
				}
			}
		}
		return null;
	}

	/**
	 * 
	 * @return
	 */
	public Object getDeadState() {
		return getStateByLabel(NinaParser.DEADSYM);
	}

	static int putWedgeN(int x) {
		if(x == '+') {
			return BRANCH - BRANCH_N;
		} else if(x < BRANCH) {
			return x - BRANCH_N;
		} else {
			throw new RuntimeException();
		}
	}

	static int putWedgeE(int x) {
		if(x == '+') {
			return BRANCH - BRANCH_E;
		} else if(x < BRANCH) {
			return x - BRANCH_E;
		} else {
			throw new RuntimeException();
		}
	}

	static int putWedgeS(int x) {
		if(x == '+') {
			return BRANCH - BRANCH_S;
		} else if(x < BRANCH) {
			return x - BRANCH_S;
		} else {
			throw new RuntimeException();
		}
	}

	static int putWedgeW(int x) {
		if(x == '+') {
			return BRANCH - BRANCH_W;
		} else if(x < BRANCH) {
			return x - BRANCH_W;
		} else {
			throw new RuntimeException();
		}
	}

	/**
	 * 
	 * @param classe
	 * @return
	 * @throws IOException
	 */
	public static Quadro readResource(String name,
			List<String> l) throws IOException {
		InputStream in = null;
		Reader rd;

		try {
			in = Quadro.class.getResourceAsStream(name);
			rd = new InputStreamReader(in);
			return read(name, rd, l);
		} finally {
			if(in != null)  in.close();
		}
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public static Quadro read(String name, String s, List<String> l) {
		StringReader r;

		try {
			r = new StringReader(s);
			return read(name, r, l);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	//
	private static Quadro putquadro(Map<String, Quadro> q,String x,
			List<int[]> l, Map<String, String> m, int y) {
		Quadro v;

		q.put(x, v = new Quadro(x, l, "", null, m));
		v.lint.setYOffset(y);
		iswarning = v.lint.validate(errst) | iswarning;
		return v;
	}

	//
	private static List<int[]> setquadro(Map<String, Quadro> q,
			Map<String, String> m, String x, int y, List<int[]> z,
			List<int[]> l) {
		if(z == null) {
			return l;
		} else if(x != null) {
			putquadro(q, x, l, m, y);
		}
		return z;
	}

	private static LRObject readlr(String fn,
			List<String> libs) throws IOException {
		InputStream in;
		Reader rd = null;

		try {
			if((in = Nina.findLib(fn, libs)) == null) {
				throw new NinaException("filenotfound", fn);
			}
			rd = new InputStreamReader(in);
			return LRObject.parse(rd);
		} finally {
			if(rd != null)  rd.close();
		}
	}

	/**
	 * 
	 * @param name
	 * @param rd
	 * @return
	 * @throws IOException
	 */
	public static Quadro read(String name, Reader rd,
			List<String> libs) throws IOException {
		Map<String, LRObject> f = new HashMap<String, LRObject>();
		Map<String, Quadro> q = new HashMap<String, Quadro>();
		Map<String, String> m = new HashMap<String, String>();
		List<int[]> l = new ArrayList<int[]>(), z = null;
		List<String[]> d = new ArrayList<String[]>();
		BufferedReader br = new BufferedReader(rd);
		List<String> sl = new ArrayList<String>();
		boolean b = false, b2 = true, nf = false;
		String s, x = null, desc = "";
		StringWriter sw = null;
		PrintWriter pw = null;
		int y = 0, p = 0;
		LRObject lr;
		String[] a;
		Matcher h;
		Quadro v;

		for(; (s = br.readLine()) != null && !(sw == null && s.equals("%%")); p++) {
			s = s.replaceAll("\t", tabs);
			s = s.replaceAll("\u3000", "  ");
			if(sw != null) {
				if(!b2) {
					if(b2 = CDLM.matcher(s).matches()) {
						desc = sw.toString();
						sw = null;
					} else {
						pw.println(s);
					}
				} else if(s.equals(EOG)) {
					lr = LRObject.parse(new StringReader(sw.toString()));
					f.put(x, lr);
					sw = null;  x = null;
				} else {
					pw.println(s);
				}
			} else if(s.equals("")) {
				continue;
			} else if(ALLBYTE1.equals(s)) {
				nf = true;
			} else if(CDLM.matcher(s).matches()) {
				b2 = false;
				sw = new StringWriter();
				pw = new PrintWriter(sw, true);
			} else if((h = PTN1.matcher(s)).matches()) {
				z = setquadro(q, m, x, y, z, l);
				y = p + 1;
				x = h.group(1);
				l = new ArrayList<int[]>();
				for(String t : sl)  l.add(Nina.toiarray(t, nf));
			} else if((h = PTN3.matcher(s)).matches()) {
				z = setquadro(q, m, x, y, z, l);
				x = h.group(1);
				sw = new StringWriter();
				pw = new PrintWriter(sw, true);
			} else if((h = PTN4.matcher(s)).matches()) {
				z = setquadro(q, m, x, y, z, l);
				a = Nina.readHeader(h.group(1), libs);
				putquadro(q, h.group(2),
						Nina.readImport(h.group(1), libs, a[2], nf),
						m, y);
				if(a[0] != null)  d.add(a);
				x = null;
			} else if((h = PTN5.matcher(s)).matches()) {
				z = setquadro(q, m, x, y, z, l);
				a = Nina.readHeader(h.group(1), libs);
				f.put(h.group(2), readlr(h.group(1), libs));
				if(a[0] != null)  d.add(a);
				x = null;
			} else if(z != null && x == null) {
				continue;
			} else if(!b && z == null && s.charAt(0) == '#') {
				sl.add(s);
				l.add(Nina.toiarray(s, nf));
			} else if(!b && PTN2.matcher(s).lookingAt()) {
				b = true;
				l.add(Nina.toiarray(s, nf));
			} else if(b && s.startsWith("%}")) {
				b = false;
				l.add(Nina.toiarray(s, nf));
			} else {
				l.add(Nina.toiarray(s, nf));
			}
		}

		if(sw != null)  throw new NinaException("unexpectedeof");
		z = setquadro(q, m, x, y, z, l);
		sw = new StringWriter();
		pw = new PrintWriter(sw, true);
		while((s = br.readLine()) != null)  pw.println(s);
		v = new Quadro(name, z, sw.toString(), q, f,
				new HashMap<String, String>());
		v.lint.setYOffset(0);
		v.ids = d;
		v.description = desc;
		iswarning = v.lint.validate(errst) | iswarning;
		return v;
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public String getOption(String s) {
		return options.get(s);
	}

	/**
	 * 
	 * @return
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * 
	 * @return
	 */
	public String getLicense() {
		return license.toString();
	}

	/**
	 * 
	 * @param s
	 * @param n
	 * @return
	 */
	public String getOptionNvl(String s, String n) {
		String x = options.get(s);

		return x != null ? x : n;
	}

	/**
	 * 
	 * @param s
	 * @param x
	 * @return
	 */
	public int getOptionInt(String s, int x) {
		try {
			return Integer.parseInt(options.get(s));
		} catch(NumberFormatException e) {
			return x;
		}
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public boolean isOptionDefined(String s) {
		return options.get(s) != null;
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#getOptionBoolean(java.lang.String)
	 */
	@Override
	public boolean getOptionBoolean(String s) {
		String x;

		if((x = options.get(s)) == null)  return false;
		return (x.equalsIgnoreCase("true") ||
				x.equalsIgnoreCase("yes")  ||
				x.equalsIgnoreCase("on")   ||
				x.equalsIgnoreCase("oui"));
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public String getAlias(String s) {
		return aliases.get(s);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#getCharset()
	 */
	@Override
	public String getCharset() {
		String s;

		return (s = options.get("charset")) != null ? s : "UTF-8";
	}

	/**
	 * 
	 * @return
	 */
	public String getType() {
		return type;
	}

	/**
	 * 
	 * @return
	 */
	public List<String> getImports() {
		return Collections.unmodifiableList(imports);
	}

	//
	void addImports(String s) {
		imports.add(s);
	}

	/**
	 * 
	 * @return
	 */
	public String getFragment() {
		return fragment != null ? fragment : "";
	}

	/**
	 * 
	 * @return
	 */
	public String getDefinition() {
		return getFragmentByName("");
	}

	/**
	 * 
	 * @param s
	 * @return
	 */
	public String getFragmentByName(String s) {
		StringBuffer b;

		return (b = buffers.get(s)) != null ? b.toString() : "";
	}

	/**
	 * 
	 * @return
	 */
	public String getFragmentField() {
		StringBuffer b, a;
		String n = System.getProperty("line.separator");

		b = new StringBuffer();
		if((a = buffers.get("field"))  != null)  b.append(a);
		if((a = buffers.get("member")) == null) {
			// do nothing
		} else {
			if(b.length() > 0)  b.append(n);
			b.append(a);
		}
		return b.toString();
	}

	/**
	 * 
	 * @param pr
	 */
	public void printTrace(PrintStream pr) {
		pr.println(toString());
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#getLanguageOption()
	 */
	public NinaLanguageOption getLanguageOption() {
		String s;

		if(langopt == null) {
			s = options.get(LOPT);
			s = s != null ? s : "Java";
			langopt = (NinaLanguageOption)Nina.prendClasse(
					NinaLanguageOption.class.getName() + s);
		}
		return langopt;
	}

	/**
	 * 
	 * @return
	 */
	public static boolean isWarning() {
		return iswarning;
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, Integer> getConstantMap() {
		return Collections.unmodifiableMap(consts);
	}

	/* (non-Javadoc)
	 * @see net.morilib.nina.NinaEvent#isGrammar(java.lang.String)
	 */
	@Override
	public boolean isGrammar(String t) {
		return lrobjects.containsKey(t);
	}

	/**
	 * 
	 * @return
	 */
	public String getLexer() {
		return lexer;
	}

	/**
	 * 
	 * @return
	 */
	public String getLexerType() {
		return ltype;
	}

	private void checkRequire1(String[] a) {
		VersionNumber f, g;

		if(ids != null) {
			for(String[] b : ids) {
				if(a[0].equals(b[0])) {
					if(a[1] == null)  return;
					if(b[1] == null) {
						throw new NinaException(
								"requireversion", a[0]);
					}

					try {
						g = new VersionNumber(b[1]);
					} catch(NumberFormatException e) {
						throw new NinaException("invalidversion",
								b[1]);
					}

					try {
						if(a[1].charAt(0) == '>') {
							f = new VersionNumber(a[1].substring(1));
							if(!g.isUpperThan(f)) {
								throw new NinaException(
										"requireversion", a[0]);
							}
						} else if(a[1].charAt(0) == '=') {
							f = new VersionNumber(a[1].substring(1));
							if(!g.isIncluded(f)) {
								throw new NinaException(
										"requireversion", a[0]);
							}
						} else if(a[1].charAt(0) == '<') {
							f = new VersionNumber(a[1].substring(1));
							if(!g.isLowerThan(f)) {
								throw new NinaException(
										"requireversion", a[0]);
							}
						} else {
							f = new VersionNumber(a[1]);
							if(!g.isUpperThan(f)) {
								throw new NinaException(
										"requireversion", a[0]);
							}
						}
						return;
					} catch(NumberFormatException e) {
						throw new NinaException("invalidversion",
								a[1]);
					}
				}
			}
		}
		throw new NinaException("packagenotfound", a[0]);
	}

	//
	void checkRequire() {
		for(String[] a : requires) {
			checkRequire1(a);
		}
	}

	/**
	 * 
	 * @return
	 */
	public int getX() {
		return curx;
	}

	/**
	 * 
	 * @return
	 */
	public int getY() {
		return cury;
	}

	/**
	 * 
	 * @param x
	 * @param y
	 */
	public void setPosition(int x, int y) {
		curx = x;
		cury = y;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuffer b = new StringBuffer();

		for(int i = 0; i < quadro.length; i++) {
			for(int j = 0; j < quadro[i].length; j++) {
				if(cury == i && curx == j) {
					b.append('x');
				} else {
					switch(quadro[i][j]) {
					case EQ_TO_LEFT:  break;
					case BLANKX:  b.append('$');  break;
					case BLANKY:  b.append('#');  break;
					case N2:      b.append('^');  break;
					case E2:      b.append('>');  break;
					case S2:      b.append('v');  break;
					case W2:      b.append('<');  break;
					case N3:      b.append('A');  break;
					case E3:      b.append(')');  break;
					case S3:      b.append('w');  break;
					case W3:      b.append('(');  break;
					case DONE:    b.append('0');  break;
					default:
						if(quadro[i][j] >= 0) {
							b.append((char)quadro[i][j]);
						} else {
							b.append('.');
						}
						break;
					}
				}
			}
			b.append('\n');
		}
		return b.toString();
	}

}
