:NAME camera_control

:VAR float32					zoomed_in_fovy
:VAR float32					aspect_ratio
:VAR s_bounding_box2d			box
:VAR c_object*					zoom_object
:VAR boolean					zoomed_out
:VAR s_vector2d					initial_camera_position
:VAR s_bounding_box2d			path_bounds
:VAR s_bounding_box2d			camera_inner_bounds
:VAR s_camera					tracking_camera
:VAR boolean					initial_transition
:VAR s_vector2d					scripted_focus_point
:VAR boolean					scripted_focus_valid
:VAR s_bounding_box2d			scripted_bounds
:VAR boolean					scripted_bounds_valid



:PARAM float32					unused
:PARAM s_function1d				initial_movement_func

-- ---------------------- constants ----------------------

k_zoomed_out_fovy= 112
k_path_margin= 3
k_dead_zone_factor= 0.5
k_safe_zone_factor= 1 - k_dead_zone_factor

k_cinematic_build= false

local vectorlib = require "scripts/vector_math"


-- ---------------------- initialization ----------------------

function var_initialize(v)
	v.zoomed_in_fovy= k_zoomed_out_fovy
	v.box.minx= 1
	v.box.maxx= -1
	v.box.miny= 1
	v.box.maxy= -1

	v.zoomed_out= false
	v.initial_camera_position.x= 0
	v.initial_camera_position.y= 0
	v.initial_transition= false
	
	v.scripted_focus_valid= false
	v.scripted_bounds_valid= false
	
end

function param_initialize(p)
	p.dragging_fovy= k_zoomed_out_fovy
end

-- ---------------------- camera zoom ----------------------

function initialize_springs(camera, object)

	local spring2d= object.spring2d_get("position")
	if (spring2d) then
	
		local offset= camera.transform.offset
		local state= spring2d.state
		state.value.x= offset.x
		state.value.y= offset.y
		state.velocity.x= 0
		state.velocity.y= 0
	end	
	
	local spring= object.spring_get("fovy")
	if (spring) then
		local state= spring.state
		state.value= camera.fovy
		state.velocity= 0
	end

end

function dynamic_camera_initialize(object)
	local resolution_mode= resolution_mode_get()
	local globals= globals_get()
	local resolution_settings= globals.resolution_settings[resolution_mode]
	local zoom= resolution_settings.zoom_scale
	local result= false

	if (zoom > 1.01 or k_cinematic_build) then
		local ws= world_state_get()
		local camera= ws.camera[0]
		local y_length= camera.y_bounds(0)		-- how far does the camera see at depth 0?
		local target_y_length= y_length/zoom
		local camera_pos= camera.transform.offset
		
		var.zoomed_in_fovy= camera.compute_fovy(0, target_y_length)
		
		-- this is typically ~73 degrees
		--print ("zoomed in fovy: ".. var.zoomed_in_fovy)

		-- cache the aspect ratio	
		var.aspect_ratio= camera.aspect_ratio_get()
		
		-- NOTE: we use a slightly-wider than 4:3 aspect ratio to bound the camera.
		-- This allows a thin black bar to appear on the left and right edges of the screen
		-- when in zoomed-in mode. This in turn allows us to move the left-hand icons into that
		-- black space when the player is hugging the edges of the map
		
		if (true) then
			local x_length= y_length*1140/768
			local inner_box= var.box
			inner_box.miny= camera_pos.y - y_length
			inner_box.maxy= camera_pos.y + y_length
			inner_box.minx= camera_pos.x - x_length
			inner_box.maxx= camera_pos.x + x_length
		end
		
		if (not k_cinematic_build) then
			var.zoom_object= object_place("zoom")
		end
		
		-- place bars
		
		local ipad_aspect_ratio= 1024.0/768.0
		local ipad_x_length= y_length*ipad_aspect_ratio

		local w= world_get()
		local bar_placement= w.placement_get("bar")
		if (bar_placement) then
			-- place bars
			local b1= ws.place(bar_placement)
			local pos= b1.world_position_get()
			pos.x= camera_pos.x + ipad_x_length
			b1.position_set(pos.x, pos.y)
			
			local b2= ws.place(bar_placement)
			pos.x= camera_pos.x - ipad_x_length
			b2.position_set(pos.x, pos.y)
			b2.scale_set(-1, 1)
		end
		
		-- initialize springs
		initialize_springs(camera, object)

		var.zoomed_out= true
		var.initial_transition= param.initial_movement_func.valid()
		
		-- initialize the tracking camera
		
		local tracking_camera= var.tracking_camera
		tracking_camera.mode= camera.mode
		tracking_camera.transform.offset.x= camera.transform.offset.x
		tracking_camera.transform.offset.y= camera.transform.offset.y
		tracking_camera.fovy= camera.fovy
		tracking_camera.z= camera.z
		
		tracking_update(var.tracking_camera, object, true)

		result= true
	end
	
	return result
