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

NmbSessionLoggerTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2020 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.

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

--==============================================================================
-- Skip all tests logic
--==============================================================================
-- 2020.02.10 bkruse
-- 
-- This is a desktop only file for now, make this file a noOp on other plats
local WFPlatformInfo = require 'WFPlatformInfo'

if WFPlatformInfo.isAndroid then return {} end

if WFPlatformInfo.isIOS then return {} end

--==============================================================================
-- normal test execution starts below
--==============================================================================

local Rx = require 'Rx'

	-- wf.core
local KSPathUtils = require 'KSPathUtils'
local WFFileUtils = require 'WFFileUtils'
local WFFunctionUtils = require 'WFFunctionUtils'
local WFRxTestCase = require 'WFRxTestCase'

	-- NmbSessionLogger
local NmbSessionLogger = require 'NmbSessionLogger'

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

local _dbFolderId

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

local function getNewDbPath( rxTest )

	_dbFolderId = ( _dbFolderId or 0 ) + 1

	local parentFolder =
		WFFileUtils.ensureFileNameIsUnique(
			KSPathUtils.child(
				KSPathUtils.getStandardFilePath( "temp" ),
				string.format( "NmbSessionLoggerTest%s", _dbFolderId ) ) )

	local ok = pcall( WFFileUtils.mkdir, parentFolder )

	rxTest:expectEqual( ok, true )

	local dbPath = 
			NmbSessionLogger.getSessionLogDbPathWithParentFolder(
			parentFolder )

	rxTest:finally(
		function()

			WFFileUtils.delete( dbPath )

		end )

	return dbPath

end

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

local function subscribeToLoggedEventStream( loggedEventStream, 
												sessionStartContainer )

	return loggedEventStream
				:subscribe(
					function( when )

						sessionStartContainer[ 1 ] = when

					end )

end

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

local function makeNELementArray( n, f, ... )

	local ret = {}

	for i = 1, n do

		ret[ i ] = f( i, ... )

	end

	return ret

end

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

local function makeEventStreamEvent( i, eventStream )

	return { eventStream, 
				i % 3 == 1 and "info" or i % 3 == 2 and "warn" or "error", 
				string.format( "event %s", i ) }

end

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

local function sendEventAndSleep( packedEventStreamAndEvent, rxTest )

	local eventStream = packedEventStreamAndEvent[ 1 ]

	eventStream
		:onNext( unpack( packedEventStreamAndEvent, 2 ) )

	rxTest:sleep( 0.01 )

end

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

local function makeExpectedEventStreamResult( 
				packedEventStreamAndEvent, getNameAndSessionIdFunc, ... )

	local eventStream, eventLevel, event = unpack( packedEventStreamAndEvent )

	local eventStreamName, sessionId =
			getNameAndSessionIdFunc(
				eventStream, eventLevel, event, ... )

	return {

			eventStreamName,

			sessionId,

			eventLevel,

			event

	}

end

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

local function getEventStream1OrEventStream2NameAndSessionId(
				eventStream, _, _, eventStream1, eventStream1SessionId,
				eventStream2SessionId )

	local eventStreamName = 
			eventStream == eventStream1 and "eventStream1" or "eventStream2"

	local sessionId =
			eventStream == eventStream1 and 
				eventStream1SessionId or eventStream2SessionId

	return eventStreamName, sessionId

end

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

local function returnEventStreamNameAndSessionId(
				_, _, _, eventStreamName, sessionId )

	
	return eventStreamName, sessionId

end

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

local _sessionsPerPath = {}

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

local function addEventStreamSession( _, rxTest, eventStream, 
										eventStreamName, eventDbPath, 
										maxSessionCount )

	_sessionsPerPath[ eventDbPath ] =
		( _sessionsPerPath[ eventDbPath ] or 0 ) + 1

	local loggedEventStream =
			NmbSessionLogger.logEventStreamToPathStream( 
				eventStream, eventDbPath, maxSessionCount )

	local loggedEventStreamSubscr = 
			subscribeToLoggedEventStream( loggedEventStream, {} )

	local eventStreamEvents = 
			makeNELementArray( 1, makeEventStreamEvent, eventStream )

	local expectedEvents =
			WFFunctionUtils.map(
				makeExpectedEventStreamResult,
					eventStreamEvents, 
						returnEventStreamNameAndSessionId,
							eventStreamName, _sessionsPerPath[ eventDbPath ] )

	WFFunctionUtils.map(
		sendEventAndSleep,
			eventStreamEvents, rxTest )

	loggedEventStreamSubscr:dispose()

	return expectedEvents

