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

WFTestModelClient.lua

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

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

local WFCloseSupport = require 'WFCloseSupport'
local WFRxScheduler = require 'WFRxScheduler'
local WFRxEventBridge = require 'WFRxEventBridge'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'
local WFArguments = require 'WFArguments'
local WFModels = require 'WFModels'

local check = assert( WFArguments.check )
local runUntilDone = WFRxRunUntilDone
local asKSEvent = WFRxEventBridge

local Observable = Rx.Observable


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

local WFTestModelClient = {}

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

-- Takes a WFWorkerThread and returns a promise for the underlying messaging processor

local function messagingProcessor( workerThread )

	local copy = workerThread:_relay()

	return copy:threadIsReady()
		:concat( Observable.timer( 0.01, WFRxScheduler.sharedScheduler() ) )
		:takeLast( 1 )
		:map( function()
			if WFCloseSupport.hasBeenOrWillBeClosed( copy ) then
				error( string.format( "The WFWorkerThread thread has been closed\n\n" ), 2 )
			end

			return copy, copy._messagingProcessor
		end )

end

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

local ModelClient

ModelClient = Rx.Class 'ModelClient' {

	_model = false,
		-- observable

	_modelInstance = false,

	_subcription = false,
		-- (Disposable) from subscribing to the model. Ensures it doesn't dispose
		-- before this object is closed.
		-- TODO [arahn 2015-09-21]: Disposable and WFCloseSupport should play nicely
		-- together so I could simply do :setOwned() and pass in a disposable, etc..

	_events = false,

	__init = function( self, factoryOrParentModel, selector, ... )
		check( 'ModelClient', 'table', '+string' )

		local model

		local n = select( '#', ... )
		local params = { ... }

		model = Rx.ReplaySubject( 1 )
			-- use a disconnected, Observable to store the model.
			-- Otherwise this network ends up having a strong reference
			-- to factoryOrParentModel, and we need that to be GC-able
			-- as soon as we have created our own instance.

		local getModel = factoryOrParentModel
			:map( function( factoryOrParentModel )
				local subject = Rx.ReplaySubject( 1 )
				return factoryOrParentModel
					:openModel( selector, subject, unpack( params, 1, n ) )
			end )
			:take( 1 )

		self._subcription = getModel
			:subscribe( function( imp )
				if WFCloseSupport.hasBeenOrWillBeClosed( self ) then
					imp:close()
				else
					self._modelInstance = imp
					model:onNext( imp )
					if self._subcription then
						self._subcription:dispose()
						self._subcription = nil
					end
				end
			end )

		self._model = model:take( 1 )

	end,

	close = function( self )
		if self._subcription then
			self._subcription:dispose()
			self._subcription = nil
		end
		if self._modelInstance then
			self._modelInstance:close()
		end
	end,

	valueB = function( self, ... )

		return self:value():call( asKSEvent )
			:holdB( ... )

	end,

	value = function( self, ... )

		return self._model
			:flatMapLatest( function( modelInstance )
				if WFCloseSupport.hasBeenOrWillBeClosed( self ) then
					return Observable.never()
				end
				local value = modelInstance:value() or Observable.never()

				return value
			end )

	end,

	postEvent = function( self, ... )

		local n = select( '#', ... )
		local packed = { ... }
		self._model
			:map( function( modelInstance )
				modelInstance:postEvent( unpack( packed, 1, n ) )
			end )
			:call( runUntilDone )

	end,

	subModel = function( self, selector, ... )
		return self:openModel( selector, ... )
	end,

	openModel = function( self, selector, ... )

		return ModelClient( self._model, selector, ... )

	end,

	WFCloseSupport.addCloseSupportMethods

}

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

-- Manages the relationship between the client and server, and is responsible
-- for opening models.

local ModelFactory = Rx.Class 'ModelFactory' {

	_factory = false,
		-- Hot observable

	_factoryInstance = false,
		-- For close support

	_workerThread = false,

	__init = function( self, workerThread )

		-- Spins up both model client and server. Both of which are owned by the factory and will be destroyed
		-- when the factory finishes closing.
		self._factory = messagingProcessor( workerThread )
			:map( function( workerThread, messagingProcessor )
				self:setOwned {
					_workerThread = workerThread,
				}
				local factory = WFModels.openFactory( messagingProcessor )
				self:setOwned {
					_factoryInstance = factory
				}
				return factory
			end )
			:replay( nil, 1 )

		self._factory:connect()

	end,

	openModel = function( self, selector, ... )

		return ModelClient( self._factory, selector, ... )

	end,

	WFCloseSupport.addCloseSupportMethods

}

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

function WFTestModelClient.makeModelFactory( workerThread )

	return ModelFactory( workerThread )
	
end

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

return WFTestModelClient