end


function tracking_update(camera, object, initial)

	local ws= world_state_get()
	local path_bounds= var.path_bounds

	local endpoint_vector= {}
	endpoint_vector.x= 0
	endpoint_vector.y= 0
	
	path_bounds.clear()

	-- NOTE: HACK: in the below, we use ws.camera[0], NOT the camera passed in. This is because this 
	-- below needs to refer to what is ACTUALLY being displayed on the screen, and not what the theoretical
	-- "tracking camera" is doing
	-- this creates a 3:2 "safe zone" in the center of the screen

	local actual_camera= ws.camera[0]
	local actual_camera_pos= actual_camera.transform.offset
	local actual_y= actual_camera.y_bounds(0)
	local safe_y= k_safe_zone_factor*actual_y
	local safe_x= safe_y*960/640 --1024/768
	
	local dead_zone_distance= actual_y - safe_y
	local dead_zone_distance_x = actual_y*var.aspect_ratio - safe_x
	
	local camera_inner_bounds= var.camera_inner_bounds
	camera_inner_bounds.minx= actual_camera_pos.x - safe_x
	camera_inner_bounds.maxx= actual_camera_pos.x + safe_x
	camera_inner_bounds.miny= actual_camera_pos.y - safe_y
	camera_inner_bounds.maxy= actual_camera_pos.y + safe_y
	

	-- --------------- iterate through players ---------------
	-- we are going to collect bounds on all their paths and then also accelerate the camera in the
	-- direction of end points if they are outside the dead zone in the middle of the screen

	local player_iterator= ws.component_cache.component_player.iterator_get()
	while (true) do
		local player_comp= player_iterator.next()
		if (player_comp) then
			local player_object= player_comp.object
			if (not player_object.is_hidden()) then
				local player_control= player_object._c.player_control
				if (not player_control.disabled) then

					local path= player_object.path_get()
					local bounds= path.path_bounds()
					path_bounds.minx= math.min(path_bounds.minx, bounds.minx)
					path_bounds.maxx= math.max(path_bounds.maxx, bounds.maxx)
					path_bounds.miny= math.min(path_bounds.miny, bounds.miny)
					path_bounds.maxy= math.max(path_bounds.maxy, bounds.maxy)

					local last_point= nil
					if (path.path_active()) then
						local last_step= path.path.steps[path.path.step_count-1]
						last_point= last_step.location.point
					else
						last_point= player_object.world_position_get()
					end

					-- add endpoint force

					if (last_point.x > camera_inner_bounds.maxx) then
						local violation_distance= -math.min(last_point.x - camera_inner_bounds.maxx, dead_zone_distance_x)/dead_zone_distance_x
						endpoint_vector.x = endpoint_vector.x + violation_distance
					elseif (last_point.x < camera_inner_bounds.minx) then
						local violation_distance= math.min(camera_inner_bounds.minx - last_point.x, dead_zone_distance_x)/dead_zone_distance_x
						endpoint_vector.x = endpoint_vector.x + violation_distance
					end

					if (last_point.y > camera_inner_bounds.maxy) then
						local violation_distance= -math.min(last_point.y - camera_inner_bounds.maxy, dead_zone_distance)/dead_zone_distance
						endpoint_vector.y = endpoint_vector.y + violation_distance
					elseif (last_point.y < camera_inner_bounds.miny) then
						local violation_distance= math.min(camera_inner_bounds.miny - last_point.y, dead_zone_distance)/dead_zone_distance
						endpoint_vector.y = endpoint_vector.y + violation_distance
					end
				end

			end
		else
			break
		end
	end
	
	-- also include scripted point, if active
	if (var.scripted_focus_valid) then

		local p= var.scripted_focus_point
		if (p.x > camera_inner_bounds.maxx) then
			local violation_distance= -math.min(p.x - camera_inner_bounds.maxx, dead_zone_distance_x)/dead_zone_distance_x
			endpoint_vector.x = endpoint_vector.x + violation_distance
		elseif (p.x < camera_inner_bounds.minx) then
			local violation_distance= math.min(camera_inner_bounds.minx - p.x, dead_zone_distance_x)/dead_zone_distance_x
			endpoint_vector.x = endpoint_vector.x + violation_distance
		end

		if (p.y > camera_inner_bounds.maxy) then
			local violation_distance= -math.min(p.y - camera_inner_bounds.maxy, dead_zone_distance)/dead_zone_distance
			endpoint_vector.y = endpoint_vector.y + violation_distance
		elseif (p.y < camera_inner_bounds.miny) then
			local violation_distance= math.min(camera_inner_bounds.miny - p.y, dead_zone_distance)/dead_zone_distance
			endpoint_vector.y = endpoint_vector.y + violation_distance
		end
	
	end
	
	-- also include scripted bounds, if valid
	
	if (var.scripted_bounds_valid) then
		path_bounds.minx = math.min(path_bounds.minx, var.scripted_bounds.minx)
		path_bounds.maxx = math.max(path_bounds.maxx, var.scripted_bounds.maxx)
		path_bounds.miny = math.min(path_bounds.miny, var.scripted_bounds.miny)
		path_bounds.maxy = math.max(path_bounds.maxy, var.scripted_bounds.maxy)
	end

	-- ---------------- update the fovy system AND the actual camera fovy ----------------
	
	local y_len= k_path_margin + (path_bounds.maxy - path_bounds.miny)/2
	local x_len= k_path_margin + (path_bounds.maxx - path_bounds.minx)/2
	
	x_len= x_len / var.aspect_ratio

	local max_len= math.max(y_len, x_len)
	local path_fovy= camera.compute_fovy(0, max_len)
	local target_fovy= math.max(path_fovy, var.zoomed_in_fovy)

	target_fovy= math.min(target_fovy, k_zoomed_out_fovy)

	local spring= object.spring_get("fovy")
	if (spring) then
		spring.state.target= target_fovy
		
		if (initial) then
			spring.snap_to_target()
		else
			-- tick it!
			spring.tick()
		end
		
		camera.fovy= spring.state.value
	end
	
	-- ---------------- update spring2d target and bounds ----------------

	local spring2d= object.spring2d_get("position")
	if (spring2d) then

		-- ---------------- accelerate if somebody's endpoint is in the danger margin around the edge of the screen ----------------
		
		local k_endpoint_scale= -200 ---100
		spring2d.state.accelerate(k_endpoint_scale*endpoint_vector.x, k_endpoint_scale*endpoint_vector.y)

		-- --------------- set target ---------------
		
		local target= spring2d.state.target
		target.x = (path_bounds.minx+path_bounds.maxx)/2
		target.y = (path_bounds.miny+path_bounds.maxy)/2

		-- --------------- set bounds ---------------
		
		local y_length= camera.y_bounds(0)		-- note, this might be an updated value based on the new fovy
		local x_length= y_length*var.aspect_ratio
		local spring_bounds= spring2d.spring.bounds
		local global_box= var.box
		
		spring_bounds.minx= global_box.minx + x_length
		spring_bounds.maxx= global_box.maxx - x_length
		spring_bounds.miny= global_box.miny + y_length
		spring_bounds.maxy= global_box.maxy - y_length
		
		if (spring_bounds.minx > spring_bounds.maxx) then
			spring_bounds.minx= var.initial_camera_position.x
			spring_bounds.maxx= var.initial_camera_position.x
		end
		
		if (spring_bounds.miny > spring_bounds.maxy) then
			spring_bounds.miny= var.initial_camera_position.y
			spring_bounds.maxy= var.initial_camera_position.y
		end
		
		-- --------------- tick system and extract new position ---------------

		local value= spring2d.state.value		

		if (initial) then
			spring2d.snap_to_target();
		else
			spring2d.tick()
		end

		local camera_offset= camera.transform.offset
		
		camera_offset.x= value.x
		camera_offset.y= value.y
		
	end
