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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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'

local KSTableUtils = require "KSTableUtils"
local KSStringUtils = require "KSStringUtils"
local WFArguments = require 'WFArguments'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFRxIndexStoreImpUtils = require 'WFRxIndexStoreImpUtils'
local WFRxThrottledBatchedBulkApplyOverlay = require "WFRxThrottledBatchedBulkApplyOverlay"

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

local check = assert( WFArguments.check )

local LrmLibraryDatabaseModelImp = {}

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

function LrmLibraryDatabaseModelImp.docValue( parentModel, docId, docType )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )
	
	if docType == nil then
		return {
			value = docStore:docValue( docId )
		}
	
	else
	
		return {
			value = docStore:docValueOnceP( docType, docId )
		}
		
	end
		
end

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

function LrmLibraryDatabaseModelImp.docValueWithFieldSpec( parentModel, docId, optKeySpec )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )

	return {
		value = docStore:docValue( docId, nil, optKeySpec )
	}

end

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

function LrmLibraryDatabaseModelImp.ongoingDocValue( parentModel, docId, docType )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )
	
	if docType == nil or WFPlatformInfo.isAndroid then
	
		return {
			value = docStore:ongoingDocValue( docId )
		}
		
	else
	
		return {
			value = docStore:docValueB( docType, docId )
		}
	
	end
	
	
end

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

function LrmLibraryDatabaseModelImp.bulkDocValue( parentModel, assetIds, fieldSpec )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )
		
	return {
		value = docStore:bulkDocValue( assetIds, fieldSpec )
	}
	
end

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

if WFPlatformInfo.isiOS then

	function LrmLibraryDatabaseModelImp.applyOverlay( parentModel, overlay )

		local catalog = assert( parentModel.catalog )
		local docStore = assert( catalog:docStore() )

		return {
			value = docStore:applyOverlay( overlay )
		}

	end

else

	function LrmLibraryDatabaseModelImp.applyOverlay( parentModel, docId, docType, overlay )

		local catalog = assert( parentModel.catalog )
		local docStore = assert( catalog:docStore() )

		return {
			value = docStore:applyOverlayToDocumentP {
				docType = docType,
				docId = docId,
				overlay = overlay,
			}
		}

	end

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

function LrmLibraryDatabaseModelImp.queryIndexStore( parentModel, args, ... )

	WFArguments.check( 'table', 'table|string' )

	local catalog = assert( parentModel.catalog )
	local indexStore = assert( catalog:indexStore() )

	if WFPlatformInfo.isAndroid then
		args = {sql = args}
	else
		local n = select( '#', ... )

		if n > 0 then

			local queryArguments = { ... }

			for i = 1, n do
				table.insert( args, queryArguments[ i ] )
			end

		end
	end

	if ( args.oneShot == true ) then

		return {
			value = indexStore:queryOnce( args )
		}

	else

		return {
			value = indexStore:query( args )
		}

	end

end

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

--[[

	See WFRxIndexStoreImpUtils.genericIndexStoreQuery for information on arguments
	and return values.

]]

function LrmLibraryDatabaseModelImp.genericIndexStoreQuery( parentModel, tableName, columnName, keys )

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

	local catalog = assert( parentModel.catalog )

	local query = WFRxIndexStoreImpUtils.genericIndexStoreQuery( catalog, tableName, columnName, keys )

	return {
		value = query
	}

end

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


