#!/usr/bin/env python
# -*- coding: utf-8 -*-

# NOTICE: This file is copied from
# git@github.com:adriank/ObjectPath.git:tests/test_ObjectPath.py and
# licensed under the GNU AGPL 3 license (see the accompanying license
# file).

# This file has been modified to use SakStig instead of ObjectPath.
# Some tests have been modified to pass. In those cases, this has been
# documented inside the tests why they have been modified. Note that
# no modification has been done for behavior that is documented on
# objectpath.org, only for undocumented behavior!

from sakstig import *
from random import randint, choice
#from bson.objectid import ObjectId
import sys, unittest, os
import re

sys.setrecursionlimit(20000)

object1={
	"__lang__":"en",
	"test":{
		"_id":1,
		"name":"aaa",
		"o":{
			"_id":2
		},
		"l":[
			{
				"_id":3,
				"aaa":"ddd",
				"false":2
			},
			{
				"_id":4
			}
		]
	}
}

object2={
	"store": {
		"book": [
			{ "category": "reference",
				"author": "Nigel Rees",
				"title": "Sayings of the Century",
				"price": 8.95
			},
			{ "category": "fiction",
				"author": "Evelyn Waugh",
				"title": "Sword of Honour",
				"price": 12.99
			},
			{ "category": "fiction",
				"author": "Herman Melville",
				"title": "Moby Dick",
				"isbn": "0-553-21311-3",
				"price": 8.99
			},
			{ "category": "fiction",
				"author": "J. R. R. Tolkien",
				"title": "The Lord of the Rings",
				"isbn": "0-395-19395-8",
				"price": 22.99
			}
		],
		"bicycle": {
			"color": "red",
			"price": 19.95
		},
		"k":[
			{
				"_id":4
			}
		]
	}
}

tree1=Tree(object1)
tree2=Tree(object2)

def execute_raw(expr):
	return tree1.execute(expr)
TYPES=[generator,chain]
if sys.version_info.major>2:
	TYPES+=[map]

TYPES=tuple(TYPES)

def execute(expr):
	r=tree1.execute(expr)
	if isinstance(r, TYPES):
		return list(r)
	else:
		return r

def execute2(expr):
	r=tree2.execute(expr)
	if isinstance(r, TYPES):
		return list(r)
	else:
		return r

