package %%%PACKAGE%%%;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

class JsonParser {

	private static class JsonParserException extends RuntimeException {
		private static final long serialVersionUID = 1L; 
	}

	private static class Index {
		public Index(int value) { this.Value = value; }
		public int Value;
	}
	
	public static Value parseJsonIntoValue(String rawValue) {
		try {
			return parseJsonIntoValue(rawValue.toCharArray(), rawValue.length(), new Index(0));
		}
		catch (JsonParserException jpe) {
			return null;
		}
	}

	private static Value parseJsonIntoValue(char[] rawValue, int length, Index i) {
		Value output = parseJsonThing(rawValue, length, i);
		skipWhitespace(rawValue, length, i);
		if (i.Value < length) throw new JsonParserException();
		return output;
	}

	private static Value parseJsonThing(char[] rawValue, int length, Index i) {
		skipWhitespace(rawValue, length, i);
		Value value = null;
		char c = rawValue[i.Value];
		if (c == '{') {
			value = parseJsonDictionary(rawValue, length, i);
		} else if (c == '[') {
			value = parseJsonList(rawValue, length, i);
		} else if (c == '"') {
			value = parseJsonString(rawValue, length, i);
		} else if (c == '.' || (c >= '0' && c <= '9')) {
			value = parseJsonNumber(rawValue, length, i);
		} else if (popIfPresent(rawValue, length, i, "true")) {
			value = wrapBoolean(true);
		} else if (popIfPresent(rawValue, length, i, "false")) {
			value = wrapBoolean(false);
		} else if (popIfPresent(rawValue, length, i, "null")) {
			value = wrapNull();
		} else {
			throw new JsonParserException();
		}
		return value;
	}

	private static Value parseJsonNumber(char[] rawValue, int length, Index i) {
		char c;
		StringBuilder sb = new StringBuilder();
		while (i.Value < length) {
			c = rawValue[i.Value++];

			if (c != '.' && (c < '0' || c > '9')) {
				i.Value--;
				break;
			} else {
				sb.append(c);
			}
		}

		String stringValue = sb.toString();
		if (stringValue.contains(".")) {
			double value;
			try {
				value = Double.parseDouble(stringValue);
				return wrapFloat(value);
			} catch (NumberFormatException nfe) {
			}
		} else {
			int value;
			try {
				value = Integer.parseInt(stringValue);
				return wrapInteger(value);
			} catch (NumberFormatException nfe) {
			}
		}

		throw new JsonParserException();
	}

	private static Value parseJsonString(char[] rawValue, int length, Index i) {
		i.Value++; // opening quote
		StringBuilder sb = new StringBuilder();
		while (i.Value < length && rawValue[i.Value] != '"') {
			char c = rawValue[i.Value++];
			if (c == '\\') {
				switch (rawValue[i.Value++]) {
					case '\\': c = '\\'; break;
					case '"': c = '"'; break;
					case '\'': c = '\''; break;
					case 'n': c = '\n'; break;
					case 't': c = '\t'; break;
					case 'r': c = '\r'; break;
					case '0': c = '\0'; break;
					default: throw new JsonParserException();
				}
			}
			sb.append(c);
		}

		if (i.Value >= length) throw new JsonParserException();
		i.Value++; // closing quote
		return wrapString(sb.toString());
	}

	private static Value parseJsonList(char[] rawValue, int length, Index i) {
		i.Value++; // '['
		skipWhitespace(rawValue, length, i);
		ArrayList<Value> items = new ArrayList<Value>();
		while (i.Value < length && rawValue[i.Value] != ']') {
			if (items.size() > 0) {
				popExpected(rawValue, length, i, ",");
				skipWhitespace(rawValue, length, i);
			}

			Value item = parseJsonThing(rawValue, length, i);
			skipWhitespace(rawValue, length, i);
			items.add(item);
		}

		if (i.Value < length) popExpected(rawValue, length, i, "]");

		return wrapList(items);
	}

