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

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.

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

	-- RxLua
local Rx = require 'Rx'

	-- WF.core.reactive
local KSReactive = require 'KSReactive'

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

	-- WF.core.os
local KSDate = require 'KSDate'
local KSTableUtils = require 'KSTableUtils'

local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.core.diagnostic_log
local WFDiagnosticLog = require 'WFDiagnosticLog'

	-- WF.oz_client.sync
local WFCatSyncStatus = require 'WFCatSyncStatus'
local WFRxRunUntilDone = require "WFRxRunUntilDone"

local WFRxScheduler = require 'WFRxScheduler'

	-- WF.lrmobile
local LrmConfig = require 'LrmConfig'

local logger = require 'WFLogger' 'LrmLibraryStateModelImp'
-- :setLevel 'debug'

local LrmCatSyncStatus = require 'LrmCatSyncStatus'

local derefOpt = KSTableUtils.derefOpt

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

local LrmLibraryStateModelImp = {}

--------------------------------------------------------------------------------
local CALM_INTERVAL_PRESET_PROFILE = 5
local CALM_INTERVAL_DCP = 5

function LrmLibraryStateModelImp:applicationStateModel()

	local this = self
	
	local stateModel = {
		value = Rx.ReplaySubject( 1 ),
		
		events = {
		
			setIsInLoupe = function( self, isInLoupe )
			
				this._isInLoupe:onNext( isInLoupe )
				
			end,
			
			setIsWiFiOnlySync = function( self, isWiFiOnlySync )
			
				this._isWiFiOnlySync:onNext( isWiFiOnlySync )
				
			end,
			
			setIsAppInBackground = function( self, isAppInBackground )
			
				this._isAppInBackground:onNext( isAppInBackground )
			
			end,

			setBinaryPreferenceMaster = function( self, useMaster )
			
				local strategy = LrmConfig.StrategyPreferProxy
				
				if useMaster then
					strategy = LrmConfig.StrategyPreferMaster
				end
				
				this._binaryDownloadStrategy:onNext( strategy )
				
			end
		},
	}
	
	if WFPlatformInfo.isiOS then

		-- iOS specific

		stateModel.events.setReachabilityStatus = function( self, isReachable )

			this._isReachable:onNext( isReachable )

		end

	else

		stateModel.events.setIsUploading = function( self, enable )

			this._setIsUploading:onNext( enable )

		end

	end

	return stateModel

end

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

function LrmLibraryStateModelImp:binaryDownloaderStatusModel()

	return {
		value = KSReactive.tableB {
			isDownloading = self._binaryDownloader:isDownloadingB(),
			activeAlbum = self._binaryDownloader:activeAlbumB(),
		} 
	}
	
end
--------------------------------------------------------------------------------

function LrmLibraryStateModelImp:dcpBinaryDownloaderStatusModel()

	return {
		value = KSReactive.tableB {
			isDownloadingDcps = self._binaryDownloader:isDownloadingDcpsB(),
		}
	}

end

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

function LrmLibraryStateModelImp:updateAcrProfileDatabaseModel()

	local result = self._updateAcrProfileDatabase
		:map( function( value )
			return {
				updateAcrProfileDatabase = value,
				timestamp = KSDate.timeInIso8601Format( nil, false )
			}
		end )
	if WFPlatformInfo.isAndroid then
		result = result
			:calm( CALM_INTERVAL_DCP, WFRxScheduler.sharedScheduler() )
	end

	return {

		value = result,
	}

end

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

function LrmLibraryStateModelImp:updateAcrPresetProfileDatabaseModel()

	local result = self._updateAcrPresetProfileDatabase
		:map( function( value )
			return {
                updateAcrPresetProfileDatabase = value,
				timestamp = KSDate.timeInIso8601Format( nil, false )
			}
		end )
		 :calm( CALM_INTERVAL_PRESET_PROFILE, WFRxScheduler.sharedScheduler() )

	return {

		value = result,
	}

end

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

function LrmLibraryStateModelImp:hasAlbums()
	
	return {
		valueB = self.hasAlbumsB,
	}
end

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

function LrmLibraryStateModelImp:catalogAssetCount()

	local result = self.catalog:indexStore():query {
		sql = [[SELECT count(docId) from coreInfo]],
		calm = 20,
	}
	:map( function( data )
	
		return derefOpt( data, 1, 1 ) or 0
		
	end )
	
	return {
		value = result
	}
	
