--[[----------------------------------------------------------------------------

KSJsonDecoderTest.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2010 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

	-- libs.json
local KSJsonDecoder = require 'KSJsonDecoder'
local KSTableUtils = require 'KSTableUtils'

	-- libs.table
local WFTableArchiving = require 'WFTableArchiving'


--============================================================================--

local KSJsonDecoderTest = {}

--------------------------------------------------------------------------------

-- GRUNGE [scouten 2011-02-22]: Some of this code is duplicated from
-- KSBlondeTest. Opportunity for refactoring code.

--------------------------------------------------------------------------------

local function convertTableToStringForDebugIfNeeded( value )

	if type( value ) == 'table' then
		return WFTableArchiving.tableToString( value )
	else
		return value
	end

end

--------------------------------------------------------------------------------

local compareValues, deepCompareTables

--------------------------------------------------------------------------------

-- Performs a deep comparison of two tables, nested up to 20 levels deep. Returns
-- true if every key-value pair in t1 also appears in t2 and the number of
-- key-value pairs in t1 and t2 are equal. Returns false otherwise.

function deepCompareTables( t1, t2, depth )

    if depth == 0 then
        error( "Tables are too deeply nested to be compared" )
    end
    
    local t1count = 0
    local t2count = 0
    
    for k, v in pairs( t1 ) do
        t1count = t1count + 1
        local equal = compareValues( v, t2[ k ], depth )
        if not equal then return false end
    end
    
    for k, v in pairs( t2 ) do
        t2count = t2count + 1
    end
    
    return t1count == t2count

end

--------------------------------------------------------------------------------

function compareValues( v1, v2, level )

	-- FIXME [scouten 2011-03-11 from mhamburg]: I like the deep compare implementation
	-- in general. Suggest standardizing on level v depth. Suggest testing
	-- for equality first in compareValues so that we exit rapidly for references
	-- to the same table.
	
	-- FIXME [scouten 2011-03-14]: This should move down into test utils or table utils.

    if type( v2 ) == "table" and type( v1 ) == "table" then
        if not level then level = 20 end
        return deepCompareTables( v1, v2, level - 1 )
    else
        return ( v2 == v1 )
    end
        
end

--------------------------------------------------------------------------------

local function testDecode( json, expectedLuaValue )

	local actualLuaValue = KSJsonDecoder.decodeWebSafeJson( json )

	if not compareValues( actualLuaValue, expectedLuaValue ) then
		local message = string.format(
					"KSJsonDecoder.decodeWebSafeJson: result was incorrect\n\nexpected:\n%s\n\nactual:\n%s\n",
					convertTableToStringForDebugIfNeeded( expectedLuaValue ),
					convertTableToStringForDebugIfNeeded( actualLuaValue ) )
		print( message )
		error( message )
	end

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.testBasicString()

	testDecode( [[while (1) {}
{
"status":"ok","last_event_received":"2011-02-22T21:21:40.711Z","events":[{"id":"bc63f8cecc09469fbc40fe544119251a","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2/events/bc63f8cecc09469fbc40fe544119251a","partition":{"id":"9a24b25d114c4a008ba051beb54d0ab2","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2"},"date_created":"2011-02-22T21:21:40.711Z","date_received":"2011-02-22T21:21:40.711Z","class":"favorite","value":"true"},{"id":"5667d946f3eb4733a0986a72f1d24108","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4/events/5667d946f3eb4733a0986a72f1d24108","partition":{"id":"d4e259b3dad84a629d2a32e0c074ffb4","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4"},"date_created":"2011-02-22T21:21:40.711Z","date_received":"2011-02-22T21:21:40.711Z","class":"favorite","value":"false"}]
}]],
		{
			status = 'ok',
			last_event_received = '2011-02-22T21:21:40.711Z',
			events = {
				{
					id = 'bc63f8cecc09469fbc40fe544119251a',
					href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2/events/bc63f8cecc09469fbc40fe544119251a',
					partition = {
						id = '9a24b25d114c4a008ba051beb54d0ab2',
						href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2'
					},
					date_created = '2011-02-22T21:21:40.711Z',
					date_received = '2011-02-22T21:21:40.711Z',
					class = 'favorite',
					value = 'true'
				},
				{
					id = '5667d946f3eb4733a0986a72f1d24108',
					href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4/events/5667d946f3eb4733a0986a72f1d24108',
					partition = {
						id = 'd4e259b3dad84a629d2a32e0c074ffb4',
						href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4'
					},
					date_created = '2011-02-22T21:21:40.711Z',
					date_received = '2011-02-22T21:21:40.711Z',
					class = 'favorite',
					value = 'false'
				},
			},
		}
	)
	
	-- current decode functions are for 'websafe' JSON, but are expected to work 
	-- for 'regular' JSON as well - the decode side does not have different functions
	-- for the two like the encode side	
	testDecode( [[{
		"status":"ok","last_event_received":"2011-02-22T21:21:40.711Z","events":[{"id":"bc63f8cecc09469fbc40fe544119251a","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2/events/bc63f8cecc09469fbc40fe544119251a","partition":{"id":"9a24b25d114c4a008ba051beb54d0ab2","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2"},"date_created":"2011-02-22T21:21:40.711Z","date_received":"2011-02-22T21:21:40.711Z","class":"favorite","value":"true"},{"id":"5667d946f3eb4733a0986a72f1d24108","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4/events/5667d946f3eb4733a0986a72f1d24108","partition":{"id":"d4e259b3dad84a629d2a32e0c074ffb4","href":"/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4"},"date_created":"2011-02-22T21:21:40.711Z","date_received":"2011-02-22T21:21:40.711Z","class":"favorite","value":"false"}]
		}]],
		{
			status = 'ok',
			last_event_received = '2011-02-22T21:21:40.711Z',
			events = {
				{
					id = 'bc63f8cecc09469fbc40fe544119251a',
					href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2/events/bc63f8cecc09469fbc40fe544119251a',
					partition = {
						id = '9a24b25d114c4a008ba051beb54d0ab2',
						href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/9a24b25d114c4a008ba051beb54d0ab2'
					},
					date_created = '2011-02-22T21:21:40.711Z',
					date_received = '2011-02-22T21:21:40.711Z',
					class = 'favorite',
					value = 'true'
				},
				{
					id = '5667d946f3eb4733a0986a72f1d24108',
					href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4/events/5667d946f3eb4733a0986a72f1d24108',
					partition = {
						id = 'd4e259b3dad84a629d2a32e0c074ffb4',
						href = '/v1.0/accounts/7a6b9ad57de3404cbc54305878afbc35/partitions/d4e259b3dad84a629d2a32e0c074ffb4'
					},
					date_created = '2011-02-22T21:21:40.711Z',
					date_received = '2011-02-22T21:21:40.711Z',
					class = 'favorite',
					value = 'false'
				},
			},
		}
	)

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestInvalidJson( rxTest )

	rxTest:expectError( function()
	
		KSJsonDecoder.decodeWebSafeJson( '<html><head><title>403 Forbidden</title></head></html>' )
		
	end )
	
end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestNullAtEndOfArray( rxTest )
	
	local json = '["something",null]'
	
	local val = KSJsonDecoder.decodeWebSafeJson( json )
	
	rxTest:expectEqual( { "something", KSJsonDecoder.json_null }, val )

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestVariousKeysAndValues( rxTest )
	
	local original = '[["float",1.5,"integer",5,"string","hello","empty",[],"false",false,"custom","custom json serializer","ostr_key",{"key":"value"},"obool_key",{"true":true},"onull_key",{"null":null}],10,10.3,10.3,"a string",null,false,true]' --]]
	
	local expected = { 
		{	'float',
			1.5,
			'integer',
			5,
			'string',
			'hello',
			'empty',
			{},
			'false',
			false,
			'custom',
			'custom json serializer',
			'ostr_key',
			{ key = 'value' },
			'obool_key',
			{ ['true'] = true },
			'onull_key',
			{ ['null'] = KSJsonDecoder.json_null }
		},
		10,
		10.3,
		10.3,
		'a string',
		KSJsonDecoder.json_null,
		false,
		true
	}
			
	local decoded = KSJsonDecoder.decodeWebSafeJson( original )
	
	rxTest:expectEqual( expected, decoded )

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestSkipNull( rxTest )
	
	local original = '{ "keep1": true, "keep2": true, "ignore1": null, "keep3": { "keep4": true, "ignore2": null} }'
	
	local expected = {
		keep1 = true,
		keep2 = true,
		keep3 = {
			keep4 = true
		}
	}
	
	local decoded = KSJsonDecoder.decodeWebSafeJson_stripNull( original )
	
	rxTest:expectEqual( expected, decoded )
	
