//
// Copyright(C) 2014-2015 Samuel Villarreal
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// DESCRIPTION:
//      Floor Mover Object Classes
//

#include "scripts/common.txt"

//-----------------------------------------------------------------------------
//
// Base Class
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokFloorMover
==============================================================
*/

class TurokFloorMover : BPR::CL_BPR_WorldObject //[BP_Randomizer]
{
    kActor  @self;
    float   distance;
    float   delayTime;
    float   currentTime;
    float   lerpTime;
    float   moveSpeed;
    float   diffHeight;
    float   destHeight;
    float   moveAmount;
    bool    bPlaySound;
    uint    stateFlags;
    
    TurokFloorMover(kActor @actor)
    {
		super(actor); //[BP_Randomizer]
        @self = actor;
        distance = 0;
        delayTime = 0;
        currentTime = 0;
        lerpTime = 0;
        moveSpeed = 0;
        destHeight = 0;
        moveAmount = 0;
        stateFlags = 0;
        bPlaySound = false;
    }
    
    /*
    ==============================================================
    SetMoveProperties
    ==============================================================
    */
    
    void SetMoveProperties(void)
    {
        if(self.SpawnParams(4) > 0)
        {
            moveSpeed = float(self.SpawnParams(4));
        }
        else
        {
            moveSpeed = 1.0f;
        }
        
        switch(self.Type())
        {
        case AT_DYNAMIC_FLOOR_1X:
        case AT_DYNAMIC_FLOOR_PERPETUAL:
            moveAmount = float(self.SpawnParams(7)) * GAME_SCALE + destHeight;
            break;
            
        default:
            moveAmount = float(self.SpawnParams(7) * 5) * GAME_SCALE + destHeight;
            break;
        }
    }
    
    /*
    ==============================================================
    ChangeAreaType
    ==============================================================
    */
    
    void ChangeAreaType(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        int flag = (self.SpawnFlags2() >> 28) & 1;
        
        if(self.GetFloorHeight() >= self.StepHeight())
        {
            World.ChangeAreaFlag(self.AreaID(), (flag != 0) ? AAF_TELEPORT : AAF_DAMAGE, false);
        }
        else
        {
            World.ChangeAreaFlag(self.AreaID(), (flag != 0) ? AAF_TELEPORT : AAF_DAMAGE, true);
        }
    }
    
    /*
    ==============================================================
    MoveFloor
    ==============================================================
    */
    
    void MoveFloor(void)
    {
        float t = (Math::Cos((1.0f - lerpTime) * Math::pi) + 1.0f) * 0.5f;
        
        self.Origin().z = (moveAmount - destHeight) * t + destHeight;
        World.ChangeSectorHeight(self.SectorIndex(), self.Origin().z - destHeight + distance);
    }
    
    /*
    ==============================================================
    LowerFloor
    ==============================================================
    */
    
    void LowerFloor(void)
    {
        int sound;
        
        SetMoveProperties();
        
        sound = self.SpawnParams(0);
        
        if(sound > 0 && lerpTime < 1.0f)
        {
            if(sound != 561 || lerpTime <= 0.75f)
            {
                bPlaySound = false;
            }
            else
            {
                sound = 0;
                
                if(!bPlaySound)
                {
                    bPlaySound = true;
                    self.PlaySound("sounds/shaders/generic_177.ksnd");
                }
            }
            
            if(sound > 0)
            {
                self.PlaySoundWithLookupID(sound);
            }
        }
        
        float amt = GAME_DELTA_TIME / moveSpeed;
        lerpTime += amt;
        
        if(lerpTime >= 1.0f)
        {
            stateFlags &= ~TFF_MOVING;
            stateFlags |= TFF_WAITING;
            lerpTime = 1.0f;
            delayTime = float(self.SpawnParams(2) * 15);
            
            self.StopSound();
        }
        
        MoveFloor();
    }
    
    /*
    ==============================================================
    RaiseFloor
    ==============================================================
    */
    
    void RaiseFloor(void)
    {
        int sound;
        
        SetMoveProperties();
        
        sound = self.SpawnParams(0);
        
        if(sound > 0)
        {
            if(sound != 561 || lerpTime <= 0.75f)
            {
                bPlaySound = false;
            }
            else
            {
                sound = 0;
                
                if(!bPlaySound)
                {
                    bPlaySound = true;
                    self.PlaySound("sounds/shaders/generic_177.ksnd");
                }
            }
            
            if(sound > 0)
            {
                self.PlaySoundWithLookupID(sound);
            }
        }
        
        float amt = GAME_DELTA_TIME / moveSpeed;
        lerpTime -= amt;
        
        if(lerpTime <= 0.0f)
        {
            stateFlags &= ~TFF_ACTIVATED;
            self.Flags() &= ~AF_ACTIVATED;
            lerpTime = 0.0f;
            
            // clear persistant data
            self.MarkPersistentBit(true);
            
            // stop looping sounds
            self.StopSound();
        }
        
        MoveFloor();
    }
    
