//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef _ACTIONMAP_H_
#define _ACTIONMAP_H_

#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _ITICKABLE_H_  
#include "core/iTickable.h"  
#endif  

class ContextAction;
struct InputEventInfo;

struct EventDescriptor
{
   U8  flags;      ///< Combination of any modifier flags.
   U8  eventType;  ///< SI_KEY, etc.
   U16 eventCode;  ///< From event.h
};

/// Map raw inputs to a variety of actions.  This is used for all keymapping
/// in the engine.
/// @see ActionMap::Node
class ActionMap : public SimObject
{
   typedef SimObject Parent;
   friend class ContextAction;

  protected:
   bool onAdd();

   struct Node {
      U32 modifiers;
      U32 action;

      enum Flags {
         Ranged      = BIT(0),   ///< Ranged input.
         HasScale    = BIT(1),   ///< Scaled input.
         HasDeadZone = BIT(2),   ///< Dead zone is present.
         Inverted    = BIT(3),   ///< Input is inverted.
         NonLinear   = BIT(4),   ///< Input should be re-fit to a non-linear scale
         BindCmd     = BIT(5),    ///< Bind a console command to this.
         Held        = BIT(6),
         DoubleTap   = BIT(7)
      };

      U32 flags;           ///< @see Node::Flags
      F32 deadZoneBegin;
      F32 deadZoneEnd;
      F32 scaleFactor;

      SimObject* object;                ///< Object to call consoleFunction on.
      StringTableEntry consoleFunction; ///< Console function to call with new values.

      char *makeConsoleCommand;         ///< Console command to execute when we make this command.
      char *breakConsoleCommand;        ///< Console command to execute when we break this command.
      ContextAction* contextEvent;      ///< Event that kicks off via context-keybind actions such as holding or double-tapping
   };

   /// Used to represent a devices.
   struct DeviceMap
   {
      U32 deviceType;
      U32 deviceInst;

      Vector<Node> nodeMap;
      DeviceMap():deviceType(NULL), deviceInst(NULL){
         VECTOR_SET_ASSOCIATION(nodeMap);
      }
      ~DeviceMap();
   };
   struct BreakEntry
   {
      U32 deviceType;
      U32 deviceInst;
      U32 objInst;
      SimObject* object;
      StringTableEntry consoleFunction;
      char *breakConsoleCommand;

      // It's possible that the node could be deleted (unlikely, but possible,
      //  so we replicate the node flags here...
      //
      U32 flags;
      F32 deadZoneBegin;
      F32 deadZoneEnd;
      F32 scaleFactor;
   };


   Vector<DeviceMap*>        mDeviceMaps;
   static Vector<BreakEntry> smBreakTable;

   // Find: return NULL if not found in current map, Get: create if not
   //  found.
   const Node* findNode(const U32 inDeviceType, const U32 inDeviceInst,
                        const U32 inModifiers,  const U32 inAction);
   bool findBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
   bool nextBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
   Node* getNode(const U32 inDeviceType, const U32 inDeviceInst,
                 const U32 inModifiers,  const U32 inAction,
                 SimObject* object = NULL);

   void removeNode(const U32 inDeviceType, const U32 inDeviceInst,
                 const U32 inModifiers,  const U32 inAction,
                 SimObject* object = NULL);

   void enterBreakEvent(const InputEventInfo* pEvent, const Node* pNode);

   static const char* getModifierString(const U32 modifiers);

   /// Pass index to a break entry, and this function will fire it off.
   static void fireBreakEvent(U32 idx, F32 value = 0.f);

  public:
   ActionMap();
   ~ActionMap();

   void dumpActionMap(const char* fileName, const bool append) const;

   static bool createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor);

   bool processBind(const U32 argc, const char** argv, SimObject* object = NULL);
   bool processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd);
   bool processUnbind(const char *device, const char *action, SimObject* object = NULL);
   bool processHoldBind(const char *device, const char *action, const char *holdFunc, const char *tapFunc, const U32 holdTime, const bool holdOnly, const bool returnHoldTime = false);

   /// @name Console Interface Functions
   /// @{
   const char* getBinding ( const char* command );                    ///< Find what the given command is bound to.
   const char* getCommand ( const char* device, const char* action ); ///< Find what command is bound to the given event descriptor .
   bool        isInverted ( const char* device, const char* action );
   F32         getScale   ( const char* device, const char* action );
   const char* getDeadZone( const char* device, const char* action );
   /// @}


   static bool        getKeyString(const U32 action, char* buffer);
   static bool        getDeviceName(const U32 deviceType, const U32 deviceInstance, char* buffer);
   static const char* buildActionString( const InputEventInfo* event );

   bool processAction(const InputEventInfo*);
   
   /// Return true if the given event triggers is bound to an action in this map.
   bool isAction( U32 deviceType, U32 deviceInst, U32 modifiers, U32 action );

   /// Returns the global ActionMap.
   static ActionMap* getGlobalMap();
   
   static bool checkBreakTable(const InputEventInfo*);
   static bool handleEvent(const InputEventInfo*);
   static bool handleEventGlobal(const InputEventInfo*);

   /// Called when we lose focus, to make sure we have no dangling inputs.
   ///
   /// This fires a break event for every currently pending item in the break
   /// table.
   static void clearAllBreaks();

   /// Returns true if the specified key + modifiers are bound to something
   /// on the global action map.
   static bool checkAsciiGlobal(U16 key, U32 modifiers);

   static bool getDeviceTypeAndInstance(const char *device, U32 &deviceType, U32 &deviceInstance);

   DECLARE_CONOBJECT(ActionMap);
};

class ContextAction : public ITickable
{
   ActionMap::Node* mButton;                   ///< our button we're holding  
   F32 mMinHoldTime;                   ///< minimum time to qualify as 'held'. If we hold less than this,   
                                       ///< it's a 'press', otherwise it's a 'held'  
public:
   F32 mStartTime;                      ///< Our timestamp when we first pressed.  
   F32 mEventValue;                    ///< Event value from our key event.  
   StringTableEntry mConsoleFunctionHeld; ///< Console function to call with new values if we held over  
                                         ///< a certain time.  

   bool mHoldOnly;                     ///< does this only care if we're holding?     
                                       ///< true means that it only fires a function while holding  
                                       ///< false time-contexts it  
   bool mBreakEvent;                    ///< Button is no longer being pressed!  
   bool mDidHold;                       ///< did we, at some point in the process, hold the button?  
   bool mActive;                        ///< do we be tickin?  
   bool mReturnHoldTime;                ///< Do we return back our time held?  

   ContextAction(StringTableEntry func, F32 minHoldTime, ActionMap::Node* button, bool holdOnly);
   virtual void processTick();
   virtual void interpolateTick(F32 delta) {}   
   virtual void advanceTime(F32 timeDelta) {}
};
#endif // _ACTIONMAP_H_
