DEBUG_MODE = false

-- semi dependencies
if not FSM_LUA     then dofile("fsm.lua")     end
if not GLOBALS_LUA then dofile("globals.lua") end
if not SCREEN_LUA  then dofile("screen.lua")  end
if not SCENE_LUA   then dofile("scene.lua")   end
if not SCORES_LUA  then dofile("scores.lua")  end
if not GAMEFSM_LUA then dofile("gamefsm.lua") end
if not MOBILE_LUA  then dofile("mobile.lua")  end
if not TRACK_LUA   then dofile("track.lua")   end
if not AI_LUA      then dofile("ai.lua")      end
----------------------------------------------------------------------------
-- ui
----------------------------------------------------------------------------

ui = {}

function ui:create()
    -- bike UI 
    self.key_turn    = 0
    self.key_lean    = 0
    self.joy_turn    = 0
    self.joy_lean_x  = 0
    self.joy_lean_z  = 0
    self.joy_lean_vx = 0
    self.joy_lean_vz = 0
    self.accel_lo    = false
    self.accel_hi    = false
    self.boost       = false
    self.reverse     = false

    jx, jy           = 0, 0

    -- debug UI
    self.pan_z      = false
end

function ui:do_timer( dt )
    local jx,  jy  = E.get_joystick( 0 )
    local djx, djy = jx - self.jx, jy - self.jy
    local jvx, jvy = djx / dt, djy / dt

    self.jz, self.jx = jx,  jy
    self.vz, self.vx = jvx, jvy   
end

----------------------------------------------------------------------------
-- do_? callback functions
----------------------------------------------------------------------------

function do_keyboard(k, s)

    if (gamefsm.state == STATE_GAME_MENU) or
       (gamefsm.state == STATE_GAME_FINISH) then 
	if s then
	    if k == E.key_right then
		local nextoption = screen.cur_option + 1
		if nextoption > screen.cur_menu.num_options then
		    nextoption = 1
		end
		screen:set_menu_option(nextoption)
	    elseif k == E.key_left then
		local nextoption = screen.cur_option - 1
		if nextoption < 1 then
		    nextoption = screen.cur_menu.num_options
		end
		screen:set_menu_option(nextoption)
	    end
	    if k == E.key_return or 
	       k == E.key_space or
	       k == E.key_lalt or
	       k == E.key_ralt then
		screen:exec_menu()
	    end
	end
    elseif (gamefsm.state == STATE_GAME_ADDINIT) then 
	if s then
	    if k == E.key_right then
		gamefsm.current_char = next_letter(gamefsm.current_char)
		set_init_sprite(screen.newinits, 
				gamefsm.current_char, 
				gamefsm.current_init)
		E.play_sound(sounds.menu_click)
	    elseif k == E.key_left then
		gamefsm.current_char = prev_letter(gamefsm.current_char)
		set_init_sprite(screen.newinits, 
				gamefsm.current_char, 
				gamefsm.current_init)
		E.play_sound(sounds.menu_click)

	    end
	    if k == E.key_return or 
	       k == E.key_space or
	       k == E.key_lalt or
	       k == E.key_ralt then
		gamefsm:advance_init()
	    end
	end

    else
	if s then
	    if k == E.key_space then ui.accel_lo  = true              end
	    if k == E.key_lalt  then ui.accel_hi  = true              end
	    if k == E.key_ralt  then ui.accel_hi  = true              end
	    if k == E.key_up    then ui.key_turn  = ui.key_turn  + 1  end
	    if k == E.key_down  then ui.key_turn  = ui.key_turn  - 1  end
	    if k == E.key_left  then ui.key_lean  = ui.key_lean  - 1  end
	    if k == E.key_right then ui.key_lean  = ui.key_lean  + 1  end
	    if k == E.key_return then
		E.set_entity_position(scene.stage, 0, 0, 0)
	    end
	    
	    
	else
	    if k == E.key_space then ui.accel_lo  = false end
	    if k == E.key_lalt  then ui.accel_hi  = false end
	    if k == E.key_ralt  then ui.accel_hi  = false end
	    if k == E.key_tab   then ui.boost     = false end
	    if k == E.key_up    then ui.key_turn  = ui.key_turn  - 1  end
	    if k == E.key_down  then ui.key_turn  = ui.key_turn  + 1  end
	    if k == E.key_left  then ui.key_lean  = ui.key_lean  + 1  end
	    if k == E.key_right then ui.key_lean  = ui.key_lean  - 1  end
	end
    end
    return false
