extends Camera2D

#save first target in "target" Group (when everything is ready)
#TODO: what if switching between two players on the fly?
var target = null
var loaded_dependencies = false

export var follow_player = true

#limit boundaries where the camera can move
export var verticalBoundary = Vector2(-10000,0)
export var horizontalBoundary = Vector2(0,10000)
export var limit_return = false


export var blockLeftBoundary = true
export var blockRightBoundary = true
export var blockTopBoundary = false
export var blockBottomBoundary = false

# exported parameters
export var speed = 80.0
export var decay_rate = 2
export var max_rumble_x = 400
export var max_rumble_y = 200
export var smoothing = 0.2
export(float, 1) var fixed_trauma = 0.0 setget fixed_trauma_update
export(float, 0.25, 3) var camera_zoom = 1.0 setget change_camera_zoom
#export var max_offset = 0.2

export (float, 0.0, 10000.0) var automatic_camera_movement = 0.0

onready var camera_width = ProjectSettings.get_setting("display/window/size/width")
onready var camera_height = ProjectSettings.get_setting("display/window/size/height")
onready var ratio = camera_width / camera_height

var is_ready = false

# default values
var start_position = position
var destination_position = Vector2(0,0)
var current_position = Vector2(0,0)
var gui = Node2D.new()
var debug = Node2D.new()
var dialogo = Node2D.new()
var effects = Node2D.new()
var effect_position = Vector2(0,0)

var trauma = 0.0
var time = 0.0
var noise = OpenSimplexNoise.new()
var noise_seed = randi()

var rumble_x = 0
var rumble_y = 0
var automatic_initial_x = 0

# configure noise
func _init():
	noise.seed = noise_seed
	noise.octaves = 1
	noise.period = 256.0
	noise.persistence = 1
	noise.lacunarity = 3.0
	is_ready = true

func set_gui(obj):
	gui = obj
	
func set_debug(obj):
	debug = obj
	
func set_dialogo(obj):
	dialogo = obj


# add trauma to start/continue the shake
func add_trauma(amount):
	trauma = min(trauma + amount, 1.0)
	
# add trauma to start/continue the shake
func set_trauma(amount):
	trauma = amount
	fixed_trauma = trauma

 
# decay the trauma effect over time
func decay_trauma(delta):
	var change = decay_rate * delta
	trauma = max(trauma - change, 0.0)
	if fixed_trauma > trauma:
		trauma = fixed_trauma


func fixed_trauma_update(new_trauma):
	fixed_trauma = new_trauma
	trauma = fixed_trauma


func change_camera_zoom(new_zoom):
	zoom = Vector2(new_zoom, new_zoom)
	camera_zoom = new_zoom


# reset camera pose inside boundaries
func repos_inside_boundaries():
	#limit camera to its physical boundaries

	if (position.x < horizontalBoundary.x + rumble_x + camera_width/2):
		position.x = horizontalBoundary.x + rumble_x + camera_width/2
	if (position.x > horizontalBoundary.y - rumble_x - camera_width/2):
		position.x = horizontalBoundary.y - rumble_x - camera_width/2	
	
	if (position.y < verticalBoundary.x + rumble_y + camera_height/2):
		position.y = verticalBoundary.x + rumble_y + camera_height/2
	if (position.y > verticalBoundary.y - rumble_y - camera_height/2):
		position.y = verticalBoundary.y - rumble_y - camera_height/2
	
	gui.position = Vector2( position.x - 640, position.y + 360 )
	debug.position = Vector2( position.x - 640, position.y + 360 )
	if dialogo: dialogo.position = Vector2( position.x - 640, position.y + 360 )
	if effects: effects.position = Vector2( position.x - 640, position.y + 360 )
	effect_position = Vector2( position.x - 640, position.y - 360 )
	
	if limit_return:
		horizontalBoundary.x = position.x - 640