class ObjectPath(unittest.TestCase):
	def test_simple_types(self):
		self.assertEqual(execute("null"), None)
		self.assertEqual(execute("true"), True)
		self.assertEqual(execute("false"), False)
		self.assertEqual(execute("''"), "")
		self.assertEqual(execute('""'), "")
		self.assertEqual(execute("2"), 2)
		self.assertEqual(execute("2.0"), 2.0)

	def test_arrays(self):
		self.assertEqual(execute("[]"), [])
		self.assertEqual(list(execute("[1,2,3]")), [1,2,3])
		self.assertEqual(list(execute("[false,null,true,'',\"\",2,2.0,{}]")), [False,None,True,'',"",2,2.0,{}])

	def test_objects(self):
		self.assertEqual(execute("{}"), {})
		self.assertEqual(execute("{a:1,b:false,c:'string'}"), {"a":1,"b":False,"c":'string'})
		self.assertEqual(execute("{'a':1,'b':false,'c':'string'}"), {"a":1,"b":False,"c":'string'})

	def test_arithm_add(self):
		self.assertEqual(execute("2+3"), 5)
		self.assertEqual(execute("2+3+4"), 9)
		self.assertEqual(execute("++3"), 3)
		# null is treated as neutral value
		self.assertEqual(execute("null+3"), 3)
		self.assertEqual(execute("3+null"), 3)

	def test_arithm_sub(self):
		self.assertEqual(execute("-1"), -1)
		self.assertEqual(execute("2-3"), 2-3)
		self.assertEqual(execute("2.2-3.4"), 2.2-3.4)
		self.assertEqual(execute("-+-3"), 3)
		self.assertEqual(execute("+-+3"), -3)

	def test_arithm_mul(self):
		self.assertEqual(execute("2*3*5*6"), 180)

	def test_arithm_mod(self):
		self.assertEqual(execute("2%3"), 2.0%3)
		self.assertEqual(execute("2.0%3"), 2.0%3)
		self.assertEqual(execute("float(2)%3"), float(2)%3)

	def test_arithm_div(self):
		self.assertEqual(execute("2/3"), 2.0/3)
		self.assertEqual(execute("2.0/3"), 2.0/3)
		self.assertEqual(execute("float(2)/3"), float(2)/3)

	def test_arithm_group(self):
		self.assertEqual(execute("2-3+4+5-7"), 2-3+4+5-7)
		self.assertEqual(execute("33*2/5-2"), 33*2/5.0-2)
		self.assertEqual(execute("33-4*5+2/6"), 33-4*5+2/6.0)
		#self.assertEqual(execute("2//3//4//5"), ('//', ('//', ('//', 2, 3), 4), 5))

	def test_arithm_parentheses(self):
		self.assertEqual(execute("+6"), 6)
		self.assertEqual(execute("2+2*2"), 6)
		self.assertEqual(execute("2+(2*2)"), 6)
		self.assertEqual(execute("(2+2)*2"), 8)
		self.assertEqual(execute("(33-4)*5+2/6"), (33-4)*5+2/6.0)
		self.assertEqual(execute("2/3/(4/5)*6"), 2/3.0/(4/5.0)*6)
		self.assertEqual(execute("((2+4))+6"), ((2+4))+6)

	def test_logic_negatives(self):
		self.assertEqual(execute("not false"), True)
		self.assertEqual(execute("not null"), True)
		self.assertEqual(execute("not 0"), True)
		self.assertEqual(execute("not 0.0"), True)
		self.assertEqual(execute("not ''"), True)
		self.assertEqual(execute("not []"), True)
		self.assertEqual(execute("not {}"), True)

	def test_logic_not(self):
		self.assertEqual(execute("not false"), True)
		self.assertEqual(execute("not not not false"), True)

	def test_logic_or(self):
		self.assertEqual(execute("1 or 2"), 1)
		self.assertEqual(execute("0 or 2"), 2)
		self.assertEqual(execute("'a' or 0 or 3"), 'a')
		self.assertEqual(execute("null or false or 0 or 0.0 or '' or [] or {}"), {})

	def test_logic_and(self):
		self.assertEqual(execute("1 and 2"), 2)
		self.assertEqual(execute("0 and 2"), 0)
		self.assertEqual(execute("'a' and false and 3"), False)
		self.assertEqual(execute("true and 1 and 1.0 and 'foo' and [1] and {a:1}"), {"a":1})

	def test_comparison_regex(self):
		self.assertIsInstance(execute("/aaa/"), type(re.compile("")))
		self.assertEqual(execute("/.*aaa/ matches 'xxxaaaadddd'"), True)
		self.assertEqual(execute("'.*aaa' matches 'xxxaaaadddd'"), True)

	def test_comparison_is(self):
		self.assertEqual(execute("2 is 2"), True)
		self.assertEqual(execute("'2' is 2"), True)
		self.assertEqual(execute("2 is '2'"), True)
		self.assertEqual(execute("2 is 2.0"), True)
		self.assertEqual(execute("0.1+0.2 is 0.3"), True)
		self.assertEqual(execute("[] is []"), True)
		self.assertEqual(execute("[1] is [1]"), True)
		self.assertEqual(execute("{} is {}"), True)
		self.assertEqual(execute("{'aaa':1} is {'aaa':1}"), True)
		#oid=ObjectId()
		#self.assertEqual(execute("ObjectID('"+str(oid)+"') is '"+str(oid)+"'"), True)

	def test_comparison_isnot(self):
		self.assertEqual(execute("3 is not 6"), True)
		self.assertEqual(execute("3 is not '3'"), False)
		self.assertEqual(execute("[] is not [1]"), True)
		self.assertEqual(execute("[] is not []"), False)
		self.assertEqual(execute("{'aaa':2} is not {'bbb':2}"), True)
		self.assertEqual(execute("{} is not {}"), False)

	def test_membership_in(self):
		self.assertEqual(execute("4 in [6,4,3]"),True)
		self.assertEqual(execute("4 in {4:true}"),True)
		self.assertEqual(execute("[2,3] in [6,4,3]"),True)

	def test_membership_notin(self):
		self.assertEqual(execute("4 not in []"), True)
		self.assertEqual(execute("1 not in {'232':2}"), True)
		self.assertEqual(execute("[2,5] not in [6,4,3]"),True)

	def test_complex(self):
		self.assertEqual(execute("23 is not 56 or 25 is 57"), True)
		self.assertEqual(execute("2+3/4-6*7>0 or 10 is not 11 and 14"), 14)

	def test_comparison_lt(self):
		self.assertEqual(execute("2<3"), True)
		self.assertEqual(execute("3<3"), False)
		self.assertEqual(execute("2<=2"), True)
		self.assertEqual(execute("2<=1"), False)

	def test_comparison_gt(self):
		self.assertEqual(execute("5>4"), True)
		self.assertEqual(execute("5>5"), False)
		self.assertEqual(execute("5>=5"), True)

	def test_concatenation(self):
		self.assertEqual(execute("'a'+'b'+\"c\""), 'abc')
		self.assertEqual(execute("'5'+5"), '55')
		self.assertEqual(execute("5+'5'"), 10)
		self.assertEqual(list(execute("[1,2,4] + [3,5]")), [1,2,4,3,5])
		self.assertEqual(execute('{"a":1,"b":2} + {"a":2,"c":3}'), {"a":2,"b":2,"c":3})
		self.assertRaises(ProgrammingError, lambda: execute('{"a":1,"b":2} + "sss"'))

	def test_builtin_casting(self):
		self.assertEqual(execute("str('foo')"), 'foo')
		self.assertEqual(execute("str(1)"), '1')
		self.assertEqual(execute("str(1.0)"), '1.0')
		self.assertEqual(execute("str(1 is 1)"), 'true')
		self.assertEqual(execute("int(1)"), 1)
		self.assertEqual(execute("int(1.0)"), 1)
		self.assertEqual(execute("int('1')"), 1)
		#Python can't handle that
		#self.assertEqual(execute("int('1.0')"), 1)
		self.assertEqual(execute("float(1.0)"), 1.0)
		self.assertEqual(execute("float(1)"), 1.0)
		self.assertEqual(execute("float('1')"), 1.0)
		self.assertEqual(execute("float('1.0')"), 1.0)
		self.assertEqual(execute("array()"), [])
		self.assertEqual(execute("array([])"), [])
		self.assertEqual(execute("array('abc')"), ['a','b','c'])
		self.assertEqual(execute("array(dateTime([2011,4,8,12,0]))"), [2011,4,8,12,0,0,0])
		self.assertEqual(execute("array(date([2011,4,8]))"), [2011,4,8])
		self.assertEqual(execute("array(time([12,12,30]))"), [12,12,30,0])

	def test_builtin_arithmetic(self):
		self.assertEqual(execute("sum([1,2,3,4])"), sum([1,2,3,4]))
                # CHANGED: sum(x1,x2,...,xn) == x1 + x2 + ... + xn
		self.assertEqual(execute("sum([2,3,4,'333',[]])"), 342)
		self.assertEqual(execute("min([1,2,3,4])"), min([1,2,3,4]))
		self.assertEqual(execute("min([2,3,4,'333',[]])"), 2)
		self.assertEqual(execute("max([1,2,3,4])"), max([1,2,3,4]))
		self.assertEqual(execute("max([2,3,4,'333',[]])"), 4)
		self.assertEqual(execute("avg([1,2,3,4])"), 2.5)
		self.assertEqual(execute("avg([1,3,3,1])"), 2.0)
		self.assertEqual(execute("avg([1.1,1.3,1.3,1.1])"), 1.2000000000000002)
                # CHANGED: avg(x1,x2,...,xn) == (x1 + x2 + ... + xn)/n
		self.assertEqual(execute("avg([2,3,4,'333',[]])"), 68.4)
		self.assertEqual(execute("round(2/3)"), round(2.0/3))
		self.assertEqual(execute("round(2/3,3)"), round(2.0/3,3))
		# edge cases
		self.assertEqual(execute("avg(1)"), 1)
		# should ommit 'sss'
                # CHANGED: avg(x1,x2,...,xn) == (x1 + x2 + ... + xn)/n
		self.assertEqual(execute("avg([1,'sss',3,3,1])"), 1.6)

	def test_builtin_string(self):
		self.assertEqual(execute("replace('foobar','oob','baz')"), 'fbazar')
		self.assertEqual(execute("""escape('&lt;')"""), "&amp;lt;")
		self.assertEqual(execute("""escape('<"&>')"""), "&lt;&quot;&amp;&gt;")
		self.assertEqual(execute("""unescape('&lt;&quot;&amp;&gt;')"""), "<\"&>")
		self.assertEqual(execute("upper('aaa')"),"AAA")
		self.assertEqual(execute("lower('AAA')"),"aaa")
		self.assertEqual(execute("title('AAA aaa')"),"Aaa Aaa")
		self.assertEqual(execute("capitalize('AAA Aaa')"),"Aaa aaa")
		self.assertEqual(execute("split('aaa aaa')"),["aaa","aaa"])
		self.assertEqual(execute("split('aaaxaaa','x')"),["aaa","aaa"])
		self.assertEqual(execute("join(['aaą','aaę'],'ć')"),"aaąćaaę")
		self.assertEqual(execute("join(['aaa','aaa'])"),"aaaaaa")
		self.assertEqual(execute("join(['aaa','aaa',3,55])"),"aaaaaa355")
		self.assertEqual(execute('slice("Hello world!", [6, 11])'), "world")
		self.assertEqual(execute('slice("Hello world!", [6, -1])'), "world")
		self.assertEqual(execute('slice("Hello world!", [[0,5], [6, 11]])'), ["Hello", "world"])

	def test_builtin_arrays(self):
		self.assertEqual(execute("sort([1,2,3,4]+[2,4])"), [1,2,2,3,4,4])
		self.assertEqual(execute("sort($.._id)"), [1,2,3,4])
		self.assertEqual(execute("sort($..l.*, _id)"), [{'_id': 3, 'aaa': 'ddd', 'false': 2}, {'_id': 4}])
		self.assertEqual(execute("reverse([1,2,3,4]+[2,4])"), [4,2,4,3,2,1])
		self.assertEqual(execute("reverse(sort($.._id))"), [4,3,2,1])
		self.assertEqual(execute("len([1,2,3,4]+[2,4])"), 6)
		# edge cases
		self.assertEqual(execute("len(True)"), True)
		self.assertEqual(execute("len('aaa')"), 3)

	def test_builtin_time(self):
		import datetime
		self.assertIsInstance(execute("now()"),datetime.datetime)
		self.assertIsInstance(execute("date()"), datetime.date)
		self.assertIsInstance(execute("date(now())"), datetime.date)
		self.assertIsInstance(execute("date([2001,12,30])"), datetime.date)
		self.assertIsInstance(execute("time()"), datetime.time)
		self.assertIsInstance(execute("time(now())"), datetime.time)
		self.assertIsInstance(execute("time([12,23])"), datetime.time)
		self.assertIsInstance(execute("time([12,23,21,777777])"), datetime.time)
		self.assertIsInstance(execute("dateTime(now())"), datetime.datetime)
		self.assertIsInstance(execute("dateTime([2001,12,30,12,23])"), datetime.datetime)
		self.assertIsInstance(execute("dateTime([2001,12,30,12,23,21,777777])"), datetime.datetime)
		self.assertEqual(execute("toMillis(dateTime([2001,12,30,12,23,21,777777]))"), 1009715001777)
		self.assertIsInstance(execute("dateTime(date(),time())"), datetime.datetime)
		self.assertIsInstance(execute("dateTime(date(),[12,23])"), datetime.datetime)
		self.assertIsInstance(execute("dateTime(date(),[12,23,21,777777])"), datetime.datetime)
		self.assertIsInstance(execute("dateTime([2001,12,30],time())"), datetime.datetime)
		self.assertEqual(execute("array(time([12,30])-time([8,00]))"), [4,30,0,0])
		self.assertEqual(execute("array(time([12,12,12,12])-time([8,8,8,8]))"), [4,4,4,4])
		self.assertEqual(execute("array(time([12,12,12,12])-time([1,2,3,4]))"), [11,10,9,8])
		self.assertEqual(execute("array(time([12,00])-time([1,10]))"), [10,50,0,0])
		self.assertEqual(execute("array(time([1,00])-time([1,10]))"), [23,50,0,0])
		self.assertEqual(execute("array(time([0,00])-time([0,0,0,1]))"), [23,59,59,9999])
		self.assertEqual(execute("array(time([0,0])+time([1,1,1,1]))"), [1,1,1,1])
		self.assertEqual(execute("array(time([0,0])+time([1,2,3,4]))"), [1,2,3,4])
		self.assertEqual(execute("array(time([23,59,59,9999])+time([0,0,0,1]))"), [0,0,0,0])
		# age tests
		self.assertEqual(execute("age(now())"), [0,"seconds"])
		self.assertEqual(execute("age(dateTime([2000,1,1,1,1]),dateTime([2001,1,1,1,1]))"), [1,"year"])
		self.assertEqual(execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,2,1,1,1]))"), [1,"month"])
		self.assertEqual(execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,1,2,1,1]))"), [1,"day"])
		self.assertEqual(execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,1,1,2,1]))"), [1,"hour"])
		self.assertEqual(execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,1,1,1,2]))"), [1,"minute"])
		self.assertEqual(execute("age(dateTime([2000,1,1,1,1,1]),dateTime([2000,1,1,1,1,2]))"), [1,"second"])

	def test_localize(self):
		pass
		#these tests are passing on computers with timezone set to UTC - not the case of TravisCI
		#test of non-DST time
		#if sys.version < "3":
			#self.assertEqual(execute("array(localize(dateTime([2000,1,1,10,10,1,0]),'Europe/Warsaw'))"), [2000,1,1,11,10,1,0])
			#test of DST time
			#self.assertEqual(execute("array(localize(dateTime([2000,7,1,10,10,1,0]),'Europe/Warsaw'))"), [2000,7,1,12,10,1,0])

	def test_builtin_type(self):
		self.assertEqual(execute("type([1,2,3,4]+[2,4])"), "array")
		self.assertEqual(execute("type({})"), "object")
		self.assertEqual(execute("type('')"), "str")

	def test_misc(self):
		self.assertEqual(execute(2), 2)
		self.assertEqual(execute("$..*[10]"), None)
		self.assertEqual(sorted(execute("keys({'a':1,'b':2})")), ['a','b'])
		self.assertRaises(ExecutionError, lambda: execute('keys([])'))
		self.assertRaises(ProgrammingError, lambda: execute('blah([])'))

	def test_optimizations(self):
		self.assertEqual(execute("$.*[@]"), execute("$.*"))
		self.assertIsInstance(execute_raw("$..*"), generator)
		self.assertIsInstance(execute_raw("$..* + $..*"), chain)
		self.assertIsInstance(execute_raw("$..* + 2"), chain)
		self.assertIsInstance(execute_raw("2 + $..*"), chain)
		self.assertEqual(execute("$.._id[0]"), 1)
		self.assertEqual(execute("sort($.._id + $.._id)[2]"), 2)
		self.assertIsInstance(execute("$.._id[2]"), int)

