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

WFSQLiteDatabaseSchema.lua

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

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

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

	-- RxLua
local Rx = require 'Rx'

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

	-- WF.sqlite.database
local WFSQLiteUtils = require 'WFSQLiteUtils'

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

local check = assert( WFArguments.check )


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

-- @mark schema table class

local schemaTableMethods = {

	_tableName = false,
		-- (string) table name

	_sql = false,
		-- (string) SQL needed to create this table

	_onConflict = false,
		-- (optional, string) what to do if this table exists and schema doesn't match
			-- "replace": delete existing table and start over
			-- "alter": attempt to modify the existing table schema using a supplied
				-- ALTER TABLE statement.
			-- (nil): raise an error
			
	_onConflictAlterSqlTable = false,
		-- (optional, table) If the onConflict is set at alter, attempt to resolve the
		-- schema conflict by modifying the table definition using this SQL and then 
		-- rechecking the schema.
		
}

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

function schemaTableMethods:__init( sql )

	local tableName = WFSQLiteUtils.tableNameFromCreateTableStatement( sql )

	if not tableName or #tableName < 1 then
		error( "WFSQLiteDatabaseSchema:addTable: could not find table name in " .. sql, 5 )
	end

	self._tableName = tableName
	self._sql = sql

end

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

function schemaTableMethods:onConflict( conflictPolicy, optAlterSqlTable )

	check( 'WFSQLiteDatabaseSchemaTable', 'string', '?table' )

	if conflictPolicy == 'replace' or conflictPolicy == 'replaceAll' then

		self._onConflict = conflictPolicy

	elseif conflictPolicy == 'alter' then
	
		self._onConflict = conflictPolicy
		self._onConflictAlterSqlTable = optAlterSqlTable
	
	else

		error( "WFSQLiteDatabaseSchema:addTable:onConflict: invalid policy "
			   .. tostring( conflictPolicy ), 2 )

	end

	return self

end

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

local schemaTableClass = Rx.Class 'WFSQLiteDatabaseSchemaTable'( schemaTableMethods )


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

-- @mark schema index class

local schemaIndexMethods = {

	_indexName = false,
		-- (string) index name

	_sql = false,
		-- (string) SQL needed to create this index

}

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

function schemaIndexMethods:__init( sql )

	local _, _, indexName = sql:find( "([%a_][%w_]*)%s+[Oo][Nn]%s+" )

	if not indexName or #indexName < 1 then
		error( "WFSQLiteDatabaseSchema:addIndex: could not find index name in " .. sql, 5 )
	end

	self._indexName = indexName
	self._sql = sql

end

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

local schemaIndexClass = Rx.Class 'WFSQLiteDatabaseSchemaIndex'( schemaIndexMethods )


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

-- @mark WFSQLiteDatabaseSchema class

--------------------------------------------------------------------------------
--[[!
	\class WFSQLiteDatabaseSchema

	\brief Represents a database schema.

	A worker thread can create an object of this type and use its
	methods to add schema elements (tables and indices).
	Use the schema to create a \c WFSQLiteDatabase instance.

	The typical usage pattern is that the client code will create
	a database schema description by calling:

		local schema = WFSQLiteDatabaseSchema()

	and adding schema elements (tables and indices) to it by calling

		schema:addTable()

	and similar methods.

	\sa WFSQLiteDatabase
]]--

local schemaMethods = {

	_tables = false,
		-- (table)
			-- key: (string) table name
			-- value: (KSSchemaTable) info about this table definition
	
	_tablesToDrop = false,
		-- (table)
		
	_indexes = false,
		-- (table)
			-- key: (string) index name
			-- value: (KSSchemaIndex) info about this index definition

	_useWAL = false,
		-- (boolean) true if WAL mode was requested

	_useForeignKeys = false,
		-- (boolean) true if foreign-keys mode was requested

	_allowMultipleConnections = false,
		-- (boolean) true if multiple connections should be allowed to this db
		
	_synchronous = false,
		-- (number) synchronous mode

	_pageSize = false,
		-- (number) if present, overrides the default (1024) SQLite page size

	_maxPageCount = false,
		-- (number) if present, overrides the maximum SQLite page count

	_currentVersion = false,
		-- (string) current schema version (typically app version)

	_backwardCompatibleToVersion = false,
		-- (string) set to < _currentVersion to assert compatibility with
			-- older software
			-- older software will fail to launch if it's version is < this

}

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

function schemaMethods:__init( currentVersion, backwardCompatibleToVersion )

	WFArguments.check( 'WFSQLiteDatabaseSchema', '?string|number', '?string|number' )

	self._tables = {}
	self._tablesToDrop = {}
	self._indexes = {}
	
	self._currentVersion = currentVersion or '0'
	self._backwardCompatibleToVersion = backwardCompatibleToVersion or currentVersion or '0'

end

--------------------------------------------------------------------------------
--[[!
	\brief Retrieves the version information with which this schema was created.

	\return Multiple values: the current version, and the earliest version
		with which this one is backward compatible.
]]--

function schemaMethods:currentAndBackwardCompatibleVersions()

	WFArguments.check( 'WFSQLiteDatabaseSchema' )

	return self._currentVersion, self._backwardCompatibleToVersion

end

--------------------------------------------------------------------------------
--[[!
	\brief Enables the write-ahead feature of SQLite.
]]--

function schemaMethods:useWAL()

	self._useWAL = true

	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Enables the foreign keys feature of SQLite.
]]--

function schemaMethods:useForeignKeys()

	self._useForeignKeys = true

	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Sets up the database to allow multiple connections
]]--

function schemaMethods:allowMultipleConnections()

	self._allowMultipleConnections = true
	
	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Sets the pragma synchronous mode for this database.
]]--

function schemaMethods:setSynchronous( synchronous )

	WFArguments.check( 'WFSQLiteDatabaseSchema', 'string|number' )

	self._synchronous = synchronous

	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Sets the page size for the SQLite database.
		Note, this is currently only used by unit tests, but there's no reason this
		could not also be used outside of tests.
]]--

function schemaMethods:setPageSize( pageSize )

	WFArguments.check( 'WFSQLiteDatabaseSchema', 'number' )

	self._pageSize = pageSize

	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Sets the max page count for the SQLite database.
]]--

function schemaMethods:setMaxPageCount( count )

	WFArguments.check( 'WFSQLiteDatabaseSchema', 'number' )

	self._maxPageCount = count

	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds a table to this schema.

	\param sql (string) An SQL statement that defines the new table.

	\return (table) The new table object.
]]--

function schemaMethods:addTable( sql )

	WFArguments.check( 'WFSQLiteDatabaseSchema', 'string' )

	local tableEntry = schemaTableClass( sql )
	self._tables[ tableEntry._tableName ] = tableEntry

	return tableEntry

end

--------------------------------------------------------------------------------
--[[!
	\brief Marks a table to drop from the schema if it's present in the existing db.
	
	\param tableName (string) The name of a table to drop
]]--

function schemaMethods:dropTable( tableName )
	
	WFArguments.check( 'WFSQLiteDatabaseSchema', 'string' )
	
	table.insert( self._tablesToDrop, tableName ) 
	
	return self

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds an index to this schema.

	\param sql (string) An SQL statement that defines the new index.

	\return The new index entry.
]]--

function schemaMethods:addIndex( sql )

	WFArguments.check( 'WFSQLiteDatabaseSchema', 'string' )

	local indexEntry = schemaIndexClass( sql )
	self._indexes[ indexEntry._indexName ] = indexEntry

	return indexEntry

end

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

return Rx.Class 'WFSQLiteDatabaseSchema'( schemaMethods )