#reset camera at target position
func reset_camera():
	if target:
		position = target.position
		start_position = target.position

# return a random float in range(-1, 1) using OpenSimplex noise
func get_noise_value(seed_value, _time):
	noise.seed = seed_value
	return noise.get_noise_2d(_time, _time)

func set_object_to_follow(obj):
	target = obj
	position = target.position
	start_position = target.position

# Called when the node enters the scene tree for the first time.
func _ready():
	repos_inside_boundaries()
	if get_tree().get_nodes_in_group("Player") and follow_player:
		target = get_tree().get_nodes_in_group("Player")[0]
	trauma = fixed_trauma
	is_ready = true
	

func add_camera_trauma(_trauma):
	add_trauma(_trauma)

		
func follow_target(delta):
	
	if target == null: return false
	
	# using a magic number here to get a pleasing effect at speed 1.0
	time += delta * speed * 5000.0
	var shake = trauma * trauma
	
	rumble_x = max_rumble_x * shake * get_noise_value(noise_seed, time)
	rumble_y = max_rumble_y * shake * get_noise_value(noise_seed + 1, time)
		
	destination_position = target.position
	current_position += Vector2(destination_position.x - current_position.x, destination_position.y - current_position.y) / smoothing * delta
	
	position = current_position.round() + Vector2(rumble_x, rumble_y)
	
	effect_position = Vector2( position.x + 640, position.y - 360 )
	
	gui.position = Vector2( position.x - 640, position.y + 360 )
	debug.position = Vector2( position.x - 640, position.y + 360 )
	if dialogo: dialogo.position = Vector2( position.x - 640, position.y + 360 )
	if effects: effects.position = Vector2( position.x - 640, position.y + 360 )
	
	
	

func margin_update_post():
	if not target: return
	
	if blockLeftBoundary and target.position.x < position.x - 600:
		target.position.x = position.x - 600
		
	if blockRightBoundary and target.position.x > position.x + 600:
		target.position.x = position.x + 600




func automatic_movement(delta):
	
	time += delta * speed * 5000.0
	var shake = trauma * trauma
	
	rumble_x = max_rumble_x * shake * get_noise_value(noise_seed, time)
	rumble_y = max_rumble_y * shake * get_noise_value(noise_seed + 1, time)
	
	destination_position = target.position

	if automatic_camera_movement == 0:
		automatic_initial_x = position.x
	elif automatic_initial_x == 0:
		automatic_initial_x = position.x
	else:
		automatic_initial_x = automatic_initial_x + automatic_camera_movement / 100
	destination_position.x = automatic_initial_x
	
	current_position += Vector2(destination_position.x - current_position.x, destination_position.y - current_position.y) / smoothing * delta
	
	position = current_position.round() + Vector2(rumble_x, rumble_y)
	effect_position = Vector2( position.x + 640, position.y - 360 )	
	gui.position = Vector2( position.x - 640, position.y + 360 )
	debug.position = Vector2( position.x - 640, position.y + 360 )
	dialogo.position = Vector2( position.x - 640, position.y + 360 )
	effects.position = Vector2( position.x - 640, position.y + 360 )


# -------- where all physics happens every frame
func _process(delta):	
	
	if trauma > 0.0:
		decay_trauma(delta)

	if automatic_camera_movement > 0:
		automatic_movement(delta)
	else:
		limit_return = false
		automatic_initial_x = 0
		follow_target(delta)

	
	#limit camera to its physical boundaries
	repos_inside_boundaries()
	margin_update_post()

	if Game.effects != null: Game.effects.change_pos(position)
	if Controller.touchpad:Controller.touchpad.position = Vector2(position.x + offset.x - 640, position.y + offset.y - 360)
	
	limit_top = verticalBoundary.x - 250
	limit_bottom = verticalBoundary.y + 250
	limit_left = horizontalBoundary.x - 250
	limit_right = horizontalBoundary.y + 250	
	
	force_update_scroll()
