#	Copyright (c) 2019 Lawnjelly
#
#	Permission is hereby granted, free of charge, to any person obtaining a copy
#	of this software and associated documentation files (the "Software"), to deal
#	in the Software without restriction, including without limitation the rights
#	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#	copies of the Software, and to permit persons to whom the Software is
#	furnished to do so, subject to the following conditions:
#
#	The above copyright notice and this permission notice shall be included in all
#	copies or substantial portions of the Software.
#
#	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#	SOFTWARE.

extends Node2D


@export
var target: NodePath:
	get:
		return target
	set(v):
		target = v
		if is_inside_tree():
			_FindTarget()

var _m_Target: Node2D
var _m_Flip:bool = false

var _m_Trans_curr: Transform2D = Transform2D()
var _m_Trans_prev: Transform2D = Transform2D()

const SF_ENABLED = 1 << 0
const SF_GLOBAL_IN = 1 << 1
const SF_GLOBAL_OUT = 1 << 2
const SF_TOP_LEVEL = 1 << 3
const SF_INVISIBLE = 1 << 4
# ADDED: Pixel snap support.
const SF_PIXELSNAP = 1 << 5

@export_flags("enabled", "global in", "global out", "top level", "pixel snap") var flags: int = SF_ENABLED | SF_GLOBAL_IN | SF_GLOBAL_OUT | SF_PIXELSNAP:
	set(v):
		flags = v
		# we may have enabled or disabled
		_SetProcessing()
	get:
		return flags

##########################################################################################
# USER FUNCS


# call this checked e.g. starting a level, AFTER moving the target
# so we can update both the previous and current values
func teleport():
	_RefreshTransform()
	_m_Trans_prev = _m_Trans_curr

	# call frame upate to make sure all components of the node are set
	_process(0)


func set_enabled(bEnable: bool):
	_ChangeFlags(SF_ENABLED, bEnable)
	_SetProcessing()


func is_enabled():
	return _TestFlags(SF_ENABLED)

func set_pixel_snap(bPixelSnap: bool):
	_ChangeFlags(SF_PIXELSNAP, bPixelSnap)

##########################################################################################


func _ready():
	set_process_priority(100)
	Engine.set_physics_jitter_fix(0.0)
	set_as_top_level(_TestFlags(SF_TOP_LEVEL))


func _SetProcessing():
	var bEnable = _TestFlags(SF_ENABLED)
	if _TestFlags(SF_INVISIBLE):
		bEnable = false

	set_process(bEnable)
	set_physics_process(bEnable)
	set_as_top_level(_TestFlags(SF_TOP_LEVEL))


func _enter_tree():
	# might have been moved
	_FindTarget()


func _notification(what):
	match what:
		# invisible turns unchecked processing
		NOTIFICATION_VISIBILITY_CHANGED:
			_ChangeFlags(SF_INVISIBLE, is_visible_in_tree() == false)
			_SetProcessing()


func _RefreshTransform():

	if _HasTarget() == false:
		return

	_m_Trans_prev = _m_Trans_curr

	if _TestFlags(SF_GLOBAL_IN):
		_m_Trans_curr = _m_Target.get_global_transform()
	else:
		_m_Trans_curr = _m_Target.get_transform()

	_m_Flip = false
	# Ideally we would use determinant core function, as in commented line below, but we
	# need to workaround for backward compat.
	# if (_m_Trans_prev.determinant() < 0) != (_m_Trans_curr.determinant() < 0):

	if (_Determinant_Sign(_m_Trans_prev) != _Determinant_Sign(_m_Trans_curr)):
		_m_Flip = true

func _Determinant_Sign(t:Transform2D)->bool:
	# Workaround Transform2D determinant function not being available
	# until 3.6 / 4.1.
	# We calculate determinant manually, slower but compatible to lower
	# godot versions.
	var d = (t.x.x * t.y.y) - (t.x.y * t.y.x)
	return d >= 0.0


func _FindTarget():
	_m_Target = null

	# If no target has been assigned in the property,
	# default to using the parent as the target.
	if target.is_empty():
		var parent = get_parent()
		if parent and (parent is Node2D):
			_m_Target = parent
		return

	var targ = get_node(target)

	if ! targ:
		printerr("ERROR SmoothingNode2D : Target " + str(target) + " not found")
		return

	if not targ is Node2D:
		printerr("ERROR SmoothingNode2D : Target " + str(target) + " is not Node2D")
		target = ""
		return

	# if we got to here targ is correct type
	_m_Target = targ

func _HasTarget() -> bool:
	if _m_Target == null:
		return false

	# has not been deleted?
	if is_instance_valid(_m_Target):
		return true

	_m_Target = null
	return false


func _process(_delta):
	var f = Engine.get_physics_interpolation_fraction()

	var tr = Transform2D()
	tr.origin = lerp(_m_Trans_prev.origin, _m_Trans_curr.origin, f)
	if _TestFlags(SF_PIXELSNAP):
		tr.origin = (tr.origin + Vector2(0.5, 0.5)).floor()
	tr.x = lerp(_m_Trans_prev.x, _m_Trans_curr.x, f)
	tr.y = lerp(_m_Trans_prev.y, _m_Trans_curr.y, f)

	# When a sprite flip is detected, turn off interpolation for that tick.
	if _m_Flip:
		tr = _m_Trans_curr

	if _TestFlags(SF_GLOBAL_OUT) and not _TestFlags(SF_TOP_LEVEL):
		set_global_transform(tr)
	else:
		set_transform(tr)


func _physics_process(_delta):
	_RefreshTransform()


func _SetFlags(f):
	flags |= f


func _ClearFlags(f):
	flags &= ~f


func _TestFlags(f):
	return (flags & f) == f


func _ChangeFlags(f, bSet):
	if bSet:
		_SetFlags(f)
	else:
		_ClearFlags(f)
