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

WFOldWFDocStoreReader.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2016 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'
local WFCloseSupport = require 'WFCloseSupport'

	-- WF.sqlite.database
local WFSQLiteDatabase = require 'WFSQLiteDatabase'
local WFSQLiteDatabaseSchema = require 'WFSQLiteDatabaseSchema'


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

local check = assert( WFArguments.check )

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

-- @mark database schema

local schema = WFSQLiteDatabaseSchema( '1.0' ):useWAL()

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

schema:addTable
	[[CREATE TABLE bodyContent( bodyContentHash UNIQUE NOT NULL,
								value NOT NULL )]]
	:onConflict 'replace'

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

schema:addTable
	[[CREATE TABLE annotationContent( annotationContentHash UNIQUE NOT NULL,
									  value NOT NULL )]]
	:onConflict 'replace'
	
--------------------------------------------------------------------------------

schema:addTable
	[[CREATE TABLE documents(
						docId NOT NULL,
						slotName NOT NULL,
						changeCounter NOT NULL,
						bodyContentHash NOT NULL,
						annotationContentHash NOT NULL )]]
	:onConflict 'replace'

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

schema:addIndex
	[[CREATE UNIQUE INDEX documents_docId_slotName ON documents( docId, slotName )]]
	
--============================================================================--

-- @mark WFOldWFDocStoreReader class

local oldOzDocStoreReaderMethods = {

	_db = false,

	_path = false,
	
	_changeCounter = false,

	WFCloseSupport.addCloseSupportMethods,

}

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

function oldOzDocStoreReaderMethods:__init( path )

	local db = WFSQLiteDatabase( path, schema )
	self._db = db

	WFCloseSupport.mustCloseAtThreadTermination( self )
	
	local changeCounterSeedRow = db:execOnce( "SELECT max( changeCounter ) FROM documents" )
	self._changeCounter = ( changeCounterSeedRow
		and changeCounterSeedRow[ "max( changeCounter )" ] or 0 ) + 1
	
end

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

function oldOzDocStoreReaderMethods:close()

	-- Using close intead of setOwned so the client can close
	-- and then delete the database immediately afterwards.

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

end

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

function oldOzDocStoreReaderMethods:sourceDocumentCount()

	local countRow = self._db:execOnce( "SELECT count(*) FROM documents" )
	return countRow[ "count(*)" ] or 0

end

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

local valueFnEnv = {} -- disallow all function calls

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

local function contentFromFlattenedValue( value )

	-- Lightweight sandboxing. The one potentially malicious piece of code
	-- somebody could inject (and they'd have to do it directly into the
	-- SQLite database) would be to put an infinite loop here.
	-- Some more potentially valuable approaches we could take are
	-- described in http://lua-users.org/wiki/SandBoxes.

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

end

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

local function processDocValueRow( contentRow )

	-- Convert flattened Lua to actual Lua values.

	local bodyContentValue = contentFromFlattenedValue( contentRow[ 4 ] )
	local docValue = contentFromFlattenedValue( contentRow[ 5 ] )

	-- We seed the docValue from the annotation in the (unexpected) case that
	-- both annotation and body contain the same key. If so, then we want the
	-- version in the bodyContent hash to dominate when we copy over.
	
	for k, v in pairs( bodyContentValue ) do
		docValue[ k ] = v
	end
	
	docValue._id = contentRow[ 1 ]
	docValue._changeCounter = contentRow[ 3 ]
	docValue._hash = contentRow[ 6 ]
	docValue._slot = contentRow[ 2 ]
	docValue._idLocal = contentRow[ 7 ]
	
	return docValue

end

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

function oldOzDocStoreReaderMethods:readDocumentBatch( fromChangeCounter )

	check( 'WFOldWFDocStoreReader', '?number', '?number' )
	
	local docRows = self._db:gatherArrayOfArray(
		[[SELECT docId,
				 slotName,
				 changeCounter,
				 bodyContent.value,
				 annotationContent.value,
				 documents.bodyContentHash,
				 documents.rowid
			FROM documents
				INNER JOIN bodyContent USING( bodyContentHash )
				INNER JOIN annotationContent USING( annotationContentHash )
			WHERE
				documents.changeCounter >= ]] .. fromChangeCounter .. [[
			ORDER BY
				documents.changeCounter ]] )
	
	local result = {}
	local nDocRows = #docRows
	
	for i = 1, nDocRows do
		result[ i ] = processDocValueRow( docRows[ i ] )
	end
	
	local lastRow = nDocRows > 0 and docRows[ nDocRows ]
	if lastRow then
		result.continueFrom = lastRow[ 3 ] + 1
	end
	
	return result

end

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

function oldOzDocStoreReaderMethods:__tostring()

	return string.format( "WFOldWFDocStoreReader( %s )", self._db:path() )

end

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

return Rx.Class 'WFOldWFDocStoreReader'( oldOzDocStoreReaderMethods )
