/*
Copyright 2007-2023. Algoryx Simulation AB.

All AGX source code, intellectual property, documentation, sample code,
tutorials, scene files and technical white papers, are copyrighted, proprietary
and confidential material of Algoryx Simulation AB. You may not download, read,
store, distribute, publish, copy or otherwise disseminate, use or expose this
material unless having a written signed agreement with Algoryx Simulation AB, or having been
advised so by Algoryx Simulation AB for a time limited evaluation, or having purchased a
valid commercial license from Algoryx Simulation AB.

Algoryx Simulation AB disclaims all responsibilities for loss or damage caused
from using this software, unless otherwise stated in written agreements with
Algoryx Simulation AB.
*/

#pragma once

#include <agxSDK/MergedState.h>

namespace agxSDK
{
  /**
  Data for merge or split actions created in an implementation of an agxSDK::MergeSplitAlgorithm.
  Instances of this class is generated by using the API of agxSDK::MergeSplitActionContainer.
  */
  class AGXPHYSICS_EXPORT MergeSplitAction
  {
    public:
      /**
      Creates an empty action.
      */
      static MergeSplitAction empty();

      /**
      Creates a merge action given an edge and the merge split handler.
      An empty action is created if the bodies in the edge interaction aren't
      allowed to merge.
      */
      static MergeSplitAction merge( agx::MergedBody::EdgeInteraction* edge, const agxSDK::MergeSplitHandler* handler );

      /**
      Creates a merge action given a merged state and an edge.
      An empty action is created if the bodies in the edge interaction aren't
      allowed to merge.
      */
      static MergeSplitAction merge( const agxSDK::MergedState& mergedState, agx::MergedBody::EdgeInteraction* edge );

      /**
      Creates a split action given a rigid body and the merge split handler.
      An empty action is created if the body isn't allowed to be splitted.
      */
      static MergeSplitAction split( agx::RigidBody* rb, const agxSDK::MergeSplitHandler* handler );

      /**
      Creates a split action given an edge and the merge split handler (to fetch allowed actions, i.e., 'may we split this edge').
      An empty action is created if the edge isn't allowed to be splitted.
      */
      static MergeSplitAction split( agx::MergedBody::EdgeInteraction* edge, const agxSDK::MergeSplitHandler* handler );

      /**
      Creates a split action given a merged state and action.
      An empty action is created if the allowed actions doesn't match the \p action.
      \param mergedState - the merged state
      \param action - split action (split first or split second or split both)
      \param edge - optional edge to split (i.e., will not split all edges)
      */
      static MergeSplitAction split( const agxSDK::MergedState& mergedState, agxSDK::MergedState::Actions action, agx::MergedBody::EdgeInteraction* edge = nullptr );

    public:
      /**
      Construct given merged state, type of action and (optional) an edge to add or remove.
      \param mergedState - current merged state
      \param actionType - type of action
      \param edge - edge to add, or optionally, edge to remove (if split state)
      */
      MergeSplitAction( const agxSDK::MergedState& mergedState, agxSDK::MergedState::Actions actionType, agx::MergedBody::EdgeInteraction* edge = nullptr );

      /**
      Synchronizes data to current state, e.g., bodies that has been merged or splitted
      after this action was create.
      \return this, synchronized
      */
      MergeSplitAction& sync();

      /**
      \return current merged state (call sync to update)
      */
      const agxSDK::MergedState& getMergedState() const;

      /**
      \return the type of action
      */
      agxSDK::MergedState::Actions getType() const;

      /**
      \return edge to add or remove (depending on action type)
      */
      agx::MergedBody::EdgeInteraction* getEdge() const;

      /**
      \return true if this is a valid action
      */
      agx::Bool isValid() const;

    private:
      agxSDK::MergedState m_mergedState;
      agxSDK::MergedState::Actions m_actionType;
      agx::MergedBody::EdgeInteractionRef m_edge;
  };

  /**
  Object containing merge-split actions to be processed by the agxSDK::MergeSplitHandler.
  */
  class AGXPHYSICS_EXPORT MergeSplitActionContainer
  {
    public:
      typedef agx::Vector< MergeSplitAction > Actions;
      typedef Actions::iterator ActionsIterator;
      typedef Actions::const_iterator ActionsConstIterator;

    public:
      /**
      Default constructor.
      */
      MergeSplitActionContainer( agx::UInt capacity = agx::UInt( 256 ) );

      /**
      \return true if no actions are present
      */
      agx::Bool empty() const;

      /**
      \return begin iterator of the actions
      */
      ActionsIterator begin();

      /**
      \return begin iterator of the actions
      */
      ActionsConstIterator begin() const;

      /**
      \return end iterator of the actions
      */
      ActionsIterator end();

      /**
      \return end iterator of the actions
      */
      ActionsConstIterator end() const;

      /**
      Merge given merged state and an edge. The action wont be added if
      it violates the allowed actions in the merged state.
      \param mergedState - merged state of the interaction
      \param edge - edge that merged the objects
      \return true if the edge is valid and doesn't violate the allowed actions
      */
      agx::Bool merge( const agxSDK::MergedState& mergedState, agx::MergedBody::EdgeInteraction* edge );

      /**
      Split given allowed actions in the merged state. Fails (returns false) when
      none of the objects may be split.
      \param mergedState - current merged state
      \return true if at least one of the objects may be split
      */
      agx::Bool split( const agxSDK::MergedState& mergedState );

      /**
      Split given action mask. E.g., actions.split( mergedState, MergedState::Actions( MergeSplitAction::SPLIT_FIRST ) ).
      \param mergedState - current merged state
      \param action - split action
      \return true if the action may be performed and the split action was added
      */
      agx::Bool split( const agxSDK::MergedState& mergedState, agxSDK::MergedState::Actions action );

      /**
      Split a single edge.
      \param mergedState - current merged state
      \param edge - edge to split
      \return true if the edge is valid to split
      */
      agx::Bool split( const MergedState& mergedState, agx::MergedBody::EdgeInteraction* edge );

      /**
      Adds the action to the container. Consider using merge and split methods instead.
      Example usage: actions.add( MergeSplitAction::merge( edge, handler ) ), when
      you're splitting or merging without a given mergedState.
      \param action - action to add
      \return true if the action is valid and added
      */
      agx::Bool add( const MergeSplitAction& action );

      /**
      Append actions to this container.
      \param other - other container
      */
      void append( const MergeSplitActionContainer& other );

      /**
      Clears data.
      */
      void clear();

    private:
      Actions m_actions;
  };

  AGX_FORCE_INLINE const MergedState& MergeSplitAction::getMergedState() const
  {
    return m_mergedState;
  }

  AGX_FORCE_INLINE MergedState::Actions MergeSplitAction::getType() const
  {
    return m_actionType;
  }

  AGX_FORCE_INLINE agx::MergedBody::EdgeInteraction* MergeSplitAction::getEdge() const
  {
    return m_edge;
  }

  AGX_FORCE_INLINE MergeSplitActionContainer::ActionsIterator MergeSplitActionContainer::begin()
  {
    return m_actions.begin();
  }

  AGX_FORCE_INLINE MergeSplitActionContainer::ActionsConstIterator MergeSplitActionContainer::begin() const
  {
    return m_actions.begin();
  }

  AGX_FORCE_INLINE MergeSplitActionContainer::ActionsIterator MergeSplitActionContainer::end()
  {
    return m_actions.end();
  }

  AGX_FORCE_INLINE MergeSplitActionContainer::ActionsConstIterator MergeSplitActionContainer::end() const
  {
    return m_actions.end();
  }
}
