// Supported with union (c) 2018 Union team

#ifndef __UNION_INVOKE_H__
#define __UNION_INVOKE_H__

#include "..\CHookInfo\CHookInfo.h"
#include "..\CUntyped\CUntyped.h"

#define IvkTargetVer( address, version ) (Union.GetEngineVersion() == version ? address : Null)

namespace UnionCore
{
  enum EInterMode
  {
    IVK_DISABLED  = 1 << 1,
    IVK_NORMAL    = 1 << 2,
    IVK_AUTO      = 1 << 3,
    IVK_REDEFINE  = 1 << 4,
    IVK_PROTECTED = 1 << 5,
    IVK_READONLY  = IVK_DISABLED
  };

  template <typename T>
  class CInvoke
  {
    THookInfo mData;

  public:
    CInvoke(
      const TInstance&,
      const TInstance& = NULL,
      const uint32&    = IVK_AUTO );

    CInvoke(
      const TInstance&,
      const T&,
      const uint32&    = IVK_AUTO );

    bool32 Attach(
      const TInstance&,
      const TInstance& = NULL,
      const uint32&    = IVK_AUTO );

    bool32  Commit();
    bool32 Detach();
    bool32 DetachTree();
    void   Protect();
    void   Unprotect();
    const  THookInfo& GetHookInfo();

    operator T();
    T& GetData() { return (T&)mData.lpPointer; }
    T ReinterpretTo();
  };

  template <class T>
  CInvoke<T>::CInvoke( const TInstance& from, const TInstance& to, const uint32& flag )
  {
    Attach( from, to, flag );
  }

  template <class T>
  CInvoke<T>::CInvoke( const TInstance& from, const T& to, const uint32& flag )
  {
    Attach( from, to, flag );
  }

  template <class T>
  bool32 CInvoke<T>::Attach( const TInstance& from, const TInstance& to, const uint32& flag )
  {
    if( !from || CCallBack::IsExist( mData ) ) {
      return False;
    }
    mData.ulProtected = ( flag & IVK_PROTECTED ) != 0;
    if( flag & IVK_AUTO )
    {
      mData.lpOriginal = (T)CCallBack::Auto( (uint32)from.data );
    }
    else
    {
      mData.lpOriginal = (T)from.data;
    }
    mData.lpPointer = mData.lpOriginal;
    mData.lpDetour = to;

    if( !to || ( flag & IVK_DISABLED ) )
    {
      return False;
    }
    if( flag & IVK_REDEFINE )
    {
      return CCallBack::Redefine( mData );
    }
    return CCallBack::Attach( mData );
  }

  template <class T>
  bool32 CInvoke<T>::Commit()
  {
    return CCallBack::Commit( mData );
  }

  template <class T>
  bool32 CInvoke<T>::Detach()
  {
    return CCallBack::Detach( mData );
  }

  template <class T>
  bool32 CInvoke<T>::DetachTree()
  {
    return CCallBack::DetachTree( mData );
  }

  template <class T>
  void CInvoke<T>::Protect()
  {
    mData.ulProtected = TRUE;
  }

  template <class T>
  void CInvoke<T>::Unprotect()
  {
    mData.ulProtected = FALSE;
  }

  template <class T>
  const THookInfo& CInvoke<T>::GetHookInfo()
  {
    return mData;
  }

  template <class T>
  CInvoke<T>::operator T()
  {
    return (T&)mData.lpPointer;
  }

  template <class T>
  T CInvoke<T>::ReinterpretTo()
  {
    return (T&)mData.lpPointer;
  }
} // namespace UnionCore

#endif // __UNION_INVOKE_H__