extends ReactivePropertyBase
class_name ReactiveProperty

## An observable property.
##
## Wraps a value and emits an item whenever it is changed.
## The emitted item is the new value of the [ReactiveProperty].

var _latest_value
var _source_subscription : DisposableBase
var _observers : Array[ObserverBase]
var _distinct_until_changed : bool
var _raise_latest_value_on_subscribe : bool

var _rwlock : ReadWriteLock

var is_disposed : bool

func _get_value():
	return self._latest_value

func _set_value(value):
	if self._distinct_until_changed and self._latest_value == value:
		return
	self._latest_value = value
	
	var observers_ : Array[ObserverBase]
	if true:
		var __ = ReadWriteLockGuard.new(self._rwlock, true)
		observers_ = self._observers.duplicate()
	for obs in observers_:
		obs.on_next(value)

func _to_string() -> String:
	if self.is_disposed:
		return "<<Disposed ReactiveProperty>>"
	return str(self.Value)

func _init(
	initial_value_,
	distinct_until_changed_ : bool = true,
	raise_latest_value_on_subscribe_ : bool = true,
	source : Observable = null
):
	var wself : WeakRef = weakref(self)
	
	self.is_disposed = false
	
	self._observers = []
	self._rwlock = ReadWriteLock.new()
	
	self._latest_value = initial_value_
	self._distinct_until_changed = distinct_until_changed_
	self._raise_latest_value_on_subscribe = raise_latest_value_on_subscribe_
	
	if source != null:
		self._source_subscription = source.subscribe(func(i): wself.get_ref().Value = i)
	
	@warning_ignore("shadowed_variable")
	var subscribe = func(
		observer : ObserverBase,
		_scheduler : SchedulerBase = null
	) -> DisposableBase:
		var prop : ReactiveProperty = wself.get_ref()
		
		if not prop or prop.is_disposed:
			observer.on_completed()
			return Disposable.new()
		
		if true:
			var __ = ReadWriteLockGuard.new(prop._rwlock, false)
			prop._observers.push_back(observer)
		
		if prop._raise_latest_value_on_subscribe:
			observer.on_next(prop._latest_value)
		
		var dispose_ = func():
			var _prop : ReactiveProperty = wself.get_ref()
			if not _prop:
				return
			var __ = ReadWriteLockGuard.new(_prop._rwlock, false)
			_prop._observers.erase(observer)
		
		return Disposable.new(dispose_)
	
	super._init(subscribe)

func dispose():
	if this.is_disposed:
		return
	
	this.is_disposed = true
	
	var observers_ : Array[ObserverBase]
	if true:
		var __ = ReadWriteLockGuard.new(this._rwlock, true)
		observers_ = this._observers.duplicate()
	for obs in observers_:
		obs.on_completed()
	
	if true:
		var __ = ReadWriteLockGuard.new(this._rwlock, false)
		this._observers.clear()
	
	if this._source_subscription != null:
		this._source_subscription.dispose()

func to_readonly() -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		self, Value, self._distinct_until_changed, 
		self._raise_latest_value_on_subscribe
	)

static func FromGetSet(
	getter : Callable,
	setter : Callable,
	distinct_until_changed_ : bool = true,
	raise_latest_value_on_subscribe_ : bool = true
) -> ReactiveProperty:
	var prop = ReactiveProperty.new(
		getter.call(),
		distinct_until_changed_,
		raise_latest_value_on_subscribe_
	)
	
	prop.subscribe(func(x): setter.call(x)).dispose_with(prop)
	return prop

static func FromMember(
	target,
	member_name : StringName,
	convert_cb = GDRx.basic.identity,
	convert_back_cb = GDRx.basic.identity,
	distinct_until_changed_ : bool = true,
	raise_latest_value_on_subscribe_ : bool = true
) -> ReactiveProperty:
	
	var getter = func():
		return target.get(member_name)
	var setter = func(v):
		target.set(member_name, v)
	
	return FromGetSet(
		func(): return convert_cb.call(getter.call()),
		func(x): setter.call(convert_back_cb.call(x)),
		distinct_until_changed_,
		raise_latest_value_on_subscribe_
	)

static func Derived(p : ReadOnlyReactiveProperty, fn : Callable) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p.map(fn),
		fn.call(p.Value)
	)

static func Computed1(p : ReadOnlyReactiveProperty, fn : Callable) -> ReadOnlyReactiveProperty:
	return Derived(p, fn)

static func Computed2(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1))),
		fn.call(p1.Value, p2.Value)
	)

static func Computed3(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	p3 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable, p3 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2))),
		fn.call(p1.Value, p2.Value, p3.Value)
	)

static func Computed4(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	p3 : ReadOnlyReactiveProperty,
	p4 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3))),
		fn.call(p1.Value, p2.Value, p3.Value, p4.Value)
	)

static func Computed5(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	p3 : ReadOnlyReactiveProperty,
	p4 : ReadOnlyReactiveProperty,
	p5 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4))),
		fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value)
	)

static func Computed6(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	p3 : ReadOnlyReactiveProperty,
	p4 : ReadOnlyReactiveProperty,
	p5 : ReadOnlyReactiveProperty,
	p6 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable, p6 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4), tup.at(5))),
		fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value, p6.Value)
	)

static func Computed7(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	p3 : ReadOnlyReactiveProperty,
	p4 : ReadOnlyReactiveProperty,
	p5 : ReadOnlyReactiveProperty,
	p6 : ReadOnlyReactiveProperty,
	p7 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable, p6 as Observable, p7 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4), tup.at(5), tup.at(6))),
		fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value, p6.Value, p7.Value)
	)

static func Computed8(
	p1 : ReadOnlyReactiveProperty,
	p2 : ReadOnlyReactiveProperty,
	p3 : ReadOnlyReactiveProperty,
	p4 : ReadOnlyReactiveProperty,
	p5 : ReadOnlyReactiveProperty,
	p6 : ReadOnlyReactiveProperty,
	p7 : ReadOnlyReactiveProperty,
	p8 : ReadOnlyReactiveProperty,
	fn : Callable
) -> ReadOnlyReactiveProperty:
	return ReadOnlyReactiveProperty.new(
		p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable, p6 as Observable, p7 as Observable, p8 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4), tup.at(5), tup.at(6), tup.at(7))),
		fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value, p6.Value, p7.Value, p8.Value)
	)
