/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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 3 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.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "HeadSpin.h"

#include "plComponent.h"
#include "plComponentReg.h"
#include "plPhysicalComponents.h"
#include "MaxMain/plMaxNode.h"
#include "MaxMain/MaxAPI.h"

#include <map>

#include "resource.h"

#include "plPickNode.h"

// local
#include "plClimbComponent.h"

// other
#include "plMessage/plClimbMsg.h"
#include "plPhysical/plCollisionDetector.h"
#include "MaxMain/plPhysicalProps.h"
#include "plPhysical/plSimDefs.h"
#include "pnSceneObject/plSceneObject.h"


/////////////////////////////////////////////////////////////////
//
// THE DUMMY
//
/////////////////////////////////////////////////////////////////
void DummyCodeIncludeFuncClimbTrigger() {}

/////////////////////////////////////////////////////////////////
//
// SOME ENUMS
//
/////////////////////////////////////////////////////////////////
// CLIMBCOMMANDS
enum Commands
{
    kMount              = 0,
    kEnableDismount     = 1,
    kDisableDismount    = 2,
    kEnableClimb        = 3,
    kDisableClimb       = 4,
    kFallOff            = 5,
    kRelease            = 6,
    kMaxCommands        = 7
};

// these synchronized ^^^^VVVVV
static const TCHAR* fCommandStrs[] =
{
    _T("Start Climbing"),
    _T("Enable Dismount"),
    _T("Disable Dismount"),
    _T("Enable Climb"),
    _T("Disable Climb"),
    _T("Fall Off"),
    _T("Let Go")
};

enum Directions
{
    kUp     = 0,
    kDown   = 1,
    kLeft   = 2,
    kRight  = 3,
    kMaxDirections
};

static const TCHAR* fDirectionStrs[] =
{
    _T("Up"),
    _T("Down"),
    _T("Left"),
    _T("Right")
};


/////////////////////////////////////////////////////////////////
//
// CLASS DESCRIPTOR AND PARAM BLOCK
//
/////////////////////////////////////////////////////////////////

// CLASS DESCRIPTOR
CLASS_DESC(plClimbTriggerComponent, gClimbTriggerDesc, "Avatar ClimbTrigger",  "AvatarClimbTrigger", COMP_TYPE_AVATAR, CLIMB_TRIGGER_COMPONENT_CLASS_ID)

// FORWARD REFERENCE OT THE COMPONENT DIALOG PROC
static plClimbTriggerComponentProc gClimbTriggerComponentProc;

// PARAM BLOCK
ParamBlockDesc2 gClimbTriggerBk
(   

    plComponent::kBlkComp, _T("ClimbTrigger"), 0, &gClimbTriggerDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    //Roll out
    IDD_COMP_CLIMB_TRIGGER, IDS_COMP_CLIMB_TRIGGER, 0, 0, &gClimbTriggerComponentProc,

    plClimbTriggerComponent::kCommand, _T("Command"),   TYPE_INT, 0, 0,
        p_default, kMount,
        p_end,

    plClimbTriggerComponent::kDirection, _T("Direction"),   TYPE_INT, 0, 0,
        p_default, kUp,
        p_end,

    plClimbTriggerComponent::kWallPicker, _T("WallPicker"), TYPE_INODE,     0, 0,
        p_end,

    p_end
);

/////////////////////////////////////////////////////////////////
//
// PLCLIMBTRIGGER IMPLEMENTATION
//
/////////////////////////////////////////////////////////////////

