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

WFSQLiteDatabaseUtilsTest.lua

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

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

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

local Rx = require 'Rx'

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

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

local WFRxScheduler = require "WFRxScheduler"

local WFSQLiteDatabase = require 'WFSQLiteDatabase'
local WFSQLiteDatabaseSchema = require 'WFSQLiteDatabaseSchema'
local WFSQLiteDatabaseUtils = require 'WFSQLiteDatabaseUtils'
local WFSQLiteUtils = require 'WFSQLiteUtils'

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

local function dbPathForName( name )

	local fname = string.format( "%s.db", name )

	local realFileDir = KSPathUtils.getStandardFilePath( "temp" )

	return KSPathUtils.child( realFileDir, fname )

end

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

local function uniqueDbPathForName( name )

	return WFFileUtils.ensureFileNameIsUnique( dbPathForName( name ) )

end

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

local function makeTableName( n )

	return string.format( "t%d", n );

end

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

local function makeColumnName( n )

	return string.format( "c%d", n );

end

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

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

	local ret = {}

	for i = 1, n do 

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

	end

	return ret

end

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

local function makeColumnList( i )

	return table.concat( mapNTimes( i, makeColumnName ), "," )

end

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

local function createTableSQL( i )

	return string.format(
		[[CREATE TABLE %s( %s );]], makeTableName( i ), makeColumnList( i ) );

end

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

local function makeInsertSQL( i )

	return string.format( "INSERT INTO %s SELECT %s", 
							makeTableName( i ),
							WFSQLiteUtils.createVariableArgList( i ) )
		
end

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

local function justReturn( v )

	return function() return v end

end

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

local function doInsert( val, db, insertSQL, colCount )

	db:exec( insertSQL, unpack( mapNTimes( colCount, justReturn( val ) ) ) )

end

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

local function populateTable( rowCount, db, i )
	
	local insertSQL = makeInsertSQL( i )

	mapNTimes( rowCount, doInsert, db, insertSQL, i )

end

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

local function createAndPopulateTable( i, db, rowCount )

	db:exec( createTableSQL( i ) )

	db:exec( "SAVEPOINT 'populate'" )

	mapNTimes( rowCount, populateTable, db, i )

	db:exec( "RELEASE 'populate'" )

end

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

local function createDb( path, schemaVersion, tableCount, rowCount )

	local schema = WFSQLiteDatabaseSchema( schemaVersion )

	local dbFromPath = WFSQLiteDatabase( path, schema )

	mapNTimes( tableCount, createAndPopulateTable, dbFromPath, rowCount )

	return dbFromPath

end

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

local function createFileDb( rxTest, name, schemaVersion, tableCount, rowCount )

	local path = uniqueDbPathForName( name )

	rxTest:finally(
		function() 
			WFFileUtils.delete( path )
		end )

	local dbFromPath = createDb( path, schemaVersion, tableCount, rowCount )

	dbFromPath:close()

	return path

end

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

local function getSelectAllStableSortSQL( i )

	return string.format( 
				[[SELECT * FROM %s ORDER BY %s]],
				makeTableName( i ), makeColumnList( i )  )

end


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

local function checkTableContentsSame( i, rxTest, db1, db2 )

	local db1Rows = db1:gatherArrayOfArray( getSelectAllStableSortSQL( i ) )

	local db2Rows = db2:gatherArrayOfArray( getSelectAllStableSortSQL( i ) )

	rxTest:logf( "Comparing table '%s' in '%s' with same table in '%s'",
					makeTableName( i ), db1:path(), db2:path() )

	rxTest:expectEqual( KSTableUtils.deepEquals( db1Rows, db2Rows ), true )

end

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

local function checkSameContentsInDb( rxTest, db1, db2, tableCount )

	mapNTimes( tableCount, checkTableContentsSame, rxTest, db1, db2 )

end

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

local function rxTestOnlineBackup( rxTest )

	local schemaVersion = "1.0"

	local memDb = createDb( ':memory:', schemaVersion, 5, 50 )

	local backupPath = uniqueDbPathForName( "onlineBackup1" )

	rxTest:finally(
		function()

			WFFileUtils.delete( backupPath )

		end )

	local schema = WFSQLiteDatabaseSchema( schemaVersion)

	local backupPathDb = WFSQLiteDatabase( backupPath, schema )

	rxTest:log( "Test onlineBackup mem -> disk" )

	backupPathDb = memDb:onlineBackup( backupPathDb )

	checkSameContentsInDb( rxTest, memDb, backupPathDb, 5 )

	memDb:close()

	backupPathDb:close()

	rxTest:log( "Test onlineBackup disk -> mem" )

	local sourcePath = uniqueDbPathForName( "onlineBackup2" )

	rxTest:finally(
		function()

			WFFileUtils.delete( sourcePath )

		end )

	local sourcePathDb = createDb( sourcePath, schemaVersion, 5, 50 )

	local backupMemDb = WFSQLiteDatabase( ':memory:', schema )

	backupMemDb = sourcePathDb:onlineBackup( backupMemDb )

	checkSameContentsInDb( rxTest, sourcePathDb, backupMemDb, 5 )

	sourcePathDb:close()

	backupMemDb:close()

