// Do not edit this file! Generated by Ragel.
// Ragel.exe -G2 -J -o JsonReader.java JsonReader.rl
/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 *
 * 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 com.badlogic.gdx.utils;

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

import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.JsonValue.ValueType;

/** Lightweight JSON parser.<br>
 * <br>
 * The default behavior is to parse the JSON into a DOM containing {@link JsonValue} objects. Extend this class and override
 * methods to perform event driven parsing. When this is done, the parse methods will return null.
 * @author Nathan Sweet */
public class JsonReader implements BaseJsonReader {
	public JsonValue parse (String json) {
		char[] data = json.toCharArray();
		return parse(data, 0, data.length);
	}

	public JsonValue parse (Reader reader) {
		try {
			char[] data = new char[1024];
			int offset = 0;
			while (true) {
				int length = reader.read(data, offset, data.length - offset);
				if (length == -1) break;
				if (length == 0) {
					char[] newData = new char[data.length * 2];
					System.arraycopy(data, 0, newData, 0, data.length);
					data = newData;
				} else
					offset += length;
			}
			return parse(data, 0, offset);
		} catch (IOException ex) {
			throw new SerializationException(ex);
		} finally {
			StreamUtils.closeQuietly(reader);
		}
	}

	public JsonValue parse (InputStream input) {
		try {
			return parse(new InputStreamReader(input, "UTF-8"));
		} catch (IOException ex) {
			throw new SerializationException(ex);
		} finally {
			StreamUtils.closeQuietly(input);
		}
	}

	public JsonValue parse (FileHandle file) {
		try {
			return parse(file.reader("UTF-8"));
		} catch (Exception ex) {
			throw new SerializationException("Error parsing file: " + file, ex);
		}
	}

	public JsonValue parse (char[] data, int offset, int length) {
		int cs, p = offset, pe = length, eof = pe, top = 0;
		int[] stack = new int[4];

		int s = 0;
		Array<String> names = new Array(8);
		boolean needsUnescape = false, stringIsName = false, stringIsUnquoted = false;
		RuntimeException parseRuntimeEx = null;

		boolean debug = false;
		if (debug) System.out.println();

		try {
		%%{
			machine json;

			prepush {
				if (top == stack.length) {
					int[] newStack = new int[stack.length * 2];
					System.arraycopy(stack, 0, newStack, 0, stack.length);
					stack = newStack;
				}
			}

			action name {
				stringIsName = true;
			}
			action string {
				String value = new String(data, s, p - s);
				if (needsUnescape) value = unescape(value);
				outer:
				if (stringIsName) {
					stringIsName = false;
					if (debug) System.out.println("name: " + value);
					names.add(value);
				} else {
					String name = names.size > 0 ? names.pop() : null;
					if (stringIsUnquoted) {
						if (value.equals("true")) {
							if (debug) System.out.println("boolean: " + name + "=true");
							bool(name, true);
							break outer;
						} else if (value.equals("false")) {
							if (debug) System.out.println("boolean: " + name + "=false");
							bool(name, false);
							break outer;
						} else if (value.equals("null")) {
							string(name, null);
							break outer;
						}
						boolean couldBeDouble = false, couldBeLong = true;
						outer2:
						for (int i = s; i < p; i++) {
							switch (data[i]) {
							case '0':
							case '1':
							case '2':
							case '3':
							case '4':
							case '5':
							case '6':
							case '7':
							case '8':
							case '9':
							case '-':
							case '+':
								break;
							case '.':
							case 'e':
							case 'E':
								couldBeDouble = true;
								couldBeLong = false;
								break;
							default:
								couldBeDouble = false;
								couldBeLong = false;
								break outer2;
							}
						}
						if (couldBeDouble) {
							try {
								if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value));
								number(name, Double.parseDouble(value), value);
								break outer;
							} catch (NumberFormatException ignored) {
							}
						} else if (couldBeLong) {
							if (debug) System.out.println("double: " + name + "=" + Double.parseDouble(value));
							try {
								number(name, Long.parseLong(value), value);
								break outer;
							} catch (NumberFormatException ignored) {
							}
						}
					}
					if (debug) System.out.println("string: " + name + "=" + value);
					string(name, value);
				}
				stringIsUnquoted = false;
				s = p;
			}
			action startObject {
				String name = names.size > 0 ? names.pop() : null;
				if (debug) System.out.println("startObject: " + name);
				startObject(name);
				fcall object;
			}
			action endObject {
				if (debug) System.out.println("endObject");
				pop();
				fret;
			}
			action startArray {
				String name = names.size > 0 ? names.pop() : null;
				if (debug) System.out.println("startArray: " + name);
				startArray(name);
				fcall array;
			}
			action endArray {
				if (debug) System.out.println("endArray");
				pop();
				fret;
			}
			action comment {
				int start = p - 1;
				if (data[p++] == '/') {
					while (p != eof && data[p] != '\n')
						p++;
					p--;
				} else {
					while (p + 1 < eof && data[p] != '*' || data[p + 1] != '/')
						p++;
					p++;
				}
				if (debug) System.out.println("comment " + new String(data, start, p - start));
			}
			action unquotedChars {
				if (debug) System.out.println("unquotedChars");
				s = p;
				needsUnescape = false;
				stringIsUnquoted = true;
				if (stringIsName) {
					outer:
					while (true) {
						switch (data[p]) {
						case '\\':
							needsUnescape = true;
							break;
						case '/':
							if (p + 1 == eof) break;
							char c = data[p + 1];
							if (c == '/' || c == '*') break outer;
							break;
						case ':':
						case '\r':
						case '\n':
							break outer;
						}
						if (debug) System.out.println("unquotedChar (name): '" + data[p] + "'");
						p++;
						if (p == eof) break;
					}
				} else {
					outer:
					while (true) {
						switch (data[p]) {
						case '\\':
							needsUnescape = true;
							break;
						case '/':
							if (p + 1 == eof) break;
							char c = data[p + 1];
							if (c == '/' || c == '*') break outer;
							break;
						case '}':
						case ']':
						case ',':
						case '\r':
						case '\n':
							break outer;
						}
						if (debug) System.out.println("unquotedChar (value): '" + data[p] + "'");
						p++;
						if (p == eof) break;
					}
				}
				p--;
				while (Character.isSpace(data[p]))
					p--;
			}
			action quotedChars {
				if (debug) System.out.println("quotedChars");
				s = ++p;
				needsUnescape = false;
				outer:
				while (true) {
					switch (data[p]) {
					case '\\':
						needsUnescape = true;
						p++;
						break;
					case '"':
						break outer;
					}
					// if (debug) System.out.println("quotedChar: '" + data[p] + "'");
					p++;
					if (p == eof) break;
				}
				p--;
			}

			comment = ('//' | '/*') @comment;
			ws = [\r\n\t ] | comment;
			ws2 = [\t ] | comment;
			comma = ',' | ([\r\n] ws* ','?);
			quotedString = '"' @quotedChars %string '"';
			nameString = quotedString | ^[":,}/\r\n\t ] >unquotedChars %string;
			valueString = quotedString | ^[":,{[\]/\r\n\t ] >unquotedChars %string;
			value = '{' @startObject | '[' @startArray | valueString;
			nameValue = nameString >name ws* ':' ws* value;
			object := ws* nameValue? ws2* <: (comma ws* nameValue ws2*)** :>> (','? ws* '}' @endObject);
			array := ws* value? ws2* <: (comma ws* value ws2*)** :>> (','? ws* ']' @endArray);
			main := ws* value ws*;

			write init;
			write exec;
		}%%
		} catch (RuntimeException ex) {
			parseRuntimeEx = ex;
		}

		JsonValue root = this.root;
		this.root = null;
		current = null;
		lastChild.clear();

		if (p < pe) {
			int lineNumber = 1;
			for (int i = 0; i < p; i++)
				if (data[i] == '\n') lineNumber++;
			int start = Math.max(0, p - 32);
			throw new SerializationException("Error parsing JSON on line " + lineNumber + " near: "
				+ new String(data, start, p - start) + "*ERROR*" + new String(data, p, Math.min(64, pe - p)), parseRuntimeEx);
		} else if (elements.size != 0) {
			JsonValue element = elements.peek();
			elements.clear();
			if (element != null && element.isObject())
				throw new SerializationException("Error parsing JSON, unmatched brace.");
			else
				throw new SerializationException("Error parsing JSON, unmatched bracket.");
		} else if (parseRuntimeEx != null) {
			throw new SerializationException("Error parsing JSON: " + new String(data), parseRuntimeEx);
		}
		return root;
	}