function LrmLibraryDatabaseModelImp.makeAssetInfoModel( parentModel, assetIds, keyPaths, sep )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )

	local WFPlatformInfo = require 'WFPlatformInfo'
	
	sep = sep or "/"

	if WFPlatformInfo.isiOS then
		error( "this model is deprecated for iOS" )
	end

	--[[ LrA still relies on the path based mechanism instead of the nested format used elsewhere.
	
		The expected values from this call look like this:
		
		{ 
			a2d63c3aadbd4f34a3d11a64f062eb1d = {
				captureDate = "2016-11-03T02:28:40.000+05:30",
				["xmp/aux/Lens"] = "Samsung Galaxy S7 Rear Camera",
				["xmp/exif/ApertureValue"] = {
					1531069,
					1000000,
				}
			}
		}	

	]]--

	
	local fieldSpec = {}

	for _, path in ipairs( keyPaths ) do
		local infoPath = KSStringUtils.split( path, sep )
		KSTableUtils.setNested( fieldSpec, true, unpack( infoPath ) )
	end

	fieldSpec._id = true

	local docValues = docStore:bulkDocValue( assetIds, fieldSpec )
		:map( function( docValueArray )
		
			local trimmedDocs = {}
		
			for _, docValue in ipairs( docValueArray ) do

				local doc = {}

				for _, path in ipairs( keyPaths ) do
					local infoPath = KSStringUtils.split( path, sep )
					doc[ path ] = KSTableUtils.derefOpt( docValue, unpack( infoPath ) )					
				end

				trimmedDocs[ docValue._id or docValue._docId ] = doc

			end

			trimmedDocs[ 'state' ] = true

			return trimmedDocs

		end )

	return {
		value = docValues,
	}

end

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

function LrmLibraryDatabaseModelImp.setAssetCustomData( parentModel, assetId, attribute, data, isValidForCurrentRevision )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )

	local localOnly = {}
	local customFields = {}
	local customData = {
		isValidForCurrentRevision = isValidForCurrentRevision,
		currentRevision = '',
		data = data,
	}
	
	customFields[attribute] = customData
	localOnly['lrm_customData'] = customFields
	
	local result = docStore:docValue( assetId )
	:flatMapLatest( function( docValue )

		if ( docValue._empty or docValue._deleted ) then
			return Rx.Observable.just( { state = false, error = 'document empty or deleted' } )
		end

		customData.currentRevision = docValue._revId

		return docStore:applyOverlay( {
			_id = assetId,
			type = 'asset',
			_localOnly = localOnly,
		} )
		:map( function()
			return { state = true }
		end )
		:catch( function( error )
			return Rx.Observable.just( { state = false, error = error } )
		end )

	end )
	:catch( function( error )
		return Rx.Observable.just( { state = false, error = error } )
	end ) 

	return {
		value = result
	}
end

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

function LrmLibraryDatabaseModelImp.compact( parentModel, optVacuum )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )
	
	return {
		value = docStore:compact( optVacuum )
	}

end

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

function LrmLibraryDatabaseModelImp.throttledBulkApplyOverlay( parentModel, desc, overlays, optBatchSize, optCallsPerSecond )

	local catalog = assert( parentModel.catalog )

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

	optBatchSize = optBatchSize or 100
	optCallsPerSecond = optCallsPerSecond or 10

	local result = catalog._wfCatalogStream
		:take( 1 )
		:flatMap( function( wfCatalog )
			 return WFRxThrottledBatchedBulkApplyOverlay( desc, wfCatalog:docStore(), overlays, optBatchSize, optCallsPerSecond )
		end )

	return {
		value = result
	}

end

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


function LrmLibraryDatabaseModelImp.makeBulkDocValueModel( parentModel, assetIds, optFieldSpec )

	local catalog = assert( parentModel.catalog )
	local docStore = assert( catalog:docStore() )

	if optFieldSpec then
		optFieldSpec._id = true
	end

	local docValues = docStore:bulkDocValue( assetIds, optFieldSpec )
		:map( function( strippedDocValues )

			local result = {}

			for _, docValue in ipairs( strippedDocValues ) do
				result[ docValue._id ] = docValue
			end

			return result

		end )

	return {
		value = docValues
	}

end

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

-- Get a stream that fires with SQLite error codes (integer) when a doc store or
-- index store operation encounters a critical error. Possible error codes include:
--
-- 10 : Some kind of disk I/O error occurred
-- 11 : The database disk image is malformed
-- 13 : Insertion failed because database is full

function LrmLibraryDatabaseModelImp.getSQLiteFatalErrorCodeStreamfunction( parentModel )

	local catalog = assert( parentModel.catalog )

	local syncClient = catalog:getSyncClient()

	return {
		value = syncClient:getSQLiteFatalErrorCodeStream()
	}

end

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

return LrmLibraryDatabaseModelImp
