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

WFMsgPackTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2018 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

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

local WFMsgPackTest = {}

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

local WFMsgPack = require 'WFMsgPack'
local KSDate = require 'KSDate'
local WFLogger = require 'WFLogger'
local WFTableArchiving = require 'WFTableArchiving'
local KSTableUtils = require 'KSTableUtils'

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

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

local timings = {}
local logger = WFLogger 'WFMsgPackTest'
logger:enable( 'print' )

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

local WFMsgPack_pack = assert( WFMsgPack.pack )
local WFMsgPack_unpack = assert( WFMsgPack.unpack )
local WFMsgPack_unpackDocValue =  assert( WFMsgPack.unpackDocValue )

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

local measureFn = function( key, fn, ... )

	local start = KSDate.timeNowForAnimation()

	local resulta, resultb, resultc, resultd = fn( ... )

	local duration = KSDate.timeNowForAnimation() - start

	timings[ key ] = timings[ key ] or {}
	
	table.insert( timings[ key ], duration )

	return resulta, resultb, resultc, resultd

end

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

local tableToString = function( ... )

	return measureFn( 'tableToString', WFMsgPack_pack, ... ) 

end

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

local stringToTable =  function( ... )

	return measureFn( 'stringToTable', WFMsgPack_unpack, ... )

end 

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

local wf_tableToString = function( ... )

	return measureFn( 'tableToString', WFTableArchiving.tableToString, ... ) 

end

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

local valueFnEnv = {}

local wf_stringToTable =  function( ... )

	local loadStringSandboxed = function( value )
	
		local valueFn = loadstring( 'return ' .. value )
		assert( type( valueFn ) == 'function' )
		setfenv( valueFn, valueFnEnv ) -- disallow all function calls
		
		return valueFn()
		
	end
	
	return measureFn( 'stringToTable', loadStringSandboxed, ... )

end 

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

local docValue = {
	importSource = {
		contentType = "image/jpeg",
		fileName = "_8150234.JPG",
		fileSize = 3769357,
		importTimestamp = "2018-09-12T15:33:19Z",
		importedBy = "d220fabbdac80b98588dd19136554396",
		importedOnDevice = "kokufuu",
		originalHeight = 3888,
		originalWidth = 5184,
		sha256 = "b3fd89cad48a13198232fd677546f23d52d18d0b05f4a84c86b1bec11356723e",
	},
	subtype = "image",
	type = "asset",
	userCreated = "2018-09-21T16:19:04.477Z",
	userUpdated = "2018-09-21T16:19:04.477Z",
	xmp = {
		aux = {
			Lens = "OLYMPUS M.12-40mm F2.8",
			SerialNumber = "BHUA17079",
		},
		dc = {
			creator = {
				"Matthew Johnson",
			},
			description = "OLYMPUS DIGITAL CAMERA",
			rights = "Matthew Johnson",
		},
		exif = {
			ApertureValue = {
				2970854,
				1000000,
			},
			DateTimeOriginal = "2017-08-15T10:18:05",
			ExposureBiasValue = {
				0,
				1,
			},
			ExposureProgram = "aperture priority",
			ExposureTime = {
				1,
				60,
			},
			FNumber = {
				28,
				10,
			},
			FlashFired = false,
			FlashFunction = false,
			FlashMode = "compulsory flash suppression",
			FlashRedEyeMode = false,
			FlashReturn = "no strobe return detection",
			FocalLength = {
				12,
				1,
			},
			FocalLengthIn35mmFilm = 24,
			ISOSpeedRatings = 3200,
			LightSource = "unknown",
			MaxApertureValue = {
				768,
				256,
			},
			MeteringMode = "pattern",
			ShutterSpeedValue = {
				5906891,
				1000000,
			},
		},
		photoshop = {
			DateCreated = "2017-08-15T10:18:05",
		},
		photoshop2 = {
			DateCreated = "2017-08-15T10:18:05",
		},
		photoshop3 = {
			DateCreated = "2017-08-15T10:18:05",
		},
		photoshop4 = {
			DateCreated = "2017-08-15T10:18:05",
		},
		tiff = {
			Make = "OLYMPUS CORPORATION",
			Model = "E-M1MarkII",
			Orientation = "top left",
		},
		xmp = {
			CreateDate = "2017-08-15T10:18:05",
			CreatorTool = "Version 1.2",
			ModifyDate = "2017-08-15T10:18:05",
		},
	},
	
}

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

