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

WFRxDatabase.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.

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

	-- Rx
local Rx = require 'Rx'

	-- WF.core.lua.threading
local WFWorkerThread = require 'WFWorkerThread'

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local WFCloseSupport = require 'WFCloseSupport'

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

local check = assert( WFArguments.check, "Cannot find WFArguments.check" )

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

local function dispatchToWorkerThread( self, selector, ... )
	return self._databaseWorkerThread:request( selector, ... )
end

--============================================================================--
--[[!
	\class WFRxDatabase
	\brief Provides SQL-style of its contents
--]]

-- @mark WFRxDatabase

local databaseMethods = {
	
	_script = false,
		-- (string) the name of the table definition script used by the database
		
	_databaseWorkerThread = false,
		-- (WFWorkerThread) reference to the event processor that does the database action
		
	WFCloseSupport.addCloseSupportMethods,

}

--------------------------------------------------------------------------------
--[[!
	\brief Creates WFRxDatabase object that tracks the contents of an Oz document store.

	\param path (string) The path for database
	\param script (string) The name of table and index definition script for this rxDatabase
--]]

function databaseMethods:__init( path, script, optVersion)

	check( 'WFRxDatabase', '+string', '+string' )
	
	optVersion = optVersion or '1.0'
	
	self._script = script
	
	local initArgs = string.format( '%s=%s=%s', path, script, optVersion )
	self._databaseWorkerThread = WFWorkerThread( 'WFRxDatabaseImp.lua', initArgs )

	local init = self._databaseWorkerThread:request( 'initDatabase', path, script )
		:subscribe(function()end)
		
	self:addCloseAction( function()
		init:dispose()
	end )

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds a table to the schema of the database. Should be called in script.

	\param sql: (string) a <code>CREATE TABLE</code> SQL statement
	
	Remember that SQL table names are case-insensitive, so any case variation
	of those names are also reserved.
]]--

function databaseMethods:addTable( sql )

	check( 'WFRxDatabase', 'string' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'addTable', sql )

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds an index related to a table the schema.

	\param sql: (string) a <code>CREATE INDEX</code> SQL statement
	
	Remember that SQL table names are case-insensitive, so any case variation
	of those names are also reserved.
]]--

function databaseMethods:addIndex( sql )

	check( 'WFRxDatabase', 'string' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'addIndex', sql )

end

--------------------------------------------------------------------------------
--[[!
	\brief Get the list of the table names
	
	\return a table of talbeNames
]]--

function databaseMethods:DEBUG_tableNames()

	check( 'WFRxDatabase' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'DEBUG_tableNames' )

end

--------------------------------------------------------------------------------
--[[!
	\brief Retrieves the set of SQL indices declared for this schema.

	\return (table)
		- key (string): A table name as declared in SQL
		- value (table): \c a list of SQL index names and their corresponding SQL strings
]]--

function databaseMethods:DEBUG_indexInfos()

	check( 'WFRxDatabase' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'DEBUG_indexInfos' )

end

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

--[[!
	\brief Create an Observable that tracks the results of a SQL query on the database

	\param args (table) arguments for query
		- \c sql: (string) SQL query to be performed
		- \c queryOnce: (boolean) only get immediate result from SQL query 
		- 1 .. n: (optional) query arguments to be substituted for ? placeholders in query

	\return B< table > query results (array of arrays syntax)
--]]

function databaseMethods:query( --[[table]] args )

	check( 'WFRxDatabase', 'table' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'query', args )

end

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

--[[!
	\brief Bulk insert rows into database table

	\param tableName: (string) tableName for inserting new rows
	\param rows: (table) rows with each row content in order of table columns
	
	\return Observable of database insert result
--]]

function databaseMethods:bulkInsert( tableName, rows )

	check( 'WFRxDatabase', 'string', 'table' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'bulkInsert', tableName, rows )

end

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

--[[!
	\brief Bulk insert or replace rows into database table

	\param tableName: (string) tableName for inserting or replacing if row already exists
	\param rows: (table) rows with each row content in order of table columns
	
	\return Observable of database insert or replace result
--]]

