import string
import types
import datetime

##	json.py implements a JSON (http://json.org) reader and writer.
##	Copyright (C) 2005  Patrick D. Logan
##	Contact mailto:patrickdlogan@stardecisions.com
##
##	This library is free software; you can redistribute it and/or
##	modify it under the terms of the GNU Lesser General Public
##	License as published by the Free Software Foundation; either
##	version 2.1 of the License, or (at your option) any later version.
##
##	This library is distributed in the hope that it will be useful,
##	but WITHOUT ANY WARRANTY; without even the implied warranty of
##	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
##	Lesser General Public License for more details.
##
##	You should have received a copy of the GNU Lesser General Public
##	License along with this library; if not, write to the Free Software
##	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


class _StringGenerator(object):
	def __init__(self, string):
		self.string = string
		self.index = -1
	def peek(self):
		i = self.index + 1
		if i < len(self.string):
			return self.string[i]
		else:
			return None
	def next(self):
		self.index += 1
		if self.index < len(self.string):
			return self.string[self.index]
		else:
			raise StopIteration
	def all(self):
		return self.string

class WriteException(Exception):
	pass

class ReadException(Exception):
	pass

class JsonReader(object):
	hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
	escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}

	def read(self, s):
		self._generator = _StringGenerator(s)
		result = self._read()
		return result

	def _read(self):
		self._eatWhitespace()
		peek = self._peek()
		if peek is None:
			raise ReadException, "Nothing to read: '%s'" % self._generator.all()
		if peek == '{':
			return self._readObject()
		elif peek == '[':
			return self._readArray()			
		elif peek == '"':
			return self._readString()
		elif peek == '-' or peek.isdigit():
			return self._readNumber()
		elif peek == 't':
			return self._readTrue()
		elif peek == 'f':
			return self._readFalse()
		elif peek == 'n':
			return self._readNull()
		elif peek == '/':
			self._readComment()
			return self._read()
		else:
			raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()

	def _readTrue(self):
		self._assertNext('t', "true")
		self._assertNext('r', "true")
		self._assertNext('u', "true")
		self._assertNext('e', "true")
		return True

	def _readFalse(self):
		self._assertNext('f', "false")
		self._assertNext('a', "false")
		self._assertNext('l', "false")
		self._assertNext('s', "false")
		self._assertNext('e', "false")
		return False

	def _readNull(self):
		self._assertNext('n', "null")
		self._assertNext('u', "null")
		self._assertNext('l', "null")
		self._assertNext('l', "null")
		return None

	def _assertNext(self, ch, target):
		if self._next() != ch:
			raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())

	def _readNumber(self):
		isfloat = False
		result = self._next()
		peek = self._peek()
		while peek is not None and (peek.isdigit() or peek == "."):
			isfloat = isfloat or peek == "."
			result = result + self._next()
			peek = self._peek()
		try:
			if isfloat:
				return float(result)
			else:
				return int(result)
		except ValueError:
			raise ReadException, "Not a valid JSON number: '%s'" % result

	def _readString(self):
		result = ""
		assert self._next() == '"'
		try:
			while self._peek() != '"':
				ch = self._next()
				if ch == "\\":
					ch = self._next()
					if ch in 'brnft':
						ch = self.escapes[ch]
					elif ch == "u":
						ch4096 = self._next()
						ch256  = self._next()
						ch16   = self._next()
						ch1	= self._next()
						n = 4096 * self._hexDigitToInt(ch4096)
						n += 256 * self._hexDigitToInt(ch256)
						n += 16  * self._hexDigitToInt(ch16)
						n += self._hexDigitToInt(ch1)
						ch = unichr(n)
					elif ch not in '"/\\':
						raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
				result = result + ch
		except StopIteration:
			raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
		assert self._next() == '"'
		return result

	def _hexDigitToInt(self, ch):
		try:
			result = self.hex_digits[ch.upper()]
		except KeyError:
			try:
				result = int(ch)
			except ValueError:
				 raise ReadException, "The character %s is not a hex digit." % ch
		return result

	def _readComment(self):
		assert self._next() == "/"
		second = self._next()
		if second == "/":
			self._readDoubleSolidusComment()
		elif second == '*':
			self._readCStyleComment()
		else:
			raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()

	def _readCStyleComment(self):
		try:
			done = False
			while not done:
				ch = self._next()
				done = (ch == "*" and self._peek() == "/")
				if not done and ch == "/" and self._peek() == "*":
					raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
			self._next()
		except StopIteration:
			raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()

	def _readDoubleSolidusComment(self):
		try:
			ch = self._next()
			while ch != "\r" and ch != "\n":
				ch = self._next()
		except StopIteration:
			pass

	def _readArray(self):
		result = []
		assert self._next() == '['
		done = self._peek() == ']'
		while not done:
			item = self._read()
			result.append(item)
			self._eatWhitespace()
			done = self._peek() == ']'
			if not done:
				ch = self._next()
				if ch != ",":
					raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
		assert ']' == self._next()
		return result

	def _readObject(self):
		result = {}
		assert self._next() == '{'
		done = self._peek() == '}'
		while not done:
			key = self._read()
			if type(key) is not types.StringType:
				raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
			self._eatWhitespace()
			ch = self._next()
			if ch != ":":
				raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
			self._eatWhitespace()
			val = self._read()
			result[key] = val
			self._eatWhitespace()
			done = self._peek() == '}'
			if not done:
				ch = self._next()
				if ch != ",":
					raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
			assert self._next() == "}"
		return result

	def _eatWhitespace(self):
		p = self._peek()
		while p is not None and p in string.whitespace or p == '/':
			if p == '/':
				self._readComment()
			else:
				self._next()
			p = self._peek()

	def _peek(self):
		return self._generator.peek()

	def _next(self):
		return self._generator.next()