end

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

function LrmLibraryStateModelImp:catalogBackedupAssetCount()

	-- TODO jrechtie, 2019/07/25 move logic to LrmCatSyncStatus
	local syncStatusList = LrmCatSyncStatus.getAssetSyncStatusList()

	local backedupSyncStatuses = { 
		syncStatusList[ 'needsProxyOrMaster' ],
		syncStatusList[ 'needsDevelopXmp' ],
		syncStatusList[ 'complete' ],
		syncStatusList[ 'completeNoMaster' ],
		syncStatusList[ "stackComplete" ],
		syncStatusList[ "completeNoNegatives" ]
	}

	-- Expensive! Will scan large parts of coreInfo table. Only use when needed.
	local result = self.catalog:indexStore():queryOnce {
		sql = string.format( [[SELECT count(rowid) 
				FROM coreInfo 
				WHERE syncStatus IN ( %s )
					AND spaceId is NULL]], 
					WFSQLiteUtils.createVariableArgList( #backedupSyncStatuses ) ),
		unpack( backedupSyncStatuses )
	}
	:map( function( data )

		return derefOpt( data, 1, 1 ) or 0

	end )

	return {
		value = result
	}

end

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

local function catalogPresetsProfileTotalCount(catalog)
		return catalog:indexStore():query {
			sql = [[ SELECT needsSave, count(docId) FROM presetsProfiles group by needsSave ]],
		catm = 5,
    }
    :map(function(data)

	        local downloadedCount = 0
		        local totalCount = 0
		        for i=1, #data do
	            totalCount = totalCount + data[ i ][ 2 ]
		            if data[ i ][ 1 ] == 0 then
	                downloadedCount = data[ i ][ 2 ]
		            end
	        end

	        return {
	            TotalPresetProfiles = totalCount,
            DownloadedPresetProfiles = downloadedCount
		        }

    end)
end

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

function LrmLibraryStateModelImp:catalogPresetsProfileCount()

    return {
        value = catalogPresetsProfileTotalCount(self.catalog)
    }

end

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

function LrmLibraryStateModelImp:mayBeSlurpFavorites(profileFavorites, presetFavorites)

    logger:debugf('reached slurp %s', profileFavorites)

    local FAV_SLURP_MARKER_DOC_ID = "favoritesSlurpDone"
    local WFRxScheduler = require 'WFRxScheduler'
    local SLURP_ERROR_RETRY_INTERVAL = 60 --In secs. Arbitrary value.
    local catalog = self.catalog

    local function waitForCatalogDocumentToBeAvailable( catalog )

        return self._wfCatalogStream
        :flatMap( function( wfCatalog )

            -- Catalog document will either already be available
            -- or will eventually become available when the metadata
            -- puller is able to get and save it. Wait till
            -- its done.
            return catalog:docStore():ongoingDocValue( wfCatalog:ozCatalogSpec():catalogId() )

            :filter( function( docValue )
                return docValue and not docValue._empty
            end )

            :take( 1 )

        end)

    end

    local function createFavoritesTableForOz( favorites )
        local favsTable = {}
        local hasSomeFavorites = false
        for fp, isFav in pairs( favorites ) do
            favsTable[ fp ] = isFav
            hasSomeFavorites = true
        end
        return favsTable, hasSomeFavorites
    end

    local function isSlurpDone( localSlurpDocValue )
        return localSlurpDocValue and localSlurpDocValue[ FAV_SLURP_MARKER_DOC_ID ] == true
    end

    local data = Rx.ReplaySubject( 1 )

    catalog:docStore():localDocValue( FAV_SLURP_MARKER_DOC_ID )
    :flatMapLatest( function( docValue )

        logger:debugf('FAV_SLURP_MARKER_DOC_ID:%s', docValue)

        if isSlurpDone( docValue ) then
            -- If the slurp was done once, then return
            data:onNext('SlurpCompletedPreviously')
            return Rx.Observable.empty()
        end

        return waitForCatalogDocumentToBeAvailable(catalog)
        :flatMap(function (docValue)

            logger:debugf('got the docV:%s', docValue._id)

            local docOverlay = {
                _id = docValue._id,
            }

            local hasSomeFavorites = false

            if ( presetFavorites ) then
                local favorites
                favorites, hasSomeFavorites = createFavoritesTableForOz( presetFavorites )
                docOverlay.presets = {
                    favorites = favorites
                }
            end

            if ( profileFavorites ) then
                local favorites
                favorites, hasSomeFavorites = createFavoritesTableForOz( profileFavorites )
                docOverlay.profiles = {
                    favorites = favorites
                }
            end

            local updateFavObservable
            if hasSomeFavorites then
                logger:debugf('docOverlay:%s',docOverlay.profiles)
                updateFavObservable = catalog:docStore():applyOverlay( docOverlay )
            else
                updateFavObservable = Rx.Observable.just()
            end
            return updateFavObservable
            :flatMapLatest( function()

                return catalog:docStore():setLocalDocValue {
                    _id = FAV_SLURP_MARKER_DOC_ID,
                    [ FAV_SLURP_MARKER_DOC_ID ] = true,
                }
                :tap(function(_)
                    data:onNext('SlurpCompleted')
                end)

            end )

        end)

    end)
    :tapOnError( function( err )
        logger:errorf( "Favorites slurp failed. Error:%s", err )
    end )
    :catch( function()

        -- Setup retry for the error
        return Rx.Observable.timer( SLURP_ERROR_RETRY_INTERVAL, WFRxScheduler.sharedScheduler() )
        :flatMapLatest( function()
            return LrmLibraryStateModelImp.maybeSlurpFavorites( catalog )
        end )

    end )
    :call( WFRxRunUntilDone )

    return {
        value = data
    }
end

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

function LrmLibraryStateModelImp:generateDiagnosticLog()

	local result = Rx.ReplaySubject( 1 )
	
	WFDiagnosticLog.generateHtmlReportP( { appName = 'Lr mobile' } )
	:whenResolvedDo( function( status, data )
	
		if status == 'data' then
			result:onNext( { state = true, diagnosticData = data } )
		else
			result:onNext( { state = false, error = data } )
		end
	end ):runUntilDone()
	
	return {
		value = result,
	}
end

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

function LrmLibraryStateModelImp:hasInitialSyncCompletedModel()

	local hasCompletedInitialSync = self._wfCatalogStream
	
		:flatMapLatest( function( wfCatalog )
		
			return WFCatSyncStatus.isDoingInitialSync( wfCatalog )
			
		end )
		
		:map( function( isDoingInitialSync )

			return not isDoingInitialSync
			
		end )
		
	return {
		value = hasCompletedInitialSync
	}
	
end

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

function LrmLibraryStateModelImp:forceUpgrade()

	local syncClient = self.catalog:getSyncClient()
	
	local result
	
	if syncClient then
		result = syncClient:getRequiredUpdateStream()
	else
		result = Rx.Observable.just( false )
	end		
		
	return {
		value = result
	}
	
end

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

function LrmLibraryStateModelImp:isOzCatalogDeleted()

    local syncClient = self.catalog:getSyncClient()

	local result
	
	if syncClient then
		result = syncClient:isOzCatalogDeleted()
	else
		result = Rx.Observable.just( false )
	end		
		
	return {
		value = result
	}
	
end

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

function LrmLibraryStateModelImp:getReindexingProgress()

	local reindexingProgress = self._wfCatalogStream

	:filter( function( wfCatalog )
		return wfCatalog ~= nil
	end )

	:take( 1 )

	:flatMap( function( wfCatalog )
	
		-- The following merges two progress reporter streams. One progress
		-- reporter stream tracks reindexing progress, and the other tracks
		-- doc store initialization progress. Reindexing will not start
		-- until the doc store is fully initialized, so the following
		-- reactive chain will produce progress events for the doc store,
		-- and then switch to progress events for the index store. For clients,
		-- we expect this to appear as two separate house-keeping progress bars.
		-- Unfortunately, combining this data into a single progress bar is 
		-- difficult, if not impossible, because we don't know what needs to be
		-- reindexed until after the doc store has been initialized.
		
		-- Note that we skip the last data event from the doc store initialization
		-- progress reporter. This is to avoid clients prematurely removing the
		-- house-keeping dialog when they see the first "100%" progress event.
	
		return wfCatalog:indexStore():getReindexingProgress()
			:merge( wfCatalog:docStore():getInitializationProgress():skipLast( 1 ) )
			:calm( 1, WFRxScheduler.sharedScheduler() )
		
	end )

	return {
		value = reindexingProgress
	}

end

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

return LrmLibraryStateModelImp
