-- $LastChangedDate: 2010-04-20 17:29:48 -0500 (Tue, 20 Apr 2010) $
-- Keyboard handler.
-- See module.txt for documentation and pitfalls.
-- Notes: Use Vec3() instead of tables (XX=0).
-- Allowing any modifier key (Shift/Ctrl/Alt) is preferred because
-- some windows managers and esp. Mac OS X reserve many key combinations.

-- Load keyboard definitions.
dofile( sim.scriptDir .. "/keyboard_defs.lua" )

--------------
-- Keyboard --
--------------

Keyboard = {
}

-- Keyboard input that cannot be ignored.
Keyboard.importantKeys = {
    [KeyDefs.KEY_BackSpace] = true,
    [string.byte("c")] = true,
    [KeyDefs.KEY_F1] = true,
}

-- Keyboard state/args used by handler.
Keyboard.state = {
    timeBetween = sim.Base:GetTickFreq(),
    rotationFraction = 1.0,
}

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

function Keyboard:ChaseViewRotationHorz()  -- horizontal
    return self:ChaseViewRotation()
end

function Keyboard:ChaseViewRotationVert()  -- vertical
    -- Because of the way chase-view is specially rotated in OSSIM, reverse vert keys.
    if ( sim.argvMap["-ossim"] ) then
        return -self:ChaseViewRotation()
    else
        return self:ChaseViewRotation()
    end
end

--------------------------------------------------------------------------------
-- Keyboard handler table.

Keyboard.handlerTable = {

    ----------------------------------------------------------------------------
    -- Program control:

    -- 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,

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

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

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

    ----------------------------------------------------------------------------
    -- Player aircraft controls:

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

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

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

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

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

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

    -- Player: 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,

    -- Player: 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,

    -- Player: 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,

    -- Player: 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,

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

    -- Player: swing wings (ok if wrong type of aircraft):
    -- 0.0:back...1.0:forward
    [string.byte("[")] =
    function( keyEvent )
        PlayerActor:IncSwingWings( 1, "key" )
    end,

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

    -- Player: toggle coordinated-turn (coturn).
    [KeyDefs.KEY_F9] =
    function( keyEvent )
        if ( keyEvent.modifier ) then
            sim.SceneGraph:ToggleWireframeMode()
        else
            if ( GetPlayerAircraft():IfCoordinatedTurnEnabled() ) then
                GetPlayerAircraft():EnableCoordinatedTurn( false )
                Log( "Disabled coturn." )
            else
                GetPlayerAircraft():EnableCoordinatedTurn( true )
                Log( "Enabled coturn." )
            end
        end
    end,

    -- Player: toggle stalling.
    [KeyDefs.KEY_F10] =
    function( keyEvent )
        -- Toggle stalling.
        sim.Physics:EnableStall( not sim.Physics:IfStallEnabled() )
        if ( sim.Physics:IfStallEnabled() ) then
            Log( "Enabled stalling." )
        else
            Log( "Disabled stalling." )
        end
    end,

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

    -- Player: fire weapon.
    [string.byte(" ")] =
    function( keyEvent )
        if ( modifier ) then
            -- Tell user this function exists but isn't available in normal mode.
            Log( "Save viewpoint function is available in manipulator-mode only." )
        else
            PlayerActor:FireWeapon()
        end
    end,

    ----------------------------------------------------------------------------
    -- Views/display:

    -- Adjust brightness.
    [KeyDefs.KEY_F8] =
    function( keyEvent )
        if ( keyEvent.modifier ) then
            Brightness:Cycle( -1.0 )
        else
            Brightness:Cycle( 1.0 )
        end
    end,

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

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

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

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

    -- Chase-plane rotate left or OTW left.
    [KeyDefs.KEY_KP_Left] =
    function( keyEvent )
        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:ChaseViewRotationHorz() )
        end
    end,

    -- Chase-plane rotate right or OTW right.
    [KeyDefs.KEY_KP_Right] =
    function( keyEvent )
        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:ChaseViewRotationHorz() )
        end
    end,

    -- Chase-plane rotate up or OTW front.
    [KeyDefs.KEY_KP_Up] =
    function( keyEvent )
        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:ChaseViewRotationVert() )
        end
    end,

    -- Chase-plane rotate down or OTW rear.
    [KeyDefs.KEY_KP_Down] =
    function( keyEvent )
        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:ChaseViewRotationVert() )
        end
    end,

    -- Chase-plane reset or OTW front (too).
    [KeyDefs.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,

    ----------------------------------------------------------------------------
    -- Replay:

    -- Replay (F5: play mode) (Mod-F5: start/stop recording).
    -- F5 chosen since a key at an edge is easier to hit
    [KeyDefs.KEY_F5] =
    function( keyEvent )
        if ( keyEvent.modifier ) then
            -- Mod-F5: start/stop recording.
            local replay = PlayerActor:GetReplay()
            if ( not replay:IfRecording() ) then
                Log( "Started recording replay.  Press Mod-F5 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
        else
            -- F5: play mode
            -- 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
    end,

    ----------------------------------------------------------------------------
    -- Scene-graph:

    -- Reload scene (Mod-F11).
    [KeyDefs.KEY_F11] =
    function( keyEvent )
        if ( keyEvent.modifier ) then
            Scene:Reload()
        end
    end,

    -- Save scene graph (Mod-F12).  The C++ core enables OSG stats if F12 is pressed.
    [KeyDefs.KEY_F12] =
    function( keyEvent )
        if ( keyEvent.modifier ) then
            sim.SceneGraph:SaveToFile( defs.PROGRAM_NAME_SHORT .. "_scene.osg" )
        else
            -- Disable HUD's info text line to show OSG stats.
            if ( sim.Base:IfGfxSysIsOSG() ) then
                Hud:EnableInfoText( false )
            end
        end
    end,
}

--------------------------------------------------------------------------------
-- Keyboard handler function.

-- Key is passed as a table or nil:
-- keyEvent = { key=int, lkey=int, ctrl=bool, shift=bool, alt=bool, modifier=bool }
-- lkey means key converted to lower-case.
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