end

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

local function interleave( t1, t2 )

	local t1Cnt, t2Cnt = #t1, #t2

	local ret = {}

	for i = 1, math.max( t1Cnt, t2Cnt ) do

		if i <= t1Cnt then

			ret[ #ret + 1 ] = t1[ i ]

		end

		if i <= t2Cnt then

			ret[ #ret + 1 ] = t2[ i ]

		end

	end

	return ret

end

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

local function stripFirstNCols( n )

	return function( row )

		return { unpack( row, n + 1, #row ) }

	end

end

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

local function rxTestHappyPath( rxTest )

	local eventStream1, eventStream2 = Rx.Subject(), Rx.Subject()

	local eventDbPath1, eventDbPath2 = 
			getNewDbPath( rxTest ), getNewDbPath( rxTest )

	local nPrevEventStream2Sessions = 2

	rxTest:logf( "Setting up %s previous sessions on %s", 
					nPrevEventStream2Sessions, eventDbPath2 )

	makeNELementArray( nPrevEventStream2Sessions, 
						addEventStreamSession, 
							rxTest, eventStream2, "eventStream2", 
							eventDbPath2, 20 )

	local loggedEventStream1 =
			NmbSessionLogger.logEventStreamToPathStream( 
				eventStream1, eventDbPath1, 15 )

	local loggedEventStream2 =
			NmbSessionLogger.logEventStreamToPathStream( 
				eventStream2, eventDbPath2, 20 )

	local packedEventStream1StartTime = {}

	local loggedEventStream1Subscr = 
			subscribeToLoggedEventStream( 
				loggedEventStream1, packedEventStream1StartTime )

	local loggedEventStream2Subscr = 
			subscribeToLoggedEventStream( loggedEventStream2, {} )

	local eventStream1Events = 
			makeNELementArray( 10, makeEventStreamEvent, eventStream1 )

	local eventStream2Events = 
			makeNELementArray( 20, makeEventStreamEvent, eventStream2 )

	local allEventsStream = interleave( eventStream1Events, eventStream2Events )

	local expectedEvents =
			WFFunctionUtils.map(
				makeExpectedEventStreamResult,
					allEventsStream, 
						getEventStream1OrEventStream2NameAndSessionId,
							eventStream1, 1, nPrevEventStream2Sessions + 1 )

	rxTest:log( "Sending interleaved events" )

	WFFunctionUtils.map(
		sendEventAndSleep,
			allEventsStream, rxTest )

	local loggedEventStream1StartTime = unpack( packedEventStream1StartTime )

	rxTest
		:expectEqual( true, loggedEventStream1StartTime ~= nil )

	local mergedEventStreamResults =
		NmbSessionLogger.getMergedSessionLogRowDataSinceTimestamp( 
				loggedEventStream1StartTime, "eventStream1", eventDbPath1,
				"eventStream2", eventDbPath2 )

	local mergedEventStreamResultsWithoutTimestamp =
		WFFunctionUtils.map(
			stripFirstNCols( 3 ),
				mergedEventStreamResults )

	rxTest:log( "Checking interleaved events" )

	rxTest
		:expectEqual( 
			expectedEvents, 
			mergedEventStreamResultsWithoutTimestamp )

	loggedEventStream1Subscr:dispose()

	loggedEventStream2Subscr:dispose()

end

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

local function bumpTimeLimit( f, n )

	return function( rxTest )

		rxTest:setTimeLimit( n )

		return f( rxTest )

	end

end

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

local function appendT( ret, t )

	local nRet = #ret

	for i, v in ipairs( t ) do

		ret[ nRet + i ] = v

	end

	return ret

end

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

local function rxTestEventStreamPruning( rxTest )

	local eventStream1, eventStream2 = Rx.Subject(), Rx.Subject()

	local eventDbPath1, eventDbPath2 = 
			getNewDbPath( rxTest ), getNewDbPath( rxTest )

	local nPrevEventStreamSessions = 3

	rxTest:logf( "Setting up %s previous sessions on %s", 
					nPrevEventStreamSessions, eventDbPath2 )

	local allEventStream1PrevSessionEvents =
		WFFunctionUtils.reduce( {},
			appendT,
				makeNELementArray( nPrevEventStreamSessions, 
					addEventStreamSession, 
						rxTest, eventStream1, "eventStream1", 
						eventDbPath1, 20 ) )

	local allEventStream2PrevSessionEvents =
		WFFunctionUtils.reduce( {},
			appendT,
				makeNELementArray( nPrevEventStreamSessions, 
					addEventStreamSession, 
						rxTest, eventStream2, "eventStream2", 
						eventDbPath2, 20 ) )

	rxTest:log( "Checking that we have all the sessions without pruning" )
	
	local expectedEventStreamResultsWithoutPruning =
				appendT(
					allEventStream1PrevSessionEvents,
					allEventStream2PrevSessionEvents )

	local mergedEventStreamResultsWithoutPruning =
		NmbSessionLogger.getMergedSessionLogRowDataSinceTimestamp( 
				'2001-01-01', "eventStream1", eventDbPath1,
				"eventStream2", eventDbPath2 )

	local actualEventStreamResultsWithoutPruning =
		WFFunctionUtils.map(
			stripFirstNCols( 3 ),
				mergedEventStreamResultsWithoutPruning )

	rxTest
		:expectEqual( 
			expectedEventStreamResultsWithoutPruning, 
			actualEventStreamResultsWithoutPruning )

	rxTest:log( "now add another session to each with pruning" )

	makeNELementArray( 1, 
					addEventStreamSession, 
						rxTest, eventStream1, "eventStream1", 
						eventDbPath1, 1 )

	makeNELementArray( 1, 
						addEventStreamSession, 
							rxTest, eventStream2, "eventStream2", 
							eventDbPath2, 1 )

	local mergedEventStreamResultsWithPruning =
		NmbSessionLogger.getMergedSessionLogRowDataSinceTimestamp( 
				'2001-01-01', "eventStream1", eventDbPath1,
				"eventStream2", eventDbPath2 )

	local actualEventStreamResultsWithPruning =
		WFFunctionUtils.map(
			stripFirstNCols( 3 ),
				mergedEventStreamResultsWithPruning )

	rxTest
		:expectEqual( 
			{
				{ "eventStream1", nPrevEventStreamSessions, "info", "event 1" },
				{ "eventStream2", nPrevEventStreamSessions, "info", "event 1" },
				{ "eventStream1", nPrevEventStreamSessions + 1, "info", "event 1" },
				{ "eventStream2", nPrevEventStreamSessions + 1, "info", "event 1" }
			},
			actualEventStreamResultsWithPruning )

end

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

local function rxTestEventStreamBeforeAfter( rxTest )

	local eventStream1, eventStream2 = Rx.Subject(), Rx.Subject()

	local eventDbPath1, eventDbPath2 = 
			getNewDbPath( rxTest ), getNewDbPath( rxTest )

	local nPrevEventStreamSessions = 3

	rxTest:logf( "Setting up %s previous sessions on %s", 
					nPrevEventStreamSessions, eventDbPath2 )

	local allEventStream1PrevSessionEvents =
		WFFunctionUtils.reduce( {},
			appendT,
				makeNELementArray( nPrevEventStreamSessions, 
					addEventStreamSession, 
						rxTest, eventStream1, "eventStream1", 
						eventDbPath1, 20 ) )

	local allEventStream2PrevSessionEvents =
		WFFunctionUtils.reduce( {},
			appendT,
				makeNELementArray( nPrevEventStreamSessions, 
					addEventStreamSession, 
						rxTest, eventStream2, "eventStream2", 
						eventDbPath2, 20 ) )

	rxTest:log( "Checking before/after 2001" )

	local allEvents =
			appendT(
				allEventStream1PrevSessionEvents,
				allEventStream2PrevSessionEvents )
	
	for _, timestampPair in ipairs(
			{ 
				{ '2001-01-01', true },
				{ '2030-01-01', false } 
			} ) do

		local timestamp, expectBeforeEmpty = unpack( timestampPair )

		local expectedEventStreamSinceResults =
					not expectBeforeEmpty and
						{} or allEvents

		local mergedEventStreamSinceResults =
			NmbSessionLogger.getMergedSessionLogRowDataSinceTimestamp( 
					timestamp, "eventStream1", eventDbPath1,
					"eventStream2", eventDbPath2 )

		local actualEventStreamSinceResults =
			WFFunctionUtils.map(
				stripFirstNCols( 3 ),
					mergedEventStreamSinceResults )

		rxTest
			:expectEqual( 
				expectedEventStreamSinceResults, 
				actualEventStreamSinceResults )

		local mergedEventStreamBeforeResults =
			NmbSessionLogger.getMergedSessionLogRowDataBeforeTimestamp( 
					timestamp, "eventStream1", eventDbPath1,
					"eventStream2", eventDbPath2 )

		local actualEventStreamBeforeResults =
			WFFunctionUtils.map(
				stripFirstNCols( 3 ),
					mergedEventStreamBeforeResults )

		local expectedEventStreamBeforeResults =
					expectBeforeEmpty and
						{} or allEvents

		rxTest
			:expectEqual( 
				expectedEventStreamBeforeResults, 
				actualEventStreamBeforeResults )

	end

end

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

local function rxTestBadFormatStrs( rxTest )

	local eventStream1, eventStream2 = Rx.Subject(), Rx.Subject()

	local eventDbPath1, eventDbPath2 = 
			getNewDbPath( rxTest ), getNewDbPath( rxTest )

	local loggedEventStream1 =
			NmbSessionLogger.logEventStreamToPathStream( 
				eventStream1, eventDbPath1, 15 )

	local loggedEventStream2 =
			NmbSessionLogger.logEventStreamToPathStream( 
				eventStream2, eventDbPath2, 15 )

	local packedEventStream1StartTime = {}

	local loggedEventStream1Subscr = 
			subscribeToLoggedEventStream( 
				loggedEventStream1, packedEventStream1StartTime )

	local loggedEventStream2Subscr = 
			subscribeToLoggedEventStream( loggedEventStream2, {} )

	local eventStream1Events = 
		{
			{ eventStream1, nil, "event string without level" },
			{ eventStream1, "info" },
			{ eventStream1, "info", "formatted %s string without args" },
			{ eventStream1, "info", "args but not formatted", "arg" },
			{ eventStream1, "info", "too many args %s", "arg", "arg" },
			{ eventStream1, "info", "too few args %s %s", "arg", },
			{ eventStream1, "info", "type mismatch %d", "arg", },
		}

	rxTest:logf( "Sending %s bad format str events", #eventStream1Events )

	WFFunctionUtils.map(
		sendEventAndSleep,
			eventStream1Events, rxTest )

	local loggedEventStream1StartTime = unpack( packedEventStream1StartTime )

	rxTest
		:expectEqual( true, loggedEventStream1StartTime ~= nil )

	local mergedEventStreamResults =
		NmbSessionLogger.getMergedSessionLogRowDataSinceTimestamp( 
				loggedEventStream1StartTime, "eventStream1", eventDbPath1,
				"eventStream2", eventDbPath2 )

	local mergedEventStreamResultsWithoutTimestamp =
		WFFunctionUtils.map(
			stripFirstNCols( 3 ),
				mergedEventStreamResults )

	rxTest:log( "Checking interleaved events" )

	rxTest
		:expectEqual( 
			{
				{
					"eventStream1",
					1,
					"info",
					"formatted %s string without args",
				},
				{
					"eventStream1",
					1,
					"info",
					"args but not formatted",
				},
				{
					"eventStream1",
					1,
					"info",
					"too many args arg",
				},
				{
					"eventStream1",
					1,
					"info",
					"too few args arg nil",
				},
			},
			mergedEventStreamResultsWithoutTimestamp )

	loggedEventStream1Subscr:dispose()

	loggedEventStream2Subscr:dispose()

end

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

return WFRxTestCase.wrapSuite( {

	rxTestHappyPath = bumpTimeLimit( rxTestHappyPath, 5 ),

	rxTestEventStreamPruning = bumpTimeLimit( rxTestEventStreamPruning, 5 ),

	rxTestEventStreamBeforeAfter = rxTestEventStreamBeforeAfter,

	rxTestBadFormatStrs = rxTestBadFormatStrs,

} )
