-- $LastChangedDate: 2008-12-24 10:42:02 -0500 (Wed, 24 Dec 2008) $
-- Keyboard handler.
-- See module.txt for documentation and pitfalls.
-- Notes: Use Vec3() instead of tables (XX=0).

-- Keyboard singleton class.
Keyboard = {
	-- OSG keyboard codes:
	-- Use string.byte("c") for normal ASCII chars.
	KEY_Escape       = 0xFF1B,
	KEY_Pause        = 0xFF13,
	KEY_BackSpace    = 0xFF08,
	KEY_Tab          = 0xFF09,
	KEY_Return       = 0xFF0D,
	KEY_Left         = 0xFF51,
	KEY_Up           = 0xFF52,
	KEY_Right        = 0xFF53,
	KEY_Down         = 0xFF54,
	KEY_Begin        = 0xFF58,
	KEY_KP_Left      = 0xFF96,
	KEY_KP_Up        = 0xFF97,
	KEY_KP_Right     = 0xFF98,
	KEY_KP_Down      = 0xFF99,
	KEY_KP_Insert    = 0xFF9E,
	KEY_KP_Delete    = 0xFF9F,
	KEY_KP_Add       = 0xFFAB,
	KEY_KP_Subtract  = 0xFFAD,
	KEY_KP_Begin     = 0xFF9D,
	KEY_KP_End       = 0xFF9C,
	KEY_KP_Page_Up   = 0xFF9A,
	KEY_KP_Page_Down = 0xFF9B,
	KEY_F1           = 0xFFBE,
	KEY_F2           = 0xFFBF,
	KEY_F3           = 0xFFC0,
	KEY_F4           = 0xFFC1,
	KEY_F5           = 0xFFC2,
	KEY_F6           = 0xFFC3,
	KEY_F7           = 0xFFC4,
	KEY_F8           = 0xFFC5,
	KEY_F9           = 0xFFC6,
	KEY_F10          = 0xFFC7,
	KEY_F11          = 0xFFC8,
	KEY_F12          = 0xFFC9,
}

-- Keyboard input that cannot be ignored.
Keyboard.importantKeys = {
	[Keyboard.KEY_BackSpace] = true,
	[string.byte("c")] = true,
	[Keyboard.KEY_F1] = true,
}
-- Keyboard state/args used by handler.
Keyboard.state = {
	timeBetween = sim.Program:GetTickFreq(),
	rotationFraction = 1.0,
	stepMode = sim.argvMap["-step"],  -- true if -step passed
	step = sim.World:Meters2Sim( 25.0 ),
}

-- Returns radian/second.
function Keyboard:ChaseViewRotation()
	return Deg2Rad( 45.0 / (1000.0 / Keyboard.state.timeBetween) )
end