end

function do_joystick(n, b, s)
    if (gamefsm.state == STATE_GAME_MENU or gamefsm.state == STATE_GAME_FINISH)
	and s then
	screen:exec_menu()
    elseif gamefsm.state == STATE_GAME_ADDINIT and s then
	gamefsm:advance_init()
    elseif b == 0 then ui.reverse  = s
    elseif b == 1 then ui.accel_lo = s
    elseif b == 2 then ui.accel_hi = s
    elseif b == 5 then mob[1].breaking = s

    elseif b == 6 and s then
	set_time_sprite( screen.laptime, gamefsm.state_time)
	gamefsm:flash("lap", 3)
    
    elseif b==8 and gamefsm.state == STATE_GAME_RACING and s then
	if gamefsm.race_music then
	    E.stop_sound(sounds.race_music)
	    gamefsm.race_music = nil
	else
	    E.loop_sound(sounds.race_music)
	    gamefsm.race_music = true
	end
    elseif b==9 and gamefsm.state == STATE_GAME_RACING and s then
	
	screen:hide_hud(true)
	E.play_sound(sounds.lap)
	do_reset_world()
	gamefsm:set_state(STATE_GAME_MENU)
	screen:show_menu("title", true, 5)
    end

end

tt = 0
function do_timer(dt)

    tt = tt + dt
--    if wait_for_frame then return end

--    E.print_console("gamefsm.state: "..gamefsm.state.."\n")

    screen:do_idle(dt)
    gamefsm:do_idle(dt)
    track:rotate_pwrups(dt)

    if (gamefsm.state == STATE_GAME_LOAD)
    then 
	if gamefsm.state_time > gamefsm.loadtime and screen.loadscreen then
	    screen:destroy_loadscreen()
	    screen:show_menu("title", true, 5)
	    gamefsm:set_state(STATE_GAME_MENU)
	end
	
	--return true 
    end