class JsonWriter(object):
		
	def _append(self, s):
		self._results.append(s)

	def write(self, obj, escaped_forward_slash=False):
		self._escaped_forward_slash = escaped_forward_slash
		self._results = []
		self._write(obj)
		return "".join(self._results)

	def _write(self, obj):
		if type(obj) in (datetime.time, datetime.date, datetime.datetime):
			obj = str(obj)
		
		ty = type(obj)
		if isinstance(obj, dict):
			n = len(obj)
			self._append("{")
			for k, v in obj.items():
				self._write(k)
				self._append(":")
				self._write(v)
				n = n - 1
				if n > 0:
					self._append(",")
			self._append("}")
		elif ty is types.ListType or ty is types.TupleType:
			n = len(obj)
			self._append("[")
			for item in obj:
				self._write(item)
				n = n - 1
				if n > 0:
					self._append(",")
			self._append("]")
		elif ty is types.StringType or ty is types.UnicodeType:
			self._append('"')
			obj = obj.replace('\\', r'\\')
			if self._escaped_forward_slash:
				obj = obj.replace('/', r'\/')
			obj = obj.replace('"', r'\"')
			obj = obj.replace('\b', r'\b')
			obj = obj.replace('\f', r'\f')
			obj = obj.replace('\n', r'\n')
			obj = obj.replace('\r', r'\r')
			obj = obj.replace('\t', r'\t')
			self._append(obj)
			self._append('"')
		elif ty is types.IntType or ty is types.LongType:
			self._append(str(obj))
		elif ty is types.FloatType:
			self._append("%f" % obj)
		elif obj is True:
			self._append("true")
		elif obj is False:
			self._append("false")
		elif obj is None:
			self._append("null")
		else:
			raise WriteException, "Cannot write in JSON: %s" % repr(obj)

def write(obj, escaped_forward_slash=False):
	return JsonWriter().write(obj, escaped_forward_slash)

def read(s):
	return JsonReader().read(s)