end

function blend_cameras(camera, tracking_camera, t)

	local tracking_pos= tracking_camera.transform.offset
	local v= vectorlib.vector_from_points(tracking_pos, var.initial_camera_position)
	
	v.x= v.x*t
	v.y= v.y*t
	
	local pos= camera.transform.offset
	pos.x= tracking_pos.x + v.x
	pos.y= tracking_pos.y + v.y
	
	local dfovy= k_zoomed_out_fovy - tracking_camera.fovy
	dfovy = dfovy*t
	
	camera.fovy= tracking_camera.fovy + dfovy

end

function camera_update(object)
	local ws= world_state_get()
	local camera= ws.camera[0]
	local time= ws.master_time.time

	-- see if we should no longer be zoomed out
	if (var.zoom_object) then
		local input= var.zoom_object.input_get()
		if (input and input.touch_started()) then
			-- toggle zoomed out
			local spring= object.spring_get("transition")
			if (spring) then
				if (var.initial_transition) then
					if (spring.state.value > 0.5) then
						var.zoomed_out= false
					else
						var.zoomed_out= true
					end
					var.initial_transition= false
				else
					var.zoomed_out = not var.zoomed_out
				end
			
			
				if (var.zoomed_out) then
					spring.state.target= 1
					var.zoom_object.tween_animation_play("press_hold")
				else
					spring.state.target= 0
					var.zoom_object.tween_animation_play("pressed")
				end
			end
		end
	end
	
	-- --------------- update the transition slider ---------------
	
	local spring= object.spring_get("transition")
	local t= 0
	if (spring) then
		if (var.initial_transition) then
			local func= param.initial_movement_func
			spring.state.target= func.evaluate(time)
			if (time > func.input_range_max) then
				var.initial_transition= false
				var.zoomed_out= false
				spring.state.target= 0
			end
		end
	
		spring.tick()
		t= spring.state.value
	end
	
	-- --------------- update the tracking camera ---------------
	-- note we do this every tick no matter what, even if we are zoomed out

	tracking_update(var.tracking_camera, object, false)
	
	-- --------------- blend between the tracking camera and the defaul camera ---------------
	
	blend_cameras(camera, var.tracking_camera, t)