local function packAndUnpack( testName, t )

	WFMsgPackTest[ "rxTest" .. testName ] = function( rxTest )

		local packedStr = WFMsgPack_pack( t )
		local t2 = WFMsgPack_unpack( packedStr )
		local t3 = WFMsgPack_unpackDocValue( packedStr )
	
		rxTest:expectEqual( KSTableUtils.deepEquals( t, t2 ), true )
		
		rxTest:expectEqual( KSTableUtils.deepEquals( t2, t3), true )
		
	end

end

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

local function packAndUnpackError( testName, t )

	WFMsgPackTest[ "rxTest_error_" .. testName ] = function( rxTest )

		local packedStr = WFMsgPack_pack( t )
		local t2 = WFMsgPack_unpack( packedStr )
		
		rxTest:expectEqual( KSTableUtils.deepEquals( t, t2 ), false )
	
	end

end

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

packAndUnpack( 'negativeNumbers',
	{ -1, -128, -129, -32768, -32769, -2147483648, -2147483649 } )

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

packAndUnpack( 'arrayAfterFalse',
	{ 42, "hi", false, true, 5, 6, foo = { "foo" } } )

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

packAndUnpack( 'arrayWithFloatKey',
	{ 42, 45, 98, 99, [ 3.14 ] = "oddball" } )

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

packAndUnpack( 'arrayWithNegativeKey',
	{ 42, 45, 98, 99, [ -87 ] = "oddball" } )

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

packAndUnpack( 'arrayWithGap',
	{ 42, 45, 98, 99, nil, "six" } )

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

packAndUnpack( 'emptyTable', {} )

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

packAndUnpack( 'nestedEmptyTable', { foo = {} } )

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

packAndUnpack( 'emptyStringAsKey',
	{ [ "" ] = "empty", [ "haha" ] = "tricked you!" } )

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

packAndUnpack( 'nonIdentifierAsKey',
	{ [ "%" ] = "percent" } )

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

packAndUnpack( 'leadingDigitInKey',
	{ [ "4sale" ] = "percent" } )

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

packAndUnpack( 'internalDigitInKey',
	{ [ "sale4you" ] = "percent" } )

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

packAndUnpack( 'keywordAsKey',
	{ [ "end" ] = "near" } )

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

packAndUnpack( 'embeddedZero',
	{ zero = "one\0zero" } )

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

packAndUnpack( 'doesntUseQuoteSyntaxUnnecessarily',
	{ expires = "2014-03-13T21:46:17.705Z" } )

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

packAndUnpack( 'stringContainingQuotes',
	{ zero = 'foo"bar"blah' } )

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

packAndUnpack( 'nestedStringContainingQuotes',
	{
		error = {
			message = "[string \"WFSQLiteDatabase.lua\"]:915: WFDocStoreImp:setDocValue: _changeCounter :new: does not match existing document change counter 1",
			time = "2014-03-13T21:45:47.704Z",
		},
	} )
--------------------------------------------------------------------------------

packAndUnpack( 'booleanTrueAsKey',
	{ [ true ] = 1 } )

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

packAndUnpack( 'booleanFalseAsKey',
	{ [ false ] = 0 } )

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

do
	local t = { }

	for i = 0, 255 do
		local s = string.char( i )
		t[ s ] = s
	end

	packAndUnpack( "char0thru255)", t )
end

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

packAndUnpack( 'embeddedQuotes',
	{ [ "\"" ] = "\"" } )

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

packAndUnpack( 'mightBlowLuaStack',
	{ {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}},
	  {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}},
	  {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}},
	  {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}},
	  {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, {{}}, } )

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

packAndUnpack( 'docValue', docValue )

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

packAndUnpackError( 'invalidKeyType', { [ {} ] = "nope" } )

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

packAndUnpackError( 'invalidValueType', { function() end } )

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

local KSUserdataProxy = assert( require 'KSUserdataProxy' )
local factory = KSUserdataProxy.makeFactory()
local proxy = factory()
assert( type( proxy ) == 'userdata' )

packAndUnpackError( 'userdata', { a = 1, b = proxy, c = 2 } )

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

local function gatherTotalsForKey( key )

	local totalTime = 0

	for _, timing in ipairs( timings[ key ] or {} ) do
	
		totalTime = totalTime + timing
	
	end

	return totalTime

end

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