function databaseMethods:bulkInsertOrReplace( tableName, rows )

	check( 'WFRxDatabase', 'string', 'table' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'bulkInsertOrReplace', tableName, rows )

end

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

--[[!
	\brief Bulk update rows in database table
	
	\param tableName: (string) tableName for updating rows
	\args: (array of table)
		- \c values: (table) key is column name, value is new value to update
		- \c condition: (string) WHERE statement
		- \c 1 .. n: (optional) query arguments to be substituted for ? placeholders in condition
	
	\return Observable of database update result
--]]

function databaseMethods:bulkUpdate( tableName, args )

	check( 'WFRxDatabase', 'string', 'table' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'bulkUpdate', tableName, args )

end

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

--[[!
	\brief update rows in database table
	
	\param tableName: (string) tableName for updating rows
	\args: (table)
		- \c values: (table) key is column name, value is new value to update
		- \c condition: (string) WHERE statement
		- \c 1 .. n: (optional) query arguments to be substituted for ? placeholders in condition
	
	\return Observable of database update result
--]]

function databaseMethods:update( tableName, args )

	check( 'WFRxDatabase', 'string', 'table' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'bulkUpdate', tableName, { args } )

end

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

--[[!
	\brief Bulk delete rows from database table where column matches a set of values

	\param tableName: (string) tableName for deleting rows
	\param columnName: (string) column name that matches the value for deletion
	\param values: (table) an array of values for the column that would be deleted
	
	\return Observable of database delete result
--]]

function databaseMethods:deleteWhereIn( tableName, columnName, values )

	check( 'WFRxDatabase', 'string', 'string' )
	
	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'deleteWhereIn', tableName, columnName, values )

end

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

--[[!
	\brief Delete from a table with an optional where clause and arguments
	
	\c tableName: (string) table to delete from
	\c optWhereClause: (string) where clause to use in the delete
	\c optArgs: (table) array of arguments to be substituted for ? placeholders

	\return Observable of SQL statement result
	
--]]
function databaseMethods:deleteFromWhere( tableName, optWhereClause, optArgs )
	
	check( 'WFRxDatabase', '+string', '+string|nil', '?table' )
	
	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ), "deleteFromWhere called after rxDatabase closed" )
	
	return dispatchToWorkerThread( self, 'deleteFromWhere', tableName, optWhereClause, optArgs )

end

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

--[[!
	\brief Clear talbe in database

	\param tableName: (string) tableName for clearing the table
	
	\return Observable of database clear table result
--]]

function databaseMethods:clearTable( tableName )

	check( 'WFRxDatabase', 'string' )

	assert( not WFCloseSupport.hasBeenOrWillBeClosed( self ),
				"query running after rxDatabase closed" )

	return dispatchToWorkerThread( self, 'clearTable', tableName )

end

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

--[[!
	\brief Permanently shut down this rxDatabase and delete all related files.
--]]

function databaseMethods:haltAndDelete()

	local result = dispatchToWorkerThread( self, 'haltAndDelete' )

	local disposable
	disposable = result:subscribeOnCompleted( function()
		disposable:dispose()
	end )

	return result

end

--------------------------------------------------------------------------------
--[[!
	\brief Permanently shuts down this rxDatabase.
--]]

function databaseMethods:halt()

	local result = dispatchToWorkerThread( self, 'halt' )

	local disposable
	disposable = result:subscribeOnCompleted( function()
		
		disposable:dispose()
		disposable = nil
		
	end )
	
	return result

end

--------------------------------------------------------------------------------
--[[!
	\brief Close this instance.

	\return An Observable which sends onCompleted when it is safe to reopen.
--]]

function databaseMethods:close()

	self._databaseWorkerThread:close()
	self._databaseWorkerThread = nil

end

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

function databaseMethods:_DEBUG_simulateCorruption()
	return dispatchToWorkerThread( self, '_DEBUG_simulateCorruption' )
end

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

return Rx.Class 'WFRxDatabase'( databaseMethods )