--    screen:do_idle(dt)
--    gamefsm:do_idle(dt)

    if gamefsm.state == STATE_GAME_RACING then
	if mob[chasetarg].state == STATE_MOBILE_PRERACE then
	    table.foreachi( mob, function ( i, m ) 
				     m:set_state(STATE_MOBILE_RACING) 
				 end )
	end
	
    end

    -- get controls from player 
    if mob[1]:in_control() then
	local jx = 0
	jx, jy = E.get_joystick(0)
	
	if mob[1].on_ground then 
	    mob[1].turn = ui.key_turn - jy
	else
	    --E.print_console(string.format("%5.2f %5.2f\n",jx,jy))
	    mob[1].lean_x = - ui.key_lean - jx
	end
	
	--    mob[1].lean_x = ui.joy_lean_vx
	--    mob[1].lean_z = ui.joy_lean_vz
	
	mob[1]:set_throttle(ui.accel_lo, ui.accel_hi, ui.reverse, ui.boost)
    end

    -- for each mobile
    for i, m in ipairs(mob) do

	if m.ai and m:in_control() then m.ai:do_idle(dt) end

        m:do_idle(dt)

	-- get mob[chasetarg]'s values to use for camera
	local mobpos = -m.pz
	
	if m.state == STATE_MOBILE_RACING or
	   m.state == STATE_MOBILE_RESPAWN then

	    if mobpos > -track.finish then -- mob crossed finish

		m:set_state(STATE_MOBILE_POSTRACE)
		m.throt:set_state(STATE_THROT_IDLE)
		m.turn = 0

		if i == chasetarg then -- this is the player's mob

		    local end_time = gamefsm.state_time

		    screen:hide_hud(true)

		    E.play_sound(sounds.lap)

		    if scores:is_top("default", end_time) then
			-- normally we would switch to the initials picker
			-- here, then from that menu call scores:add_score()
			-- with the initials, like so:
			--
			-- screen:set_initials_data( "default", end_time ) 
			-- screen:show_menu( "initials", true )
			--
			-- instead though, we will just send the score
			-- with bogus initials
			set_time_sprite(screen.highscore, end_time)
			gamefsm:set_state(STATE_GAME_ADDINIT)
			--scores:add_score("AMG", "default", end_time)
			--screen:create_score_sheet()
		    else
			set_time_sprite(screen.fintime, end_time)
			gamefsm:set_state(STATE_GAME_FINISH)
			screen:show_menu("finish", true)
		    end
		end

	    elseif (mobpos > -track.lappoint) and (m.lap == 1) then

		m.lap = 2
		if m.id == chasetarg then
		    set_time_sprite( screen.laptime, gamefsm.state_time)
		    gamefsm:flash("lap", 3)
		end

	    else

		--[[
		local t1 = track:in_bounds(E.get_entity_position(m.tire1))
		local t2 = track:in_bounds(E.get_entity_position(m.base ))
	    
		if not (t1 and t2) then
		    m:set_state(STATE_MOBILE_OOB)
		end
		--]]
				
		-- debug
		--[[
		if i == 1 then
		    if m.on_ground then
			screen:set_string("level", "Racing")
		    else
			screen:set_string("level", "In Air")
		    end
		    --track:set_shadow_ofs(m.px, 0, m.pz, tt)
		end--]]
	    end
		
	elseif m.state == STATE_MOBILE_POSTRACE then
	end

    end

    if (mob[chasetarg].state == STATE_MOBILE_OOC) or
       (mob[chasetarg].state == STATE_MOBILE_OOB) then
        scene:set_state(STATE_SCENE_LOCK)
	-- debug
	--screen:set_string ("level", mob[chasetarg].state)

    end

    -- calculate bike speed and update screen

    local lastpos = scenepos
    scenepos = -scene.camera_pos_z
    scenerate = (scenepos - lastpos)/dt

    if gamefsm.state == STATE_GAME_MENU then
	local theta = gamefsm.state_time * math.pi / 100
	local rab_p = track.finish * (math.sin(theta-math.pi / 2) / 2 + 0.5)
	E.set_entity_position(rabbit, 0, 0, rab_p)
	scene:update_snow(0, 15, 0, dt)
    else
	-- set scenepos, scenerate and update gauges, time

	scene:update_snow(mob[chasetarg].vx, 
			  mob[chasetarg].vy +15, 
			  mob[chasetarg].vz, dt )

	screen:update_speedometer( -mob[chasetarg].vz )

	if mob[chasetarg].throt.state == STATE_THROT_OH then
	    screen:flash_tempgauge( mob[chasetarg].throt.state_time )
	end
	screen:update_tempgauge( mob[chasetarg].throt.heat )

	
    end

--    E.print_console(bike_rate.. "\n")
--[[
    E.set_string_text(screen.strings.speed, 
		      "speed = "   .. math.floor(bike_rate) .. 
		      " height = " .. math.floor(bike_y)    ..
		      " pos = "    .. math.floor(bikepos)   )
--]]

    -- as the bike moves forward, maintain the track beneath it

    local sm = math.mod(scenepos, 32)
    local sd = math.floor(scenepos / 32)
    if (sm > 16) and (track.rot <= sd) then 
        track:rotate()
    elseif (sm < 16) and (track.rot > sd) then
	track:rotate_rev()
    end

    if mob[chasetarg].on_ground then
	scene.camera_shake = true
    else
	scene.camera_shake = false
    end

    scene:do_idle(dt)

    return true
end

function do_frame()
    if wait_for_frame then wait_for_frame = nil end
    if scene.camera then
	local x, y, z = E.get_entity_position(scene.camera)
	E.set_entity_position(scene.sky,   x,   y,    z)
    end
end

function do_contact( entityA, entityB, px, py, pz, nx, ny, nz, d )
    
    local catA = E.get_entity_geom_attr(entityA, E.geom_attr_category)
    local catB = E.get_entity_geom_attr(entityB, E.geom_attr_category)
    
    local bike_part, object, obj_cat = 0, 0, 0

    if check_flag(category_bike, catA) then 
	bike_part, object, obj_cat = entityA, entityB, catB
    elseif check_flag(category_bike, catB) then
	bike_part, object, obj_cat = entityB, entityA, catA
    end

    for i, m in ipairs(mob) do
	local owned = m:is_owner(bike_part)
	if owned then
	    if     obj_cat == category_srfc  then
		type = track:get_nearest_surf_type ( px, py, pz )
	    elseif obj_cat == category_pwrup then
		type = track:get_nearest_pwrup_type( px, py, pz )
	    else
		type = "N/A"
	    end
	    m:do_contact( owned, obj_cat, type, px, py, pz, nx, ny, nz, d )
	end
    end