end
--------------------------------------------------------------------------------

local function rxTestOnlineBackupWithProgressStream( rxTest )

	local schemaVersion = "1.0"

	local sourcePath = uniqueDbPathForName( "OnlineBackupWithProgressStream_SourceDb" )

	rxTest:finally( function()
		WFFileUtils.delete( sourcePath )
	end )

	local sourceDb = createDb( sourcePath, schemaVersion, 5, 50 )

	local backupPath = uniqueDbPathForName( "OnlineBackupWithProgressStream_BackupDb" )

	rxTest:finally( function()
		WFFileUtils.delete( backupPath )
	end )

	rxTest:getLastValue( WFSQLiteDatabaseUtils.onlineBackupDbToPathWithProgressStream( sourceDb, backupPath ), 1 )

	-- open the backed up database
	local schema = WFSQLiteDatabaseSchema( schemaVersion )
	local backupDb = WFSQLiteDatabase( backupPath, schema )

	checkSameContentsInDb( rxTest, sourceDb, backupDb, 5 )

	sourceDb:close()
	backupDb:close()

end

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

local function rxTestOnlineBackupWithProgressStream_cancel( rxTest )

	local schemaVersion = "1.0"

	local sourcePath = uniqueDbPathForName( "OnlineBackupWithProgressStream_c_SourceDb" )

	rxTest:finally( function()
			WFFileUtils.delete( sourcePath )
	end )

	local sourceDb = createDb( sourcePath, schemaVersion, 5, 50 )

	local backupPath = uniqueDbPathForName( "OnlineBackupWithProgressStream_c_BackupDb" )

	rxTest:finally( function()
		WFFileUtils.delete( backupPath )
	end )

	local disposeCalled = Rx.Subject()
	local onlineBackupObserver

	onlineBackupObserver = rxTest:observe(
		WFSQLiteDatabaseUtils.onlineBackupDbToPathWithProgressStream( sourceDb, backupPath )
		:map( function( ... )
			onlineBackupObserver:dispose()

			-- Schedule the notification of the dispose so it can happen after
			-- onlineBackupDbToPathWithProgressStream has had time to respond
			-- to the subscriptions disposal.

			WFRxScheduler.sharedScheduler():schedule( function()
				disposeCalled:onNext( true )
				disposeCalled:onCompleted()
			end )

			return ...

		end ) )

	rxTest:waitFor( disposeCalled )

	-- Make sure the backup database doesn't exist.
	rxTest:expectEqual( false, WFFileUtils.exists( backupPath ) )

	sourceDb:close()

end

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

local function rxTestHappyPath( rxTest )

	rxTest:setTimeLimit( 120 )

	local schemaVersion = "1.0"
	
	local tableCount = 5

	local rowCount = 50

	local dbPath = createFileDb( rxTest, "happyPath", schemaVersion,
									tableCount, rowCount )

	rxTest:expectEqual( not WFFileUtils.exists( dbPath ), false )

	local expectedDb = createDb( ':memory:', schemaVersion,
									tableCount, rowCount )

	local createInMemoryDbFromPathDb

	local watchCreateInMemoryDbFromPathDb =
			rxTest:observe(
				WFSQLiteDatabaseUtils.createInMemoryDbFromPath(
						dbPath, schemaVersion )
					:tap(
						function( memoryDbFromPath )

							createInMemoryDbFromPathDb = memoryDbFromPath

						end ) )

	rxTest:log( "Waiting for createInMemoryDbFromPath" )

	watchCreateInMemoryDbFromPathDb:expectData( ':ignore:' )

	watchCreateInMemoryDbFromPathDb:expectCompleted()

	checkSameContentsInDb( rxTest, createInMemoryDbFromPathDb, expectedDb, 5 )

	createInMemoryDbFromPathDb:close()
	
	expectedDb:close()

end

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

return WFRxTestCase.wrapSuite( 'WFSQLiteDatabaseUtilsTest', {

	rxTestOnlineBackup = rxTestOnlineBackup,

	rxTestOnlineBackupWithProgressStream = rxTestOnlineBackupWithProgressStream,

	rxTestOnlineBackupWithProgressStream_cancel = rxTestOnlineBackupWithProgressStream_cancel,

	rxTestHappyPath = rxTestHappyPath,
	
} )