	private static Value parseJsonDictionary(char[] rawValue, int length, Index i) {
		i.Value++; // '{'
		skipWhitespace(rawValue, length, i);
		ArrayList<Value> keys = new ArrayList<Value>();
		ArrayList<Value> values = new ArrayList<Value>();
		while (i.Value < length && rawValue[i.Value] != '}') {
			if (keys.size() > 0) {
				popExpected(rawValue, length, i, ",");
				skipWhitespace(rawValue, length, i);
			}

			Value key = parseJsonThing(rawValue, length, i);
			if (key.type != %%%TYPE_STRING%%%) throw new JsonParserException();
			skipWhitespace(rawValue, length, i);
			popExpected(rawValue, length, i, ":");
			skipWhitespace(rawValue, length, i);
			Value value = parseJsonThing(rawValue, length, i);
			skipWhitespace(rawValue, length, i);
			keys.add(key);
			values.add(value);
		}

		if (i.Value < length) {
			i.Value++; // '}'
		} else {
			throw new JsonParserException(); // EOF
		}

		return wrapDictionary(keys, values);
	}

	private static Value wrapInteger(int value) {
		return CrayonWrapper.v_buildInteger(value);
	}

	private static Value wrapFloat(double value) {
		return new Value(%%%TYPE_FLOAT%%%, value);
	}

	private static void popExpected(char[] rawValue, int length, Index index, String value) {
		if (!popIfPresent(rawValue, length, index, value)) {
			throw new JsonParserException();
		}
	}

	private static boolean popIfPresent(char[] rawValue, int length, Index index, String value) {
		if (index.Value + value.length() > length) {
			return false;
		}

		for (int i = 0; i < value.length(); ++i) {
			if (rawValue[index.Value + i] != value.charAt(i)) {
				return false;
			}
		}
		index.Value += value.length();
		return true;
	}

	private static HashSet<Character> WHITESPACE = null;

	private static void skipWhitespace(char[] rawValue, int length, Index i) {
		if (WHITESPACE == null) {
			WHITESPACE = new HashSet<Character>(4);
			WHITESPACE.add(' ');
			WHITESPACE.add('\t');
			WHITESPACE.add('\r');
			WHITESPACE.add('\n');
		}

		while (i.Value < length && WHITESPACE.contains(rawValue[i.Value])) {
			i.Value++;
		}
	}

	private static Value wrapNull() {
		return CrayonWrapper.v_VALUE_NULL;
	}

	private static Value wrapBoolean(boolean value) {
		return value ? CrayonWrapper.v_VALUE_TRUE : CrayonWrapper.v_VALUE_FALSE;
	}

	private static Value wrapString(String value) {
		return value.length() == 0 ? CrayonWrapper.v_VALUE_EMPTY_STRING : new Value(%%%TYPE_STRING%%%, value);
	}

	private static Value wrapList(ArrayList<Value> items) {
		return new Value(%%%TYPE_LIST%%%, new ArrayList<Value>(items));
	}

	private static Value wrapDictionary(ArrayList<Value> keys, ArrayList<Value> values) {
		HashMap<String, Value> dictKeyLookup = new HashMap<String, Value>();
		HashMap<String, Value> dictValueLookup = new HashMap<String, Value>();

		for (int i = 0; i < keys.size(); ++i) {
			String key = keys.get(i).internalValue.toString();
			dictKeyLookup.put(key, keys.get(i));
			dictValueLookup.put(key, values.get(i));
		}

		DictImpl dictionary = new DictImpl(
			new HashMap<Integer, Value>(),
			new HashMap<Integer, Value>(),
			dictKeyLookup,
			dictValueLookup,
			dictKeyLookup.size(), %%%TYPE_STRING%%%);
		return new Value(%%%TYPE_DICTIONARY%%%, dictionary);
	}
}
