-- LUA Script - precede every function and global member with lowercase name of script + '_main'
local whackyRacer = {}

local Q = require "scriptbank\\quatlib"

local sin  = math.sin
local cos  = math.cos 
local rad  = math.rad
local deg  = math.deg
local asin = math.asin
local atan = math.atan2


function whacky_racer_walkthru_init( e )
    Include( "quatlib.lua" )
end

function WhackyRacerAddWheel( e )
    for k, v in pairs( whackyRacer ) do
        if #v.wheels < 4 then
            v.wheels[#v.wheels + 1] = { Ent = e }
            return
        end
    end    
end

function WhackyRacerAddHammer( e )
    for k, v in pairs( whackyRacer ) do
        if v.hammer == nil then
            v.hammer = { Ent = e, angle = -20 }
            return
        end
    end    
end

local function Rotate3D ( x, y, z, xrot, yrot, zrot ) 

    function RotatePoint2D ( x, y, Ang )  -- Ang in radians
        local Sa, Ca = sin( Ang ), cos( Ang )
        return x * Ca - y * Sa, x * Sa + y * Ca
    end
 
    local NX, NY, NZ = x, y, z

    -- X
    NZ, NY = RotatePoint2D ( NZ, NY, -xrot )
    
    -- Y
    NX, NZ = RotatePoint2D ( NX, NZ, -yrot )
    
    -- Z
    NY, NX = RotatePoint2D ( NY, NX, -zrot )

    return NX, NY, NZ
end

local wheelOffsets = 
        {{ x = -62.7, y = -3.7, z = 136.4 },  -- left front
         { x = -62.7, y = -3.7, z =   0.6 },  -- left rear
         { x =  62.7, y = -3.7, z = 136.4 },  -- right front
         { x =  62.7, y = -3.7, z =   0.6 }   -- right rear
        }

local wheelHeights = {}

local function calcAngle( h1, h2, l )
    return asin( ( h2 - h1 ) / l )
end

local function GetWheelHeights( v, xA, yA, zA )

    local xo, yo, zo
    -- given proposed vehicle position and current angles
    -- calculate the terrain heights under the eight wheels
    local avgHeight, avgRoll, avgPitch = 0, 0, 0
    
    for i = 1, 4 do
        local wo = wheelOffsets[i]
    
        xo, yo, zo = Rotate3D( wo.x, wo.y, wo.z, xA, yA, zA )

        wheelHeights[i] = GetTerrainHeight( v.x + xo, v.z + zo ) + 24
        
        avgHeight = avgHeight + wheelHeights[i]
        
        wheelHeights[i] = ( v.y + yo ) - wheelHeights[i] 
    end
    
    avgHeight = avgHeight / 4
    
    -- now work out vehicle angles
    local ax1 = calcAngle( wheelHeights[1], wheelHeights[3],
	                       wheelOffsets[3].x - wheelOffsets[1].x );
							   
    local ax2 = calcAngle( wheelHeights[2], wheelHeights[4],
	                       wheelOffsets[4].x - wheelOffsets[2].x );
    
    avgRoll = ( ax1 + ax2 ) / 2
    
    local p1 = calcAngle( wheelHeights[1], wheelHeights[2],
	                      wheelOffsets[1].z + wheelOffsets[2].z );
						  
    local p2 = calcAngle( wheelHeights[3], wheelHeights[4],
	                      wheelOffsets[3].z + wheelOffsets[4].z );
    
    avgPitch = ( p1 + p2 ) / 2

    return avgHeight + 3, avgPitch, avgRoll
end

local turnDir = { 'F', 'R', 'F', 'R' }

local rotQuat = nil
local finQuat = nil

local function PositionWheel( num, v, xA, yA, zA )

    local wo = wheelOffsets[num]
    
    local xo, yo, zo = Rotate3D( wo.x, wo.y, wo.z, xA, yA, zA )
    
    local suspensionOffset = wheelHeights[num]
    
    if suspensionOffset >  4 then suspensionOffset =  4 end
    if suspensionOffset < -4 then suspensionOffset = -4 end
    
    ResetPosition( v.wheels[num].Ent, v.x + xo, 
                                      v.y + yo - suspensionOffset, 
                                      v.z + zo );
        
    if turnDir[num] == 'F' then
        if num == 1 then
            finQuat = Q.FromEuler( 0, rad( v.turnAng ), 0 )
        else
            finQuat = Q.FromEuler( 0, rad( -v.turnAng ), rad( 180 ) )
        end
    else
        if num == 2 then
            finQuat = Q.FromEuler( 0, 0, 0 )
        else
            finQuat = Q.FromEuler( 0, 0, rad( 180 ) )
        end
    end
    
    if num < 3 then suspensionOffset = -suspensionOffset end
    
    finQuat = Q.Mul( v.quat, finQuat )
    
    if num > 2 then
        rotQuat = Q.FromEuler( rad( -v.rotAng ), 0, rad( suspensionOffset * 2 ) )
    else
        rotQuat = Q.FromEuler( rad( v.rotAng ), 0, rad( suspensionOffset * 2 ) )
    end
    
    v.rotAng = v.rotAng - v.speed / 10

    if v.rotAng < -180 then 
        v.rotAng = v.rotAng + 360
    elseif v.rotAng > 180 then
        v.rotAng = v.rotAng - 360
    end
    
    finQuat = Q.Mul( finQuat, rotQuat )

    local xA, yA, zA = Q.ToEuler( finQuat )
    
    ResetRotation( v.wheels[num].Ent, deg( xA ), deg( yA ), deg( zA ) )
end

local function PositionWheels( v, xA, yA, zA )
    for i = 1, 4 do
        PositionWheel( i, v, xA, yA, zA )
    end
end

local function PositionVehicle( e, v, xA, yA, zA )
    
    ResetPosition( e, v.x, v.y, v.z )
    ResetRotation( e, deg( xA ), deg( yA ), deg( zA ) )

end

local ho = { x = 0, y = 55.5, z = 129.3 }

local function PositionHammer( v, xA, yA, zA )


    local xo, yo, zo = Rotate3D( ho.x, ho.y, ho.z, xA, yA, zA )
        
    ResetPosition( v.hammer.Ent, v.x + xo, v.y + yo, v.z + zo )
    
    xA, yA, zA = Q.ToEuler( Q.Mul( v.quat, Q.FromEuler( rad( v.hammer.angle ), 0 ,0 ) ) )

    ResetRotation( v.hammer.Ent, deg( xA ), deg( yA ), deg( zA ) )
    
    if v.fireHammer then
        if v.hammer.angle > -170 then
            v.hammer.angle = v.hammer.angle - 17
        else
            v.fireHammer = false
        end
    elseif v.hammer.angle < -20 then
        v.hammer.angle = v.hammer.angle + 5
    else
        v.hammer.angle = -20
    end
end

local maxSpeed      = 20
local acceleration  = 0.03

local function PlayerControl( v )
    if v.speed < maxSpeed and g_KeyPressW == 1 then
        v.speed = v.speed + acceleration
    end
    if v.speed > 0 and g_KeyPressS == 1 then
        v.speed = v.speed - acceleration * 2
    end
    if g_KeyPressR == 1 then
        if v.turnAng < 30 then
            v.turnAng = v.turnAng + 0.3
        end
    elseif v.turnAng > 0 then
        v.turnAng = v.turnAng - 0.1
    end
    if g_KeyPressE == 1 then
        if v.turnAng > -30 then
            v.turnAng = v.turnAng - 0.3
        end
    elseif v.turnAng < 0 then
        v.turnAng = v.turnAng + 0.1
    end
    if not v.fireHammer and 
       v.hammer.angle > -60 and g_MouseClick == 1 then
        v.fireHammer = true
    end
end

local function MoveVehicle( v, xA, yA, zA )
    
    if v.speed > 0 then
        local mX, mY, mZ = Rotate3D( 0, 0, v.speed, xA, yA, zA )
    
        v.x, v.y, v.z = v.x + mX, v.y + mY, v.z + mZ

        v.vmAng = deg( atan( mX, mZ ) )
    
        -- we need to turn the vehicle a small amount each
        -- frame until we are pointing in the right direction

        v.quat = Q.Mul( v.quat, Q.FromEuler( 0, rad( v.turnAng / 20 ),0 ) )
    end
end    

local function GotEnts( e, v )
    if #v.wheels ~= 4 then
        PromptLocal( e, "Not enough wheel entities!" )
        return false
    end
    return true
end

local function AttachPlayer( v, xA, yA, zA )
        
    local xo, yo, zo = Rotate3D( 0, 0, -300, xA, yA, zA )

    local y = GetTerrainHeight( v.x + xo, v.z + zo )
    
    SetFreezePosition( v.x + xo, y + 200, v.z + zo )
    SetFreezeAngle( 20, v.vmAng, 0 )
    TransportToFreezePosition()
end

function whacky_racer_walkthru_main( e )

    local vehicle = whackyRacer[e]
    
    if vehicle == nil then
        local Ent = g_Entity[e]
        
        whackyRacer[e] = 
            { state = 'init',
              wheels = {}, hammer = nil, fireHammer = false,
              x = Ent.x, y = Ent.y + 50, z = Ent.z,
              quat = Q.FromEuler( rad( GetEntityAngleX( e ) ), 
                                  rad( GetEntityAngleY( e ) ), 
                                  rad( GetEntityAngleZ( e ) ) ),
              vmAng = 0, rotAng = 0, turnAng = 0,
              speed = 0
            }
        return
    end
    
    if vehicle.state == 'init' and GotEnts( e, vehicle ) then

        vehicle.state = 'idle'
    end
    
    if vehicle.state == 'idle' then
	
        local xA, yA, zA = Q.ToEuler( vehicle.quat )
        local roll, pitch
        
        vehicle.y, pitch, roll = GetWheelHeights( vehicle, xA, yA, zA )
        
        vehicle.quat = Q.Mul( vehicle.quat, Q.FromEuler( pitch, 0, roll ) )
        
        xA, yA, zA = Q.ToEuler( vehicle.quat )
                
        PositionVehicle( e, vehicle, xA, yA, zA )

        PositionWheels( vehicle, xA, yA, zA )

        AttachPlayer( vehicle, xA, yA, zA )
        
        MoveVehicle( vehicle, xA, yA, zA )
        
        PlayerControl( vehicle )
        
        if vehicle.hammer ~= nil then
            PositionHammer( vehicle, xA, yA, zA )
        end            
    end
end