-- Keyboard handler table.
Keyboard.handlerTable = {

	-- Exit program.
	-- FLTK and OSG conflict with ESC key.
	-- Pressing ESC while GUI has the focus would stop the whole program.
    [string.byte("c")] =
	function( keyEvent )
		if ( keyEvent.ctrl ) then  -- Ctrl+C
			sim.Program:Exit( 0 )
		end
	end,

	-- Pause program.
	[Keyboard.KEY_Pause] =
	function( keyEvent )
		sim.Program:SetPause( not sim.Program:IfPaused() )
	end,
    [string.byte("p")] =
	function( keyEvent )
		if ( keyEvent.ctrl ) then sim.Program:SetPause( not sim.Program:IfPaused() ) end
	end,

	-- Cycle view.
	[Keyboard.KEY_Tab] =
	function( keyEvent )
		-- Switch view (OTW or chase).
		sim.SuperView:CycleViewMode()
	end,

    [string.byte("`")] =
	function( keyEvent )
		sim.SuperView:CycleViewControl()
	end,

    -- Player: turn left
	[Keyboard.KEY_Left] =
	function( keyEvent )
		GetPlayerAircraft():PhysicalRotate( AXIS_ROLL, sim.Control.rollLeftDir * Keyboard.state.rotationFraction, Keyboard.state.timeBetween )
	end,

    -- Player: turn right
	[Keyboard.KEY_Right] =
	function( keyEvent )
		GetPlayerAircraft():PhysicalRotate( AXIS_ROLL, sim.Control.rollRightDir * Keyboard.state.rotationFraction, Keyboard.state.timeBetween )
	end,

    -- Player: pitch up.
	[Keyboard.KEY_Down] =
	function( keyEvent )
		GetPlayerAircraft():PhysicalRotate( AXIS_PITCH, sim.Control.pitchUpDir * Keyboard.state.rotationFraction, Keyboard.state.timeBetween )
	end,

    -- Player: pitch down.
	[Keyboard.KEY_Up] =
	function( keyEvent )
		GetPlayerAircraft():PhysicalRotate( AXIS_PITCH, sim.Control.pitchDownDir * Keyboard.state.rotationFraction, Keyboard.state.timeBetween )
	end,

    -- Player: yaw left.
	[Keyboard.KEY_KP_Insert] =
	function( keyEvent )
		GetPlayerAircraft():PhysicalRotate( AXIS_YAW, sim.Control.yawLeftDir * Keyboard.state.rotationFraction, Keyboard.state.timeBetween )
	end,

    -- Player: yaw right.
	[Keyboard.KEY_KP_Delete] =
	function( keyEvent )
		GetPlayerAircraft():PhysicalRotate( AXIS_YAW, sim.Control.yawRightDir * Keyboard.state.rotationFraction, Keyboard.state.timeBetween )
	end,

    -- Throttle 0..9:
    [string.byte("0")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 0.0 )
	end,

    [string.byte("1")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 1.0 / 9.0 )  -- 9 is 100%
	end,

    [string.byte("2")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 2.0 / 9.0 )
	end,

    [string.byte("3")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 3.0 / 9.0 )
	end,

    [string.byte("4")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 4.0 / 9.0 )
	end,

    [string.byte("5")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 5.0 / 9.0 )
	end,

    [string.byte("6")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 6.0 / 9.0 )
	end,

    [string.byte("7")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 7.0 / 9.0 )
	end,

    [string.byte("8")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 8.0 / 9.0 )
	end,

    [string.byte("9")] =
	function( keyEvent )
		GetPlayerAircraft():SetThrottle( 1.0 )  -- 100%
	end,

    -- Decrease throttle.
    [string.byte("-")] =
	function( keyEvent )
		local throttle = GetPlayerAircraft():GetThrottle()
		throttle = throttle - 1.0 / 9.0
		if ( throttle < 0.0 ) then throttle = 0.0 end
		GetPlayerAircraft():SetThrottle( throttle )
	end,

    -- Increase throttle.
    [string.byte("=")] =
	function( keyEvent )
		local throttle = GetPlayerAircraft():GetThrottle()
		throttle = throttle + 1.0 / 9.0
		if ( throttle > 1.0 ) then throttle = 1.0 end
		GetPlayerAircraft():SetThrottle( throttle )
	end,

    -- Landing gear.
    [string.byte("g")] =
	function( keyEvent )
		-- ToggleLandingGear() returns true if gear changed state.
		-- Returns false if aircraft is sitting on runway as gear cannot be retracted up.
		if ( GetPlayerAircraft():ToggleLandingGear() ) then
			sim.Sound:Play( "gear.wav.gz", sim.Sound.PLAY_ONCE, sim.Sound.DEFAULT_VOLUME )
		end
	end,

    -- Brakes.
    [string.byte("b")] =
	function( keyEvent )
		GetPlayerAircraft():ToggleBrakes()
	end,

    -- Sweep-wings (NOP if not F-14 Tomcat):
    [string.byte("[")] =
	function( keyEvent )
	    PlayerActor:IncWingSweep( 1, "key" )
	end,

    [string.byte("]")] =
	function( keyEvent )
	    PlayerActor:IncWingSweep( -1, "key" )
	end,

    -- Cycle brightness of light.
    [Keyboard.KEY_F8] =
	function( keyEvent )
		Brightness:Cycle()
	end,

    -- Toggle coordinated-turn (coturn).
    [Keyboard.KEY_F9] =
	function( keyEvent )
		if ( GetPlayerAircraft():IfCoordinatedTurnEnabled() ) then
			GetPlayerAircraft():EnableCoordinatedTurn( false )
			Log( "Disabled coturn." )
		else
			GetPlayerAircraft():EnableCoordinatedTurn( true )
			Log( "Enabled coturn." )
		end
	end,

    -- Show help screen.
    [Keyboard.KEY_F1] =
	function( keyEvent )
	   if ( sim.Gui ) then  -- GUI might not be compiled!
		  sim.Gui:Show()
	   else
		  print( "[Lua] GUI unavailable" )
	   end
	end,

    -- Toggle stalling or step mode.
    [Keyboard.KEY_F10] =
	function( keyEvent )
		if ( keyEvent.shift ) then
			-- Toggle step mode.
			Keyboard.state.stepMode = not Keyboard.state.stepMode
			Log( "step mode " .. tostring(Keyboard.state.stepMode) )
		else
			-- Toggle stalling.
			sim.Physics:EnableStall( not sim.Physics:IfStallEnabled() )
			if ( sim.Physics:IfStallEnabled() ) then
				Log( "Enabled stalling." )
			else
				Log( "Disabled stalling." )
			end
		end
	end,

    -- Reload scene.
    [Keyboard.KEY_F11] =
	function( keyEvent )
		Scene:Reload()
	end,

    -- Save scene graph (Shift + F12).
    [Keyboard.KEY_F12] =
	function( keyEvent )
		if ( keyEvent.shift ) then
			sim.Window:SaveSceneGraphToFile( "palomino_scene.osg" )
		end
	end,

    -- Chase-plane zoom-out.
    [Keyboard.KEY_KP_Add] =
	function( keyEvent )
		ChaseView.chaseViewpoint:Move( sim.World:Meters2Sim( ChaseView.inc ) )
	end,

    -- Chase-plane zoom-in.
    [Keyboard.KEY_KP_Subtract] =
	function( keyEvent )
		ChaseView.chaseViewpoint:Move( sim.World:Meters2Sim( -ChaseView.inc ) )
	end,

    -- Chase-plane rotate left or OTW left.
    [Keyboard.KEY_KP_Left] =
	function( keyEvent )
		if ( Keyboard.state.stepMode ) then
			local pos = GetPlayerAircraft():GetPosition()
			pos[LAT] = pos[LAT] - Keyboard.state.step
			GetPlayerAircraft():SetPosition( pos )
		else
			if ( sim.SuperView.viewControl == 0 and not sim.SuperView:IfChaseView() ) then
				sim.SuperView:SetViewMode( sim.SuperView.eViewMode_OTW_LEFT )
			else
				ChaseView.chaseViewpoint:Rotate( sim.ChaseViewpoint.AXIS_CHASE_PLANE_HORZ,
											-Keyboard:ChaseViewRotation() )
			end
		end
	end,

    -- Chase-plane rotate right or OTW right.
    [Keyboard.KEY_KP_Right] =
	function( keyEvent )
		if ( Keyboard.state.stepMode ) then
			local pos = GetPlayerAircraft():GetPosition()
			pos[LAT] = pos[LAT] + Keyboard.state.step
			GetPlayerAircraft():SetPosition( pos )
		else
			if ( sim.SuperView.viewControl == 0 and not sim.SuperView:IfChaseView() ) then
				sim.SuperView:SetViewMode( sim.SuperView.eViewMode_OTW_RIGHT )
			else
				ChaseView.chaseViewpoint:Rotate( sim.ChaseViewpoint.AXIS_CHASE_PLANE_HORZ,
												Keyboard:ChaseViewRotation() )
			end
		end
	end,

    -- Chase-plane rotate up or OTW front.
    [Keyboard.KEY_KP_Up] =
	function( keyEvent )
		if ( Keyboard.state.stepMode ) then
			local pos = GetPlayerAircraft():GetPosition()
			pos[LON] = pos[LON] + Keyboard.state.step
			GetPlayerAircraft():SetPosition( pos )
		else
			if ( sim.SuperView.viewControl == 0 and not sim.SuperView:IfChaseView() ) then
				sim.SuperView:SetViewMode( sim.SuperView.eViewMode_OTW_FRONT )
			else
				ChaseView.chaseViewpoint:Rotate( sim.ChaseViewpoint.AXIS_CHASE_PLANE_VERT,
												-Keyboard:ChaseViewRotation() )
			end
		end
	end,

    -- Chase-plane rotate down or OTW rear.
    [Keyboard.KEY_KP_Down] =
	function( keyEvent )
		if ( Keyboard.state.stepMode ) then
			local pos = GetPlayerAircraft():GetPosition()
			pos[LON] = pos[LON] - Keyboard.state.step
			GetPlayerAircraft():SetPosition( pos )
		else
			if ( sim.SuperView.viewControl == 0 and not sim.SuperView:IfChaseView() ) then
				sim.SuperView:SetViewMode( sim.SuperView.eViewMode_OTW_REAR )
			else
				ChaseView.chaseViewpoint:Rotate( sim.ChaseViewpoint.AXIS_CHASE_PLANE_VERT,
												Keyboard:ChaseViewRotation() )
			end
		end
	end,

    -- Chase-plane reset or OTW front (too).
    [Keyboard.KEY_KP_Begin] =  -- keypad 5 (center)
	function( keyEvent )
		if ( sim.SuperView.viewControl == 0 and not sim.SuperView:IfChaseView() ) then
			sim.SuperView:SetViewMode( sim.SuperView.eViewMode_OTW_FRONT )
		else
			ChaseView:Reset()
		end
	end,

    -- Step up.
    [Keyboard.KEY_KP_Page_Up] =
	function( keyEvent )
		if ( Keyboard.state.stepMode ) then
			local pos = GetPlayerAircraft():GetPosition()
			pos[ALT] = pos[ALT] + Keyboard.state.step
			GetPlayerAircraft():SetPosition( pos )
		end
	end,

    -- Step down.
    [Keyboard.KEY_KP_Page_Down] =
	function( keyEvent )
		if ( Keyboard.state.stepMode ) then
			local pos = GetPlayerAircraft():GetPosition()
			pos[ALT] = pos[ALT] - Keyboard.state.step
			GetPlayerAircraft():SetPosition( pos )
		end
	end,

    -- Replay (play mode).
    [string.byte("r")] =
	function( keyEvent )
		-- KillFriends() prevents starting replay where a friend is (hack solution).
		if ( Mission.KillFriends ) then Mission:KillFriends() end
		Log( "Reloading replay data." )
		local replay = PlayerActor:GetReplay()
		replay:Load( "replay.dat.gz" )
		replay:Play()
	end,

    -- Replay (start/stop recording).
    [string.byte("R")] =
	function( keyEvent )
		local replay = PlayerActor:GetReplay()
		if ( not replay:IfRecording() ) then
			Log( "Started recording replay.  Press 'R' (capital) again to stop-and-save replay." )
			replay:Record()
		else
			Log( "Stopped recording replay." )
			replay:Stop()
			local filename = "replay.dat.gz"
			if ( replay:Save( filename ) ) then
				Log( "Saved " .. filename )
			else
				Log( "ERROR: failed to save " .. filename )
			end
		end
	end,

    -- Switch targets.
    [string.byte("t")] =
	function( keyEvent )
		PlayerActor:CycleTargets()
	end,

    -- Fire weapon.
    [string.byte(" ")] =
	function( keyEvent )
		PlayerActor:FireWeapon()
	end,

    -- Program reset.
    [Keyboard.KEY_BackSpace] =
	function( keyEvent )
		Mission:Reset()
	end,
}

-- Key is passed as a table or nil:
-- keyEvent = { key=int, lkey=int, ctrl=bool, shift=bool, alt=bool }
function Keyboard:Handler( keyEvent )
	-- Lookup function in handler table by key.  Ignore key release.
	if ( keyEvent.press ) then
		-- If player has crashed, ignore (most) keyboard input.
		if ( not PlayerActor:IfDestroyed() or self.importantKeys[keyEvent.key] ) then
			local handler = Keyboard.handlerTable[keyEvent.key]
			if ( handler ) then  -- could be unrecognized key
				handler( keyEvent )
			end
		end
	end
end