// CTOR
plClimbTriggerComponent::plClimbTriggerComponent()
{
    fClassDesc = &gClimbTriggerDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

extern const plArmatureMod * FindArmatureMod(const plSceneObject *obj);

// CONVERT
bool plClimbTriggerComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    plClimbMsg::Command enterCommand;       // when entering the region
    plClimbMsg::Command exitCommand;        // run this command when exiting the region
    bool enterStatus = false;
    bool exitStatus = false;
    plClimbMsg::Direction direction;        // direction is assumed the same for both enter and exit commands
                                            // i.e. enable up, disable up

    int iCommand = fCompPB->GetInt(plClimbTriggerComponent::kCommand);
    int iDirection = fCompPB->GetInt(plClimbTriggerComponent::kDirection);

    switch(iCommand)
    {
    case kMount:
        enterCommand = plClimbMsg::kStartClimbing;
        exitCommand = plClimbMsg::kNoCommand;
        break;
    case kEnableClimb:
        enterCommand = plClimbMsg::kEnableClimb;
        enterStatus = true;
        exitCommand = plClimbMsg::kEnableClimb;
        exitStatus = false;
        break;
    case kDisableClimb:
        enterCommand = plClimbMsg::kEnableClimb;
        enterStatus = false;
        exitCommand = plClimbMsg::kEnableClimb;
        exitStatus = true;
        break;
    case kEnableDismount:
        enterCommand = plClimbMsg::kEnableDismount;
        enterStatus = true;
        exitCommand = plClimbMsg::kEnableDismount;
        exitStatus = false;
        break;
    case kDisableDismount:
        enterCommand = plClimbMsg::kEnableDismount;
        enterStatus = false;
        exitCommand = plClimbMsg::kEnableDismount;
        exitStatus = true;
        break;
    case kFallOff:
        enterCommand = plClimbMsg::kFallOff;
        exitCommand = plClimbMsg::kNoCommand;
        break;
    case kRelease:
        enterCommand = plClimbMsg::kRelease;
        exitCommand = plClimbMsg::kNoCommand;
        break;
    }

    switch(iDirection)
    {
    case kUp:
        direction = plClimbMsg::kUp;
        break;
    case kDown:
        direction = plClimbMsg::kDown;
        break;
    case kLeft:
        direction = plClimbMsg::kLeft;
        break;
    case kRight:
        direction = plClimbMsg::kRight;
        break;
    }

    plKey nilKey;
    plKey target = node->GetSceneObject()->GetKey();
    plClimbMsg *enterMsg = nullptr;
    if (enterCommand != plClimbMsg::kNoCommand)
    {
        enterMsg = new plClimbMsg(nilKey, nilKey, enterCommand, direction, enterStatus, target);
        enterMsg->SetBCastFlag(plMessage::kPropagateToModifiers);
        enterMsg->SetBCastFlag(plMessage::kNetPropagate);
        enterMsg->SetBCastFlag(plMessage::kNetForce);
    }

    plClimbMsg *exitMsg = nullptr;
    if (exitCommand != plClimbMsg::kNoCommand)
    {
        exitMsg = new plClimbMsg(nilKey, nilKey, exitCommand, direction, exitStatus, target);
        exitMsg->SetBCastFlag(plMessage::kPropagateToModifiers);
        exitMsg->SetBCastFlag(plMessage::kNetPropagate);
        exitMsg->SetBCastFlag(plMessage::kNetForce);
    }

    plSimpleRegionSensor *sensMod = new plSimpleRegionSensor(enterMsg, exitMsg);
    node->AddModifier(sensMod, IGetUniqueName(node));
    
    return true;
}

bool plClimbTriggerComponent::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
    node->SetForceLocal(true);
    node->SetDrawable(false);

    plPhysicalProps *props = node->GetPhysicalProps();

    // only if movable will it have mass (then it will keep track of movements in PhysX)
    if ( node->IsMovable() || node->IsTMAnimatedRecur() )
        props->SetMass(1.0, node, pErrMsg);
    props->SetFriction(0.0, node, pErrMsg);
    props->SetRestitution(0.0, node, pErrMsg);
    props->SetBoundsType(plSimDefs::kExplicitBounds, node, pErrMsg);
    props->SetGroup(plSimDefs::kGroupDetector, node, pErrMsg);
    props->SetReportGroup(1<<plSimDefs::kGroupAvatar, node, pErrMsg);
//  props->SetPinned(true, node, pErrMsg);

    return true;
}