end

--------------------------------------------------------------------------------

local WFWorkerThread = require "WFWorkerThread"

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestRapidJsonThreadSafety( rxTest )

	-- create two different KSJsonDecoderTestWorker threads. Each of these will
	-- get its own unique Lua state.
	local threadA = WFWorkerThread( "KSJsonDecoderTestWorker.lua", "A" )

	local threadB = WFWorkerThread( "KSJsonDecoderTestWorker.lua", "B" )

	-- now modify the LUA_REGISTRY for luaStateA
	local threadALuaRefCount = 5

	-- With useNLuaRefs, we are effectively doing
	-- 
	-- LUA_REGISTRY[ 1 ] = function() end
	-- ...
	-- LUA_REGISTRY[ i ] = function() end
	-- ...
	-- LUA_REGISTRY[ threadALuaRefCount ] = function() end
	-- 
	-- on luaStateA
	-- 
	local threadACleanupId
	rxTest
		:expectSingleValue( 
			threadA
				:request( 'useNLuaRefs', threadALuaRefCount )
				:tap( function( threadACleanupId )  threadACleanupId = threadACleanupId end ),
			':ignore:' )


	-- now modify the LUA_REGISTRY for luaStateB
	local threadBLuaRefCount = threadALuaRefCount + 10

	-- With useNLuaRefs, we are effectively doing
	-- 
	-- LUA_REGISTRY[ 1 ] = function() end
	-- ...
	-- LUA_REGISTRY[ i ] = function() end
	-- ...
	-- LUA_REGISTRY[ threadBLuaRefCount ] = function() end
	-- 
	-- on luaStateB
	-- 
	local threadBCleanupId
	rxTest
		:expectSingleValue( 
			threadB
				:request( 'useNLuaRefs', threadBLuaRefCount )
				:tap( function( threadBCleanupId )  threadBCleanupId = threadBCleanupId end ),
			':ignore:' )

	-- parse json in thread B. The first time we do this bootstraps rapidjson
	-- for luaStateB and does this on luaStateB
	-- 
	-- 		LUA_REGISTRY[ threadBLuaRefCount + 1 ] = json_null
	-- 
	-- Also, the static cpp variable values::nullref in rapidjson.cpp is set to
	-- threadBLuaRefCount + 1 in all threads.

	rxTest
		:expectOnlyCompleted( 
			threadB:request( 'testStripNull' ) )

	-- parse json in thread A. The first time we do this bootstraps rapidjson
	-- for luaStateA and does this on luaStateA
	-- 
	-- 		LUA_REGISTRY[ threadALuaRefCount + 1 ] = json_null
	-- 
	-- Also, the static cpp variable values::nullref in rapidjson.cpp is set to
	-- threadALuaRefCount + 1 in all threads.

	rxTest
		:expectOnlyCompleted( 
			threadA:request( 'testStripNull' ) )

	-- parse json in thread B again. rapidjson is already loaded into threadB,
	-- so it won't update the luaStateB registry and it won't update the static
	-- cpp variable values:nullref 
	-- 
	-- That means value::nullref is still threadALuaRefCount + 1 for both
	-- threadA _and_ threadB
	-- 
	-- However, in luaStateB, json_null is actually stored at
	-- 
	-- 	LUA_REGISTRY[ threadBLuaRefCount+ 1 ]
	-- 	
	-- but instead, we are going to look up
	-- 
	-- 	LUA_REGISTRY[ threadALuaRefCount+ 1 ]
	-- 	
	-- in luaStateB. And from above, since 
	-- 
	-- 	threadALuaRefCount + 1 < threadBLuaRefCount
	-- 	
	-- we are now going to insert something other than json_null into the
	-- parsed json. This will cause strip null to fail because it compares 
	-- the inserted value with json_null when stripping

	rxTest
		:expectOnlyCompleted( 
			threadB:request( 'testStripNull' ) )

	-- cleanup
	rxTest
		:expectOnlyCompleted( 
			threadA:request( 'cleanUpLuaRefUsages', threadALuaRefCount ) )

	threadA:close()

	rxTest
		:expectOnlyCompleted( 
			threadB:request( 'cleanUpLuaRefUsages', threadBLuaRefCount ) )

	threadB:close()

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestIgnoreEmpty( rxTest )
	
	local original = ""
	
	rxTest:expectEqual( {}, KSJsonDecoder.decodeWebSafeJson( original, true ) )
	
	-- when not specified or false, we don't actually do anything different in terms
	-- of parsing, but if not ignoreEmpty then the underlying code logs (can't easily
	-- verify that in a unit test)
	rxTest:expectEqual( {}, KSJsonDecoder.decodeWebSafeJson( original, false ) )

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestWeirdNumbers( rxTest )
	
	-- SPAM [matthewj 2018-07-23]: As of this comment, this test mostly validates 
	-- the behavior of the current code, I do not believe we are relying on the
	-- way this works in WF or the communication with Oz as we do not transit 
	-- data that looks like this.
	
	-- -0 is converted to 0 (this means we can't roundtrip -0)
	rxTest:expectEqual( { 0 }, KSJsonDecoder.decodeWebSafeJson( '[-0]' ) )
	
	-- pretty large number ok
	rxTest:expectEqual( { 1.55e308 }, KSJsonDecoder.decodeWebSafeJson( '[ 1.55e+308 ]' ) )
	rxTest:expectEqual( { -1.55e308 }, KSJsonDecoder.decodeWebSafeJson( '[ -1.55e+308 ]' ) ) 
	
	rxTest:expectSpecificError( 'invalid JSON data', KSJsonDecoder.decodeWebSafeJson, '[ 1.55e+309 ]' )
	rxTest:expectSpecificError( 'invalid JSON data', KSJsonDecoder.decodeWebSafeJson, '[ -1.55e+309 ]' )
	
	-- tiny number.  table comparison doesn't like these much so...turn to string and see how it goes
	local pp = require 'WFTableArchiving'.tableToString
	rxTest:expectEqual( pp( { 1.55e-300 } ), pp( KSJsonDecoder.decodeWebSafeJson( '[ 1.55e-300 ]' ) ) )
	rxTest:expectEqual( pp( { -1.55e-300 } ), pp( KSJsonDecoder.decodeWebSafeJson( '[ -1.55e-300 ]' ) ) )
	
	-- basically 0
	rxTest:expectEqual( { 0 }, KSJsonDecoder.decodeWebSafeJson( '[ 1.55e-500 ]' ) )
	rxTest:expectEqual( { -0 }, KSJsonDecoder.decodeWebSafeJson( '[ -1.55e-500 ]' ) )

end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestNumberInStringKey( rxTest )
	
	-- Thanks to agentzh for this bug report!
	
	local original = '{ "2":"two"}'
	
	rxTest:expectEqual( { [ "2" ] = "two" }, KSJsonDecoder.decodeWebSafeJson( original ) )
	
end

--------------------------------------------------------------------------------

function KSJsonDecoderTest.rxTestDeepJson( rxTest )

	local original = [[{"1":{"2":{"3":{"4":{"5":{"6":{"7":{"8":{"9":{"10":{"11":{"12":{"13":{"14":{"15":{}}}}}}}}}}}}}}}}]]	
	
	rxTest:expectEqual(
	{
		["1"] = {
			["2"] = {
				["3"] = {
					["4"] = {
						["5"] = {
							["6"] = {
								["7"] = {
									["8"] = {
										["9"] = {
											["10"] = {
												["11"] = {
													["12"] = {
														["13"] = {
															["14"] = {
																["15"] = {
																},
															},
														},
													},
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
	}, KSJsonDecoder.decodeWebSafeJson( original ) )
	
	original = "[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]"
	
	rxTest:expectEqual( true, KSTableUtils.deepEquals( {{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}, 
									KSJsonDecoder.decodeWebSafeJson( original ), 30 ) )
	
end

--------------------------------------------------------------------------------

return WFRxTestCase.wrapSuite( 'KSJsonDecoderTest', KSJsonDecoderTest )
