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

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

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

This is the private implementation for KSDataCacheService.

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

local WFCurrentThread = require 'WFCurrentThread'

local WFRequestDispatcher = require 'WFRequestDispatcher'

local KSDataCacheServiceImp = require 'KSDataCacheServiceImp'

local WFCloseSupport = require 'WFCloseSupport'

local WFTimer = require 'WFTimer'

local wantPriority = '< low >'

-- Check to see whether we have uniquified the string with a magic prefix. We
-- could work with general prefixes but we really just need to distinguish
-- the read and write prefixes.

local cacheFraction = 1.0

local FAIL_READ_EP = false

local FAIL_READS = false
	-- Set to true if FAIL_READ_EP and this is the read EP

local cacheDB

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

-- FIXME [mhamburg 2011-03-14]: Given the WAL, do we need the commit timer?
-- [ mhamburg 2011-04-13]: What we probably want is a checkpoint timer that
-- will keep firing until we have had a checkpoint succeed.

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

WFCurrentThread.setThreadPriority( wantPriority )

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

WFRequestDispatcher.onMessage( 'fetchCachedData', function( path, debugRetainCount )

	-- Set debugRetainCount to true to enable retain/release tracking.
	
	if FAIL_READS then
		path = "< FAIL >" .. path
	end

	local data = cacheDB:fetch( path, debugRetainCount )
	
	-- FIXME [mhamburg 2012-05-30] via arahn: Mark, sometimes data is nil, so we need to check
	-- for data before sending it to deferredClose, right?
	if data then
		WFRequestDispatcher.deferredClose( data )
	end
	
	return data
    
end )

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

WFRequestDispatcher.onMessage( 'enumerateKeys', function( optStartAfterPath, optLimit )

	if not cacheDB then
		local KSPromises = require 'KSPromises'
		return KSPromises.resolvedPromiseP( 'error', 'cache_has_been_closed' )
	end
	return cacheDB:enumerateKeys( optStartAfterPath, optLimit )
	
end )

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

local function dostore( path, data )

	return cacheDB:store( path, data )

end

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

local timerStatus = 'idle' -- idle | running | dirty
	-- The current timer and the status for the time cycle.
	-- We need to get through a time cycle without another write coming in.
	-- That would be recognized by the timer expiring in the running status.
	
local startTimer
	-- Forward declaration
	
--------------------------------------------------------------------------------

local function timerExpired()

	if timerStatus == 'running' then
		timerStatus = 'idle'
		if cacheDB then
			cacheDB:checkpoint()
		end
	else
		startTimer()
	end

end

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

startTimer = function()

	WFTimer.afterDelayDo( 5, timerExpired )
	timerStatus = 'running'

end

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

local function dbChanged()

	if timerStatus == 'idle' then
		startTimer()
	else
		timerStatus = 'dirty'
	end

end

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

WFRequestDispatcher.onMessage( 'storeCachedData', function( path, data )

	WFRequestDispatcher.deferredClose( data )

	local pcallSuccess, storeSuccess = pcall( dostore, path, data )
	if not pcallSuccess then
		error "In KSDataCacheImp storeCachedData, pcall to store implementation failed"
	end
	
	dbChanged()
	
	if storeSuccess then
		return data
	else
		error "Write to data cache failed"
		-- This error message should be made more useful when KSDataCacheServiceImp has error handling
	end
	
end )

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

WFRequestDispatcher.onMessage( 'removeData', function( path )

	dbChanged()

	if not cacheDB:remove( path ) then
		error "Remove from data cache failed"
	end
	
end )

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

WFRequestDispatcher.onMessage( 'removeBulkData', function( paths )

	local keysRemoved = cacheDB:removeBulk( paths )
	if keysRemoved < 0 then
		error "Remove bulk from data cache failed"
	end

	dbChanged()

	return keysRemoved
	
end )

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

WFRequestDispatcher.onMessage( 'removeAll', function()

	dbChanged()

	if not cacheDB:removeAll() then
		error "Remove all from data cache failed"
	end
	
end )
	
--------------------------------------------------------------------------------

WFRequestDispatcher.onMessage( 'commitCachedData', function()

	cacheDB:checkpoint()

end )

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

WFRequestDispatcher.onMessage( 'vacuum', function()

	cacheDB:vacuum()

end )

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

WFRequestDispatcher.onMessage( 'path', function()
  
  	if cacheDB then
		return cacheDB:path()
  	end
  
end )

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

WFRequestDispatcher.onMessage( 'haltAndDelete', function( delete )

  	if cacheDB then
		cacheDB:haltAndDelete( delete )
		cacheDB = nil
	end

	return true
	
end )

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

local initMessageProcessor = function( cacheName )

	local function stripCachePrefix( prefix, useFraction )
	
		if not cacheName or string.find( cacheName, prefix ) ~= 1 then
			return false
		end
	
		cacheName = string.sub( cacheName, #prefix + 1 )
	
		if cacheName == '' then
			cacheName = nil
		end
	
		cacheFraction = useFraction
	
		return true
		
	end
	
	local forWrite = nil
	
	if stripCachePrefix( 'read:', 0.75 ) then
		if FAIL_READ_EP then
			FAIL_READS = true
		end
		forWrite = false
		wantPriority = '< default >'
	elseif stripCachePrefix( 'write:', 0.25 ) then
		forWrite = true
	end
	
	if not cacheName then
		cacheName = "KSSQLiteDataCacheDB"
	end
	
	assert( forWrite ~= nil, "Should have opened for either read or write." )

	local db = assert( KSDataCacheServiceImp.openCache( cacheName, cacheFraction, forWrite ) )
	cacheDB = {
		_db = db,
		close = function( wrapper )
			wrapper._db.close( wrapper._db )
			cacheDB = nil
		end,
	}
	local methods = { 'fetch', 'enumerateKeys', 'store', 'checkpoint', 'remove', 'removeBulk', 'removeAll',
					  'vacuum', 'path', 'haltAndDelete', }
	for _, key in ipairs( methods ) do
		cacheDB[ key ] = function( wrapper, ... )
			if not wrapper._db then
				error "cache_has_been_closed"
			end
			return wrapper._db[ key ]( wrapper._db, ... )
		end
	end
	
	WFCloseSupport.mustCloseAtThreadTermination( cacheDB )

end

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

return {
	initMessageProcessor = initMessageProcessor,
}