    /*
    ==============================================================
    DelayWait
    ==============================================================
    */
    
    void DelayWait(void)
    {
        if(delayTime <= 0.0f)
        {
            return;
        }
        
        delayTime -= GAME_FRAME_TIME;
            
        if(delayTime <= 0.0f)
        {
            delayTime = 0;
            stateFlags &= ~TFF_WAITING;
        }
    }
    
    /*
    ==============================================================
    OnBeginLevel
    ==============================================================
    */
    
    void OnBeginLevel(void)
    {
        self.Flags() &= ~(AF_HIDDEN|AF_DISABLED);
    }
    
    /*
    ==============================================================
    OnSpawn
    ==============================================================
    */
    
    void OnSpawn(void)
    {
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
    }
    
    /*
    ==============================================================
    OnRestore
    ==============================================================
    */
    
    void OnRestore(void)
    {
        stateFlags |= TFF_ACTIVATED|TFF_MOVING;
        lerpTime = 1.0f;
        destHeight = self.Origin().z;
        distance = self.FloorHeight();
    }
};

//-----------------------------------------------------------------------------
//
// LowerOnce Mover
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokFloorMoverLowerOnce
==============================================================
*/

class TurokFloorMoverLowerOnce : TurokFloorMover
{
    float initialAngle;
    
    TurokFloorMoverLowerOnce(kActor @actor)
    {
        super(actor);
        initialAngle = 0;
    }
    
    /*
    ==============================================================
    Rotate
    ==============================================================
    */
    
    void Rotate(void)
    {
        float diff;
        float oldYaw;
        float speed;
        
        oldYaw = self.Yaw();
        speed = float(self.SpawnParams(1)) * 2.0f;
        
        self.Yaw() = initialAngle - (self.Origin().z - destHeight) / GAME_SCALE * -(Math::pi * 2.0f) / speed;
        diff = self.Yaw() - oldYaw;
        
        int sec1 = Player.Actor().SectorIndex();
        int sec2 = self.SectorIndex();
        
        if(sec1 >= 0 && sec2 >= 0 && Player.Actor().AreaID() == self.AreaID())
        {
            if(Player.Actor().OnGround())
            {
                // TODO
            }
        }
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        if((stateFlags & TFF_ACTIVATED) == 0)
        {
            return;
        }
        
        if((stateFlags & (TFF_MOVING|TFF_WAITING)) == (TFF_MOVING|TFF_WAITING))
        {
            int sec1 = Player.Actor().SectorIndex();
            int sec2 = self.SectorIndex();
            
            if(sec1 >= 0 && sec2 >= 0 && Player.Actor().AreaID() == self.AreaID())
            {
                delayTime -= GAME_FRAME_TIME;
                
                if(delayTime <= 0.0f)
                {
                    stateFlags &= ~TFF_ACTIVATED;
                }
            }
            else
            {
                stateFlags &= ~TFF_ACTIVATED;
            }
        }
        else
        {
            if((stateFlags & TFF_MOVING) != 0)
            {
                LowerFloor();
                
                if(self.Type() == AT_DYNAMIC_FLOOR_LOWER_AND_ROTATE)
                {
                    Rotate();
                }
            }
            else
            {
                if((stateFlags & TFF_WAITING) != 0)
                {
                    DelayWait();
                }
                else
                {
                    RaiseFloor();
                    
                    if(self.Type() == AT_DYNAMIC_FLOOR_LOWER_AND_ROTATE)
                    {
                        Rotate();
                    }
                    
                    if((stateFlags & TFF_ACTIVATED) == 0)
                    {
                        stateFlags |= (TFF_MOVING|TFF_WAITING|TFF_ACTIVATED);
                        delayTime = float(self.SpawnParams(2) * 15);
                    }
                }
            }
        }
    }
    
    /*
    ==============================================================
    OnActivate
    ==============================================================
    */
    