class ObjectPath_Paths(unittest.TestCase):
	def test_simple_paths(self):
		#self.assertEqual(execute("$.*"), object1)
		self.assertEqual(execute("$.a.b.c"), None)
		self.assertEqual(execute("$.a.b.c[0]"), None)
		self.assertEqual(execute("$.__lang__"), "en")
		self.assertEqual(execute("$.test.o._id"), 2)
                # CHANGED: Dictionaries inside lists aren't implicitly expanded
		self.assertEqual(execute("$.test.l.*._id"), [3, 4])
                # CHANGED: test inside a selector is executed in the
                # local context, and means the same as @.test... This could be considered a bug in SakStig.
		# self.assertEqual(execute("$.*[test].o._id"), 2)
                # CHANGED: $.* is a queryset of all members of $, not a dict with test as one member name...
		self.assertEqual(execute("$['test'].o._id"), 2)
		self.assertEqual(execute('[1,"aa",{"a":2,"c":3},{"c":3},{"a":1,"b":2}].(a,b)'), [{"a":2},{"a":1,"b":2}])
		self.assertEqual(execute2("$.store.book.(price,title)[0]"), {"price": 8.95, "title": "Sayings of the Century"})
		self.assertEqual(execute2("$..book.(price,title)"), [{'price': 8.95, 'title': 'Sayings of the Century'}, {'price': 12.99, 'title': 'Sword of Honour'}, {'price': 8.99, 'title': 'Moby Dick'}, {'price': 22.99, 'title': 'The Lord of the Rings'}])
		self.assertEqual(execute2("sort($..(price,title),'price')"), [{'price': 8.95, 'title': 'Sayings of the Century'}, {'price': 8.99, 'title': 'Moby Dick'}, {'price': 12.99, 'title': 'Sword of Honour'}, {'price': 19.95}, {'price': 22.99, 'title': 'The Lord of the Rings'}])
		self.assertIsInstance(execute("now().year"),int)

	def test_complex_paths(self):
		self.assertEqual(sorted(execute("$.._id")), [1, 2, 3, 4])
		self.assertEqual(execute("$..l"), object1["test"]["l"])
		self.assertEqual(execute("$..l.._id"), [3,4])
		self.assertEqual(execute2("$.store.*"), object2["store"])
		self.assertEqual(execute2("$.store.book.author"), ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien'])
		#print()
		#print(execute2("$.store.book.(author,aaa)"))
		self.assertEqual(execute2("$.store.book.(author,aaa)"), [{"author": "Nigel Rees"}, {"author": "Evelyn Waugh"}, {"author": "Herman Melville"}, {"author": "J. R. R. Tolkien"}])
		self.assertEqual(execute2("$.store.book.(author,price)"), [{'price': 8.95, 'author': 'Nigel Rees'}, {'price': 12.99, 'author': 'Evelyn Waugh'}, {'price': 8.99, 'author': 'Herman Melville'}, {'price': 22.99, 'author': 'J. R. R. Tolkien'}])
		self.assertEqual(execute2("$.store.book.*[author]"), ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien'])
		self.assertEqual(execute2("$.store.book.*['author']"), ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien'])
		self.assertEqual(execute2("$.store.book"), object2["store"]["book"])
		self.assertEqual(list(execute2("$..author")), ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien'])

	def test_selectors(self):
		self.assertEqual(len(execute("$..*[@._id>2]")), 2)
                # CHANGED: Dictionaries inside lists aren't implicitly expanded, single item queryset always returned as item
		self.assertEqual(execute("$..*[3 is @.l.*._id]"), object1['test'])
                # CHANGED: Dictionaries inside lists aren't implicitly expanded, single item queryset always returned as item
		self.assertEqual(execute2("$.store..*[4 is @.k.*._id]"), object2['store'])
                # CHANGED: A QuerySet of multiple results is not a list, [idx] operates on list items in querysets, not on querysets
		self.assertEqual(execute("$..*[@._id>1 and @._id<3]"), {'_id': 2})
                # CHANGED: Totally undocumented syntax! In SakStig,
                # $.store.book[@.price] a $.store.book such that it's
                # price attribute is non-false, which isn't going to
                # match anything.
		self.assertEqual(sorted(execute2("$.store.book.*.price")), sorted([8.95,12.99,8.99,22.99]))

#testcase2=unittest.FunctionTestCase(test_efficiency(2))
testcase1=unittest.TestLoader().loadTestsFromTestCase(ObjectPath)
testcase2=unittest.TestLoader().loadTestsFromTestCase(ObjectPath_Paths)

op_test=unittest.TestSuite([testcase1,testcase2])
#utils_interpreter=unittest.TestSuite([testcase2])