	%% write data;

	private final Array<JsonValue> elements = new Array(8);
	private final Array<JsonValue> lastChild = new Array(8);
	private JsonValue root, current;

	/** @param name May be null. */
	private void addChild (String name, JsonValue child) {
		child.setName(name);
		if (current == null) {
			current = child;
			root = child;
		} else if (current.isArray() || current.isObject()) {
			child.parent = current;
			if (current.size == 0)
				current.child = child;
			else {
				JsonValue last = lastChild.pop();
				last.next = child;
				child.prev = last;
			}
			lastChild.add(child);
			current.size++;
		} else
			root = current;
	}

	/** @param name May be null. */
	protected void startObject (String name) {
		JsonValue value = new JsonValue(ValueType.object);
		if (current != null) addChild(name, value);
		elements.add(value);
		current = value;
	}

	/** @param name May be null. */
	protected void startArray (String name) {
		JsonValue value = new JsonValue(ValueType.array);
		if (current != null) addChild(name, value);
		elements.add(value);
		current = value;
	}

	protected void pop () {
		root = elements.pop();
		if (current.size > 0) lastChild.pop();
		current = elements.size > 0 ? elements.peek() : null;
	}

	protected void string (String name, String value) {
		addChild(name, new JsonValue(value));
	}

	protected void number (String name, double value, String stringValue) {
		addChild(name, new JsonValue(value, stringValue));
	}

	protected void number (String name, long value, String stringValue) {
		addChild(name, new JsonValue(value, stringValue));
	}

	protected void bool (String name, boolean value) {
		addChild(name, new JsonValue(value));
	}

	private String unescape (String value) {
		int length = value.length();
		StringBuilder buffer = new StringBuilder(length + 16);
		for (int i = 0; i < length;) {
			char c = value.charAt(i++);
			if (c != '\\') {
				buffer.append(c);
				continue;
			}
			if (i == length) break;
			c = value.charAt(i++);
			if (c == 'u') {
				buffer.append(Character.toChars(Integer.parseInt(value.substring(i, i + 4), 16)));
				i += 4;
				continue;
			}
			switch (c) {
			case '"':
			case '\\':
			case '/':
				break;
			case 'b':
				c = '\b';
				break;
			case 'f':
				c = '\f';
				break;
			case 'n':
				c = '\n';
				break;
			case 'r':
				c = '\r';
				break;
			case 't':
				c = '\t';
				break;
			default:
				throw new SerializationException("Illegal escaped character: \\" + c);
			}
			buffer.append(c);
		}
		return buffer.toString();
	}
}