local function gatherPerformanceStats( numDocs ) 

	local serializationTime = gatherTotalsForKey( 'tableToString' )

	local deserializationTime = gatherTotalsForKey( 'stringToTable' )
		
	local verifyStringTime = gatherTotalsForKey( 'verifyString' )

	logger:infof( "Serialization Time (%d docs): %s", numDocs, serializationTime )
	logger:infof( "Deserialization Time (%d docs): %s", numDocs, deserializationTime )
	
	if verifyStringTime > 0 then
		logger:infof( "Verify Time (%d docs): %s", numDocs, verifyStringTime )
	end
	
	return serializationTime, deserializationTime, verifyStringTime

end

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

function WFMsgPackTest.DISABLED_rxTestCompare_testPerformance( rxTest )
	
	rxTest:setTimeLimit( 60 )
	
	logger:infof( "Testing MsgPack" )
	
	local numDocs = 10000
	
	for i = 1, numDocs do
	
		local str = tableToString( docValue )
		
		local tbl = stringToTable( str )
		
		assert( KSTableUtils.deepEquals( docValue, tbl ) )
		
	end
	
	gatherPerformanceStats( numDocs )

	timings = {}
	
	logger:info( "-------------------------------------" )
	
	logger:infof( "Testing WFTableArchiving" )
	
	for i = 1, numDocs do
	
		local str = wf_tableToString( docValue )
		wf_stringToTable( str )

	end
	
	gatherPerformanceStats( numDocs )
	
end

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

local perfNumDocs = 100000

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

function WFMsgPackTest.rxTestUnpack_testPerformance( rxTest )
	
	-- SPAM [jandrew 2021-10-28]: This test takes ~2.4s on my MBP.
	
	rxTest:setTimeLimit( 60 )
	
	local str = WFMsgPack_pack( docValue )
	
	-- Sanity Test
	
	local tbl1 = WFMsgPack_unpack( str )
	local tbl2 = WFMsgPack_unpackDocValue( str )
	
	assert( KSTableUtils.deepEquals( docValue, tbl1 ) )
	assert( KSTableUtils.deepEquals( tbl1, tbl2 ) )
	
	-- Start Performance Test
	
	rxTest:measurePerformance( function()
	
		for i = 1, perfNumDocs do
		
			WFMsgPack_unpack( str )
			
		end
	
	end )
	
end

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

function WFMsgPackTest.rxTestUnpackDocValue_testPerformance( rxTest )
	
	-- SPAM [jandrew 2021-10-28]: This test takes ~1.7s on my MBP, and is
	-- roughly 30% faster than rxTestUnpack_testPerformance.
	
	rxTest:setTimeLimit( 60 )
	
	local str = WFMsgPack_pack( docValue )
	
	-- Sanity Test
	
	local tbl1 = WFMsgPack_unpack( str )
	local tbl2 = WFMsgPack_unpackDocValue( str )
	
	assert( KSTableUtils.deepEquals( docValue, tbl1 ) )
	assert( KSTableUtils.deepEquals( tbl1, tbl2 ) )
	
	-- Start Performance Test
	
	rxTest:measurePerformance( function()
	
		for i = 1, perfNumDocs do
		
			WFMsgPack_unpackDocValue( str )
			
		end
	
	end )
	
end

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

function WFMsgPackTest.rxTestBug_WF_132( rxTest )
	
   local bugDoc = {
		type = 'asset',
		subtype = 'image',
		_id = 10,
		importSource = {
			originalWidth = 30,
			originalHeight = 20,
		},
		captureDate = '2012-08-15T15:22:29',
		xmp = {
			["mwg-rs"] = {
				   Regions = {
					   AppliedToDimensions = {
						   w = 100,
						   h = 33,
						   unit = "pixel"
					   },
					   RegionList = {
						   e72de4e2 = {
							   Area = {
								   x = .05,
								   y = .03,
								   w = .11,
								   h = .13,
								   unit = "normalized"
							   },
							   Type = "Face",
							   Name = "Joe",
							   Description = "Joe Blow",
							   Extensions = {
								   ["adobe-fi"] = {
									   Version = 1,
									   Application = "Mona3",
									   Timestamp = "2017-03-08T22:04:36.193Z",
									   ConfidenceLevel = 10,
									   Quality = 66,
									   People = {
											personId = {
												ConfidenceLevel = 70,
												Confirmed = true
											},
											personId2 = {
												ConfidenceLevel = 50,
												Rejected = true
											}
									   },
								   }
							   }
						   },
					   },
				   },
			   },
		},
	}

	local str = tableToString( bugDoc )
	
	local tbl = stringToTable( str )
	
	assert( KSTableUtils.deepEquals( bugDoc, tbl ) )
	
end

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

return WFRxTestCase.wrapSuite( 'WFMsgPackTest', WFMsgPackTest )
