extends Camera2D

var horizontal_facing = 0
var vertical_facing = 0
var camera_vertical_shift = 0
var right_move_time: float = 0
var left_move_time: float = 0
var slow_time: float = 0
var input_time: float = 0
var original_limit_left: int
var original_limit_right: int
var original_limit_bottom: int
var original_limit_top: int
var camera_is_panning: bool = false
var target_offset: Vector2 = Vector2(0,0)

export var camera_horizontal_shift = 60
export var offset_reset_seconds := 1
export var offset_adapt_seconds := 1
export var offset_input_seconds := 0.618 * 2

onready var levelState := $"%LevelState"
onready var signalManager := $"%SignalManager"
onready var shiftLeft = $CameraAnimationPlayer.get_animation("shiftingLeft")
onready var shiftRight = $CameraAnimationPlayer.get_animation("shiftingRight")
onready var shiftCenter = $CameraAnimationPlayer.get_animation("shiftingCenter")
onready var anim_player := $CameraAnimationPlayer
onready var horizontal_tween := $HorizontalShiftTween
onready var original_x_zoom := zoom.x
onready var original_y_zoom := zoom.y
onready var blobby := get_node("%Blobby")

var image = Image.new()
var texture = ImageTexture.new()
var prev_pos: Vector2
var camera_state := "centered"

var screen_rect = Vector2()
var old_screen_rect = Vector2(ProjectSettings.get_setting("display/window/size/width") * zoom.x, ProjectSettings.get_setting("display/window/size/height") * zoom.y )
var screen_center = Vector2()
var screen_bottom = Vector2()
var screen_top = Vector2()
var screen_left = Vector2()
var screen_right = Vector2()

# Gets the camera limits from the tilemap of the level
# Requires "TileMap" to be a sibling of blobby
func _ready():
    _set_boundaries()
    get_tree().get_root().connect("size_changed", self, "_set_boundaries")
    self.position = blobby.global_position
    image.create(128, 2, false, Image.FORMAT_RGBAH)
    # TODO Test Performance
    _update_lighting_shader()
    # TODO Trigger when needed
    signalManager.connect("terminal_activated", self, "_on_SignalManager_terminal_activated")
    signalManager.connect("player_died", self, "_death_cam")

func _on_SignalManager_terminal_activated(animation_number: int = 0):
    get_node("LightAnimationPlayer").play("Pulsing")

#func _draw():
#		draw_line(Vector2((limit_left - position.x), screen_center.y), screen_left, Color(255, 0, 0), 1)

func _physics_process(delta: float) -> void:
#	update()
    screen_center = (get_camera_screen_center() - position)
    screen_bottom = screen_center + Vector2(0, screen_rect.y/2)
    screen_top = screen_center - Vector2(0, screen_rect.y/2)
    screen_left = screen_center - Vector2(screen_rect.x/2, 0)
    screen_right = screen_center + Vector2(screen_rect.x/2, 0)
    var was_adjusted := false
    if(!levelState.is_dead):
        was_adjusted = _adjust_offset(delta)

    if(anim_player.is_playing() || was_adjusted):
        position = blobby.position
        prev_pos = position
        _update_lighting_shader()
        return
    var player_vel = (blobby.position - prev_pos)/delta
    # TODO Take average of velocity here
    if(abs(player_vel.x) >= blobby.max_velocity["walk"] * 0.97
        && (sign(player_vel.x) == sign(target_offset.x) || target_offset.x == 0)):
        if(player_vel.x > 0):
            right_move_time += delta
            left_move_time = max(0, left_move_time - delta)
            slow_time = max(0, slow_time - delta)
        else:
            left_move_time += delta
            right_move_time = max(0, right_move_time - delta)
            slow_time = max(0, slow_time - delta)
    elif(abs(player_vel.x) <= blobby.max_velocity["walk"] * 0.9
            || sign(player_vel.x) != sign(target_offset.x)  || target_offset.x == 0):
        slow_time += delta
        left_move_time = max(0, left_move_time - delta)
        right_move_time = max(0, right_move_time - delta)

    _adapt_to_movement(player_vel)
    if abs(player_vel.x) <= blobby.max_velocity["walk"] * 0.9:
        _adapt_to_input(player_vel, delta)
    position = blobby.position
    prev_pos = position
    _update_lighting_shader()

# TODO This has to be redone when the screen is resized in any way
# Otherwise the boundaries will not be correct anymore
func _set_boundaries():
    screen_rect = get_viewport_rect().size
    screen_rect.x *= zoom.x
    screen_rect.y *= zoom.y
    original_x_zoom = zoom.x
    original_y_zoom = zoom.y
    # This is ok, because it only happens on initialization
    # But it is also quite fickle
    var tilemap = get_node("./%TileMap")
    # TODO: This goes wrong when overwriting old tiles with new sprites
    # New pngs -> completely new tiles and rebuild map
    var rect = tilemap.get_used_rect()
    var cell_size = tilemap.cell_size
    # TODO  is fixed for camera issue in adjust horizontal
    limit_right = rect.end.x * cell_size.x - 6
    limit_left = rect.position.x * cell_size.x + 6
    limit_top = rect.position.y * cell_size.y + 6
    limit_bottom = rect.end.y * cell_size.y - 6
    original_limit_left = limit_left
    original_limit_right = limit_right
    original_limit_top = limit_top
    original_limit_bottom = limit_bottom
    var screen_size = get_viewport_rect()
    var h_pixels = limit_right - limit_left
    var v_pixels = limit_bottom - limit_top
    # TODO: Fix that it can zoom both?
    if screen_size.end.x * original_x_zoom - h_pixels > 0:
        zoom.x = h_pixels / screen_size.end.x
        zoom.y = zoom.x
    if screen_size.end.y * original_y_zoom - v_pixels > 0:
        zoom.y = v_pixels / screen_size.end.y
        zoom.x = zoom.y