end

-- ---------------------- system ----------------------


function attach(object)
	local w= world_get()
	local ws= world_state_get()
	local camera= ws.camera[0]

	if (not w.initial_camera_state_valid()) then
		if (ws) then
			-- set global initial state
			camera.transform.offset.x= 0
			camera.transform.offset.y= 0
			camera.z= 8.6
			camera.fovy= 112
		end
	end
	
	var.initial_camera_position.x= camera.transform.offset.x
	var.initial_camera_position.y= camera.transform.offset.y
	
	if (not dynamic_camera_initialize(object)) then
		sleep()
	end

end

local bounds_color= {}
bounds_color.red= 0
bounds_color.green= 1
bounds_color.blue= 0

local camera_target_color= {}
camera_target_color.red= 1
camera_target_color.green= 0
camera_target_color.blue= 0

local camera_position_color= {}
camera_position_color.red= 0
camera_position_color.green= 0
camera_position_color.blue= 1

local safe_bounds_color= {}
safe_bounds_color.red= 1
safe_bounds_color.green= 0
safe_bounds_color.blue= 0



function debug(object)

	local ws= world_state_get()
	local camera= ws.camera[0]
	local player= ws.primary_player_get()
	local path_bounds= var.path_bounds

	if (path_bounds.valid()) then
	
		local top_left= {}
		top_left.x= path_bounds.minx
		top_left.y= path_bounds.maxy
		
		local bottom_right= {}
		bottom_right.x= path_bounds.maxx
		bottom_right.y= path_bounds.miny

		render_rectangle(player.plate, top_left, bottom_right, bounds_color, false)

		top_left.x= path_bounds.minx - k_path_margin
		top_left.y= path_bounds.maxy + k_path_margin
		bottom_right.x= path_bounds.maxx + k_path_margin
		bottom_right.y= path_bounds.miny - k_path_margin
		
		render_rectangle(player.plate, top_left, bottom_right, bounds_color, false)

	end
	
	if (true) then
		local camera_inner_bounds= var.camera_inner_bounds

		--[[
		local y_length= camera.y_bounds(0)		-- how far does the camera see at depth 0?
		local x_length= y_length*var.aspect_ratio
		local dead_zone_distance= y_length*k_dead_zone_factor
		local camera_position= camera.transform.offset
		
		y_length = y_length - dead_zone_distance
		x_length = x_length - dead_zone_distance
		
		local top_left= {}
		top_left.x= camera_position.x - x_length
		top_left.y= camera_position.y + y_length
		
		local bottom_right= {}
		bottom_right.x= camera_position.x + x_length
		bottom_right.y= camera_position.y - y_length
		--]]
		
		local top_left= {}
		top_left.x= camera_inner_bounds.minx
		top_left.y= camera_inner_bounds.maxy
		
		local bottom_right= {}
		bottom_right.x= camera_inner_bounds.maxx
		bottom_right.y= camera_inner_bounds.miny
	
		render_rectangle(player.plate, top_left, bottom_right, safe_bounds_color, false)

	end

	local spring2d= object.spring2d_get("position")
	if (spring2d) then
		render_circle(player.plate, spring2d.state.last_target, 0.5, camera_target_color, true)
	end

	render_circle(player.plate, var.tracking_camera.transform.offset, 0.5, camera_position_color, true)
	
end

function update(object)
	local ws= world_state_get()
	camera_update(object)
	
	--debug(object)
end


component_register(attach, update, nil, nil)