end

function do_create_world()

    screen:create() 
    scene :create()
    track :create()
    ui    :create()

    screen:hide_hud( true )

    E.parent_entity(track.pivot,   scene.pivot)
    
    mob[1]    = mobile:instantiate{ id = 1 }
    mob[2]    = mobile:instantiate{ id = 2 } --, cor_fac_rz = 0.4 }
    mob[3]    = mobile:instantiate{ id = 3 }
    mob[4]    = mobile:instantiate{ id = 4 }
    --[[
    mob[5]    = mobile:instantiate{ id = 5 }
    mob[6]    = mobile:instantiate{ id = 6 }
    mob[7]    = mobile:instantiate{ id = 7 }
    mob[8]    = mobile:instantiate{ id = 8 }
    --]]
    mob[2].ai = ai:instantiate{ mobile = mob[2], 
	state = STATE_AI_FOLLOWWP, 
	pers = "SHY" }
    mob[3].ai = ai:instantiate{ 
	mobile = mob[3], 
	state = STATE_AI_FOLLOWWP, 
	pers = "ANGRY" }
    mob[4].ai = ai:instantiate{ 
	mobile = mob[4], 
	state = STATE_AI_FOLLOWWP, 
	pers = "MAD" }
    --[[
    mob[5].ai = ai:instantiate{ 
	mobile = mob[5], 
	state = STATE_AI_FOLLOWWP, 
	pers = "SHY" }
    mob[6].ai = ai:instantiate{ 
	mobile = mob[6], 
	state = STATE_AI_FOLLOWWP, 
	pers = "ANGRY" }
    mob[7].ai = ai:instantiate{ 
	mobile = mob[7], 
	state = STATE_AI_FOLLOWWP, 
	pers = "SHY" }
    mob[8].ai = ai:instantiate{ 
	mobile = mob[8], 
	state = STATE_AI_FOLLOWWP, 
	pers = "MAD" }
    --]]
    table.foreachi( mob, function (i, v) 
			     v:create( 0, 5, 500 + i*5, 0, track.physpivot )
			     v:set_state(STATE_MOBILE_PRERACE)
			 end)


    rabbit = E.create_pivot()
    E.parent_entity(rabbit, track.physpivot)
    scene.chase = rabbit
    scene.camera_smooth_factor = 50
    scene:set_state(STATE_SCENE_MENU)

    scenepos = 0

    E.set_sound_receiver(scene.mic, 32)

    gamefsm:set_state(STATE_GAME_LOAD)

end

function put_bikes( z )
    if not z then
	mob[1]:reset ( 0,   5, 0, 0, 1 ) 
	mob[2]:reset ( -11, 5, 0, 0, 1 )
	mob[3]:reset ( 11,  5, 0, 0, 1 )
	mob[4]:reset ( 0,   5, 7, 0, 1 )
	--[[
	mob[5]:reset ( -11,   5, 7, 0, 1 )
	mob[6]:reset ( 11,   5, 7, 0, 1 )
	mob[7]:reset ( -11,   5, 14, 0, 1 )
	mob[8]:reset ( 11,   5, 14, 0, 1 )
	--]]
    else
	table.foreachi( mob, function (i, v) v:reset( 0, 5, z + i*5, 0) end)
    end
end



function do_reset_world()
    track:reset()

    put_bikes(500)
    
    table.foreachi( mob, function (i, v) v:set_state(STATE_MOBILE_PRERACE) end)

    scene.chase = rabbit
    scene.camera_smooth_factor = 50
    scene:set_state(STATE_SCENE_MENU)

    scenepos = 0

end


function do_start()
    math.randomseed(os.time())
    scores:create()
    do_create_world()
    E.enable_timer(true)
end


do_start()