    void OnActivate(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        if(self.SpawnParams(2) > 0)
        {
            self.Flags() &= ~AF_ACTIVATED;
        }
        
        if((stateFlags & TFF_ACTIVATED) == 0)
        {
            stateFlags &= ~TFF_WAITING;
            stateFlags |= (TFF_MOVING|TFF_ACTIVATED);
            
            // store persistant data as being started
            self.MarkPersistentBit(false);
            
            lerpTime        = 0.0f;
            currentTime     = 0.0f;
            initialAngle    = self.Yaw();
            destHeight      = self.Origin().z;
            distance        = self.FloorHeight();
            
            if((self.SpawnFlags1() & 0x40000000) != 0)
            {
                kActor @actor = ActorFactory.Spawn("QuakeSource", 0, 0, 0, 0, self.SectorIndex());
                TurokQuakeSource @quake;
        
                if(actor is null)
                {
                    return;
                }

                @quake = cast<TurokQuakeSource@>(actor.ScriptObject().obj);
                
                if(quake is null)
                {
                    return;
                }

                quake.SetupShake(self.Origin(), 1.125f * GAME_SCALE, -0.1048f, float(self.SpawnParams(4)));
            }
        }
    }
};

//-----------------------------------------------------------------------------
//
// LowerChangeWaitRaise Mover
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokFloorMoverLowerChangeWaitRaise
==============================================================
*/

class TurokFloorMoverLowerChangeWaitRaise : TurokFloorMover
{
    TurokFloorMoverLowerChangeWaitRaise(kActor @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        if((stateFlags & TFF_ACTIVATED) == 0)
        {
            return;
        }
        
        ChangeAreaType();
        
        switch(stateFlags & (TFF_MOVING|TFF_WAITING))
        {
        case TFF_WAITING:
            delayTime -= GAME_FRAME_TIME;
            if(delayTime <= 0.0f)
            {
                stateFlags &= ~TFF_WAITING;
                stateFlags |= TFF_MOVING;
            }
            break;
            
        case TFF_MOVING:
            LowerFloor();
            if((stateFlags & TFF_WAITING) != 0)
            {
                stateFlags |= (TFF_MOVING|TFF_WAITING);
                delayTime   = float(self.SpawnParams(1) * 15);
            }
            break;
            
        case (TFF_MOVING|TFF_WAITING):
            if(delayTime > 0.0f)
            {
                delayTime -= GAME_FRAME_TIME;
            }
            else
            {
                RaiseFloor();
                
                if((stateFlags & TFF_ACTIVATED) == 0)
                {
                    World.ChangeAreaFlag(self.AreaID(), AAF_EVENT, true);
                }
            }
            break;
        }
    }
    
    /*
    ==============================================================
    OnActivate
    ==============================================================
    */
    
    void OnActivate(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        if((stateFlags & TFF_ACTIVATED) == 0)
        {
            stateFlags &= ~TFF_MOVING;
            stateFlags |= (TFF_ACTIVATED|TFF_WAITING);
            
            lerpTime    = 0.0f;
            currentTime = 0.0f;
            delayTime   = float(self.SpawnParams(2) * 15);
            destHeight  = self.Origin().z;
            distance    = self.FloorHeight();
        }
    }
};

//-----------------------------------------------------------------------------
//
// Perpetual Mover
//
//-----------------------------------------------------------------------------

/*
==============================================================
TurokFloorMoverPerpetual
==============================================================
*/

class TurokFloorMoverPerpetual : TurokFloorMover
{
    TurokFloorMoverPerpetual(kActor @actor)
    {
        super(actor);
    }
    
    /*
    ==============================================================
    OnTick
    ==============================================================
    */
    
    void OnTick(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        if((stateFlags & TFF_ACTIVATED) == 0)
        {
            return;
        }
        
        if((stateFlags & TFF_MOVING) != 0)
        {
            LowerFloor();
            
            if((stateFlags & TFF_WAITING) != 0)
            {
                stateFlags &= ~TFF_WAITING;
            }
        }
        else
        {
            RaiseFloor();
            
            if((stateFlags & TFF_ACTIVATED) == 0)
            {
                stateFlags |= (TFF_MOVING|TFF_ACTIVATED);
            }
        }
    }
    
    /*
    ==============================================================
    OnActivate
    ==============================================================
    */
    
    void OnActivate(void)
    {
        if(self.SectorIndex() <= -1)
        {
            return;
        }
        
        if((stateFlags & TFF_ACTIVATED) == 0)
        {
            stateFlags &= ~TFF_WAITING;
            stateFlags |= (TFF_MOVING|TFF_ACTIVATED);
            
            lerpTime    = 0.0f;
            destHeight  = self.Origin().z;
            distance    = self.FloorHeight();
            
            self.MarkPersistentBit(false);
        }
    }
    
    /*
    ==============================================================
    OnRestore
    ==============================================================
    */
    
    void OnRestore(void)
    {
        self.Flags() |= AF_IGNORESOUNDEVENTS;
        
        stateFlags &= ~TFF_WAITING;
        stateFlags |= (TFF_MOVING|TFF_ACTIVATED);
        
        lerpTime = 0.0f;
        destHeight = self.Origin().z;
        distance = self.FloorHeight();
    }
};