/////////////////////////////////////////////////////////////////
//
// DIALOG PROC IMPLEMENTATION
//
/////////////////////////////////////////////////////////////////
INT_PTR plClimbTriggerComponentProc::DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    IParamBlock2 *pb = pm->GetParamBlock();
    HWND hCommandMenu = GetDlgItem(hWnd, IDC_COMP_CLIMB_COMMAND);
    HWND hDirectionMenu = GetDlgItem(hWnd, IDC_COMP_CLIMB_DIRECTION);
    HWND hPick = GetDlgItem(hWnd, IDC_COMP_WALL_PICK);
    INode *curPick = nullptr;
    int curSurface = 0;

    switch (msg)
    {
    case WM_INITDIALOG:
        {
            int i = 0;
            // fill out the command menu
            for (i = 0; i < kMaxCommands; i++)
                ComboBox_AddString(hCommandMenu, fCommandStrs[i]);
            // reflect the current selection
            ComboBox_SetCurSel(hCommandMenu, pb->GetInt(ParamID(plClimbTriggerComponent::kCommand)));

            // fill out the direction menu
            for (i = 0; i < kMaxDirections; i++)
                ComboBox_AddString(hDirectionMenu, fDirectionStrs[i]);
            // reflect the current selection
            ComboBox_SetCurSel(hDirectionMenu, pb->GetInt(ParamID(plClimbTriggerComponent::kDirection)));

            // show the name of the currently picked item
            curPick = pb->GetINode(ParamID(plClimbTriggerComponent::kWallPicker));
            Button_SetText(hPick, (curPick == nullptr ? _T("None") : curPick->GetName()));
        }
        return TRUE;
        break;


    case WM_COMMAND:
        if (HIWORD(wParam) == BN_CLICKED)
        {
            if (LOWORD(wParam) == IDC_COMP_WALL_PICK)
            {
                // we're picking a new climbing wall
                std::vector<Class_ID> pickableClasses;
                pickableClasses.push_back(PHYSICS_TERRAIN_CID);     // allow picking terrains
                pickableClasses.push_back(PHYS_CLIMBABLE_CID);      // and climbables
                if (plPick::NodeRefKludge(pb, plClimbTriggerComponent::kWallPicker, &pickableClasses, true, false))         
                {
                    curPick = pb->GetINode(ParamID(plClimbTriggerComponent::kWallPicker));
                    Button_SetText(hPick, (curPick == nullptr ? _T("None") : curPick->GetName()));
                }
            
                return TRUE;
            }
        }
        else if (LOWORD(wParam) == IDC_COMP_CLIMB_COMMAND)
        {
            HWND hSurface = GetDlgItem(hWnd, IDC_COMP_CLIMB_COMMAND);
            curSurface = ComboBox_GetCurSel(hSurface);
            pb->SetValue(ParamID(plClimbTriggerComponent::kCommand), 0, curSurface);
        }
        else if (LOWORD(wParam) == IDC_COMP_CLIMB_DIRECTION)
        {
            HWND hSurface = GetDlgItem(hWnd, IDC_COMP_CLIMB_DIRECTION);
            curSurface = ComboBox_GetCurSel(hSurface);
            pb->SetValue(ParamID(plClimbTriggerComponent::kDirection), 0, curSurface);
        }
    }

    return FALSE;
}




/////////////////////////////////////////////////////////////////////////////////////////
//
//  CLIMB BLOCKER COMPONENT
//
/////////////////////////////////////////////////////////////////////////////////////////

//Class that accesses the paramblock below.
class plClimbBlockerComponent : public plComponent
{
public:
    plClimbBlockerComponent();

    // SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
    // of properties on the MaxNode, as it's still indeterminant.
    bool SetupProperties(plMaxNode *pNode, plErrorMsg *pErrMsg) override;
    bool Convert(plMaxNode *node, plErrorMsg *pErrMsg) override;
};

//Max desc stuff necessary below.
CLASS_DESC(plClimbBlockerComponent, gClimbBlockDesc, "Climb Blocker",  "ClimbBlocker", COMP_TYPE_AVATAR, Class_ID(0x170000ac, 0x3cee02c5))

ParamBlockDesc2 gClimbBlockBk
(
    plComponent::kBlkComp, _T("Climb Blocker"), 0, &gClimbBlockDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_CLIMB_BLOCK, IDS_COMP_CLIMB_BLOCKER, 0, 0, nullptr,

    p_end
);

plClimbBlockerComponent::plClimbBlockerComponent()
{
    fClassDesc = &gClimbBlockDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

// SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
bool plClimbBlockerComponent::SetupProperties(plMaxNode *node,  plErrorMsg *errMsg)
{
    node->SetDrawable(false);

    plPhysicalProps *props = node->GetPhysicalProps();

//  props->SetMass(0.0, node, errMsg);
//  props->SetFriction(0.0, node, errMsg);
//  props->SetRestitution(0.0, node, errMsg);
    props->SetBoundsType(plSimDefs::kExplicitBounds, node, errMsg);
//  props->SetPinned(true, node, errMsg);
    props->SetLOSBlockCustom(true, node, errMsg);

    return true;
}

bool plClimbBlockerComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return true;
}