# Smoothing the camera limits in godot ruins something
func _adapt_to_movement(velocity: Vector2) -> void:
    var offset_track
    var center = get_camera_screen_center()
    var left_edge_pos = center.x - screen_rect.x/2 + camera_horizontal_shift
    var right_edge_pos = center.x + screen_rect.x/2 - camera_horizontal_shift
    if(left_move_time >= offset_adapt_seconds && !anim_player.is_playing()):
        left_move_time = 0
        target_offset.x = -camera_horizontal_shift
        if(offset == target_offset):
            return
        offset_track = shiftLeft.find_track(".:offset")
        shiftLeft.track_set_key_value(offset_track, 0, offset)
        shiftLeft.track_set_key_value(offset_track, 1, target_offset)
        camera_state = "shiftedLeft"
        anim_player.play("shiftingLeft")
    elif(right_move_time >= offset_adapt_seconds && !anim_player.is_playing()):
        right_move_time = 0
        target_offset.x = camera_horizontal_shift
        if(offset == target_offset):
            return
        offset_track = shiftRight.find_track(".:offset")
        shiftRight.track_set_key_value(offset_track, 0, offset)
        shiftRight.track_set_key_value(offset_track, 1, target_offset)
        camera_state = "shiftedRight"
        anim_player.play("shiftingRight")
    elif(slow_time >= offset_reset_seconds &&
         !(Input.is_action_pressed("up") ||  Input.is_action_pressed("duck"))):
        slow_time = 0
        target_offset.x = 0
        if(offset == target_offset):
            return
        if(left_edge_pos > limit_left && limit_right > right_edge_pos):
            offset_track = shiftCenter.find_track(".:offset")
            shiftCenter.track_set_key_value(offset_track, 0, offset)
            shiftCenter.track_set_key_value(offset_track, 1, target_offset)
            camera_state = "centered"
            anim_player.play("shiftingCenter")
    return

func _adapt_to_input(velocity: Vector2, delta: float) -> void:
    # TODO Den bug dass man damit durch die map gucken kann wenn man sich weiter bewegt
    # lasse ich erstmal drin
    if(velocity.length() > 20.0):
        input_time = 0
        return
    if(input_time < offset_input_seconds):
        input_time += delta
        return
    if Input.is_action_pressed("duck"):
        if(original_limit_bottom - position.y - 2 > screen_bottom.y && offset.y < 48):
            offset.y += 0.5
    elif Input.is_action_pressed("up"):
        if(original_limit_top - position.y + 2 < screen_top.y  && offset.y > -48):
            offset.y -= 0.5

# TODO This is a regulatory problem, it doesn't adapt fast enough
# TODO Maybe just make background black and dont bother
func _adjust_offset(delta: float) -> bool: 
    var new_offset = offset
    if (limit_left - position.x - screen_left.x > 0.1):
        if (anim_player.is_playing()):
            anim_player.stop(true)
        new_offset.x += (limit_left - position.x - screen_left.x)/1.5
    if (limit_right - position.x - screen_right.x < 0.1):
        if (anim_player.is_playing()):
            anim_player.stop(true)
        new_offset.x += (limit_right - position.x - screen_right.x)/1.5
    if (limit_top - position.y - screen_top.y > 0.001):
        new_offset.y += (limit_top - position.y - screen_top.y)/1.5
    if (limit_bottom - position.y - screen_bottom.y < 0.001):
        new_offset.y += (limit_bottom - position.y - screen_bottom.y)/1.5
    #print(abs(offset.x) - abs(new_offset.x))
    if(abs(offset.x) > abs(new_offset.x) || abs(offset.y) > abs(new_offset.y)):
        offset = new_offset
        return true
    else:
        return false
    
        
func reset_limits() -> void:
    limit_left = original_limit_left
    limit_right = original_limit_right
    limit_bottom = original_limit_bottom
    limit_top = original_limit_top

func _death_cam(animation_number: int = 0) -> void:
    if(animation_number < 1):
        $CameraAnimationPlayer.play("deathCamJustZoom")
    if(animation_number == 1):
        $CameraAnimationPlayer.play("deathCamLateRotation")

func _update_lighting_shader() -> void:
    # Props to gameendaevour
    # TODO get this into a central world update management system
    var lights = get_tree().get_nodes_in_group("light")
    image.lock()
    for i in lights.size():
        var light = lights[i]
        # TODO To make the lighting affect all layers properly
        # I would have the access the global positions of nodes in different Z layers
        # without the projection to the global center layer.

        # var vtrans = get_canvas_transform()
        # var top_left = -vtrans.origin / vtrans.get_scale()
        # var vsize = get_viewport_rect().size
        # var t = Transform2D(0, (top_left + 0.5*vsize/vtrans.get_scale()).rotated(rotation))
        image.set_pixel(i, 0, Color(
            light.position.x, light.position.y,
            light.strength, light.radius
        ))
        image.set_pixel(i, 1, light.color)
    image.unlock()

    texture.create_from_image(image)

    material.set_shader_param("n_lights", lights.size())
    material.set_shader_param("light_data", texture)
    material.set_shader_param("global_transform", get_global_transform())
    material.set_shader_param("viewport_transform", get_viewport_transform())
