/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once


#include <Windows.h>
#include <msclr/marshal_cppstd.h>

#include "geode_defs.hpp"
#include "begin_native.hpp"
#include <geode/ExceptionTypes.hpp>
#include "end_native.hpp"


using namespace System;
using namespace System::Collections::Generic;
using namespace System::Runtime::Serialization;
using namespace msclr::interop;

namespace Apache
{
  namespace Geode
  {
    namespace Client
    {

      ref class GeodeException;

      /// <summary>
      /// Factory delegate to create a managed Geode exception.
      /// </summary>
      /// <remarks>
      /// For each managed exception class, its factory delegate is registered
      /// and maintained in a static dictionary mapped to its corresponding
      /// native Geode C++ exception name.
      /// </remarks>
      delegate GeodeException^ CreateException2(
        const apache::geode::client::Exception& nativeEx, System::Exception^ innerException);

      /// <summary>
      /// The base exception class of all managed Geode exceptions.
      /// </summary>
      [Serializable]
      public ref class GeodeException
        : public System::Exception
      {
      private:

        /// <summary>
        /// Prefix for distiguishing managed system exceptions
        /// </summary>
        literal String^ MgSysExPrefix = "GFCLI_EXCEPTION:";

        /// <summary>
        /// This contains a mapping of the native Geode exception class
        /// name to the factory delegate of the corresponding managed Geode
        /// exception class.
        /// </summary>
        static Dictionary<String^, CreateException2^>^ Native2ManagedExMap =
          Init( );

        /// <summary>
        /// Name and delegate pair class. The Native2ManagedExMap dictionary
        /// is populated from a static array of this class.
        /// </summary>
        value class NameDelegatePair
        {
        public:

          /// <summary>
          /// The name of the native Geode exception class.
          /// </summary>
          String^ m_name;

          /// <summary>
          /// The factory delegate of the managed Geode exception class
          /// corresponding to <c>m_name</c>
          /// </summary>
          CreateException2^ m_delegate;
        };


      internal:

        /// <summary>
        /// Static method to associate the native exception names with
        /// the corresponding managed exception factory delegates.
        /// </summary>
        /// <remarks>
        /// This method is not thread-safe and should be called in a single thread.
        /// </remarks>
        static Dictionary<String^, CreateException2^>^ Init( );

        /// <summary>
        /// Create the managed Geode exception for a given native Geode exception.
        /// As a special case normal system exceptions are also created when the
        /// native exception is a wrapper of a managed system exception.
        /// </summary>
        /// <remarks>
        /// Wherever the native Geode C++ code raises a <c>apache::geode::client::Exception</c>,
        /// the CLI wrapper code should have a catch-all for those and use
        /// this function to create the corresponding managed Geode exception.
        /// If no managed Geode exception has been defined (or has not been
        /// added using _GF_MG_EXCEPTION_ADD3 in ExceptionTypesMN.cpp) then a
        /// generic <c>GeodeException</c> exception is returned.
        /// </remarks>
        /// <param name="nativeEx">The native Geode exception object</param>
        /// <returns>
        /// The managed Geode exception object corresponding to the provided
        /// native Geode exception object.
        /// </returns>
        static Exception^ Get(const apache::geode::client::Exception& nativeEx);

        /// <summary>
        /// Get the stack trace for the given native exception.
        /// </summary>
        /// <param name="nativeEx">The native Geode exception object</param>
        /// <returns>The stack trace of the native exception.</returns>
        inline static String^ GetStackTrace(
          const apache::geode::client::Exception& nativeEx )
        {
          return marshal_as<String^>(nativeEx.getStackTrace());
        }

        /// <summary>
        /// Gets the C++ native exception object for a given managed exception.
        /// </summary>
        /// <remarks>
        /// This method is to handle conversion of managed exceptions to
        /// C++ exception for those thrown by managed callbacks.
        /// For non-Geode .NET exceptions we wrap it inside the generic
        /// <c>GeodeException</c> with a special prefix in message.
        /// While converting the exception back from C++ to .NET if the
        /// prefix is found in the message, then it tries to construct
        /// the original exception by reflection on the name of exception
        /// contained in the message. Note that in this process the
        /// original stacktrace is appended to the message of the exception.
        /// </remarks>
        inline static std::shared_ptr<apache::geode::client::Exception> GetNative(Exception^ ex)
        {
          if (ex != nullptr) {
            GeodeException^ gfEx = dynamic_cast<GeodeException^>(ex);
            if (gfEx != nullptr) {
              return gfEx->GetNative();
            }
            else {
              std::shared_ptr<apache::geode::client::Exception> cause;
              if (ex->InnerException != nullptr) {
                cause = GeodeException::GetNative(ex->InnerException);
              }
              return std::make_shared<apache::geode::client::Exception>(
                marshal_as<std::string>(MgSysExPrefix + ex->ToString()) 
                  + (cause ? cause->getMessage() : ""));
            }
          }
          return nullptr;
        }

        /// <summary>
        /// Gets the C++ native exception object for this managed
        /// <c>GeodeException</c>.
        /// </summary>
        virtual std::shared_ptr<apache::geode::client::Exception> GetNative()
        {
          std::shared_ptr<apache::geode::client::Exception> cause;
          if (this->InnerException != nullptr) {
            cause = GeodeException::GetNative(this->InnerException);
          }
          return std::make_shared<apache::geode::client::Exception>(
            marshal_as<std::string>(this->Message + ": " + this->StackTrace)
              + (cause ? cause->getMessage() : ""));
        }

        /// <summary>
        /// Throws the C++ native exception object for the given .NET exception.
        /// </summary>
        /// <remarks>
        /// This method is to handle conversion of managed exceptions to
        /// C++ exception for those thrown by managed callbacks.
        /// For non-Geode .NET exceptions we wrap it inside the generic
        /// <c>GeodeException</c> with a special prefix in message.
        /// While converting the exception back from C++ to .NET if the
        /// prefix is found in the message, then it tries to construct
        /// the original exception by reflection on the name of exception
        /// contained in the message. Note that in this process the
        /// original stacktrace is appended to the message of the exception.
        /// </remarks>
        inline static void ThrowNative(Exception^ ex)
        {
          if (ex != nullptr) {
            std::shared_ptr<apache::geode::client::Exception> cause;
            if (ex->InnerException != nullptr) {
              cause = GeodeException::GetNative(ex->InnerException);
            }
            throw apache::geode::client::Exception(
              marshal_as<std::string>(MgSysExPrefix + ex->ToString())
                + (cause ? cause->getMessage() : ""));
          }
        }

        /// <summary>
        /// Throws the C++ native exception object for this managed
        /// <c>GeodeException</c>.
        /// </summary>
        inline void ThrowNative()
        {
          throw GetNative();
        }


      public:

        /// <summary>
        /// Default constructor.
        /// </summary>
        inline GeodeException( )
          : Exception( ) { }

        /// <summary>
        /// Constructor to create an exception object with the given message.
        /// </summary>
        /// <param name="message">The exception message.</param>
        inline GeodeException( String^ message )
          : Exception( message ) { }

        /// <summary>
        /// Constructor to create an exception object with the given message
        /// and with the given inner exception.
        /// </summary>
        /// <param name="message">The exception message.</param>
        /// <param name="innerException">The inner exception object.</param>
        inline GeodeException( String^ message, System::Exception^ innerException )
          : Exception( message, innerException ) { }

      protected:

        /// <summary>
        /// Initializes a new instance of the <c>GeodeException</c> class with
        /// serialized data.
        /// This allows deserialization of this exception in .NET remoting.
        /// </summary>
        /// <param name="info">
        /// holds the serialized object data about
        /// the exception being thrown
        /// </param>
        /// <param name="context">
        /// contains contextual information about
        /// the source or destination
        /// </param>
        inline GeodeException( SerializationInfo^ info, StreamingContext context )
          : Exception( info, context ) { }
      };

/// Handle geode exceptions from native layer and convert to managed
/// exceptions.
#define _GF_MG_EXCEPTION_TRY2        \
      try {
#define _GF_MG_EXCEPTION_CATCH_ALL2  \
      } \
      catch (const apache::geode::client::Exception& ex) { \
      throw Apache::Geode::Client::GeodeException::Get(ex); \
      } \
      catch (System::AccessViolationException^ ex) { \
        throw ex; \
      }


/// Creates a class <c>x</c> named for each exception <c>y</c>.
#define _GF_MG_EXCEPTION_DEF4(x,y) \
      [Serializable] \
      public ref class x: public GeodeException \
      { \
      public: \
      \
        /** <summary>Default constructor</summary> */ \
        x( ) \
          : GeodeException( ) { } \
        \
        /** <summary>
         *  Constructor to create an exception object with the given message.
         *  </summary>
         *  <param name="message">The exception message.</param>
         */ \
        x( String^ message ) \
          : GeodeException( message ) { } \
        \
        /** <summary>
         *  Constructor to create an exception object with the given message
         *  and with the given inner exception.
         *  </summary>
         *  <param name="message">The exception message.</param>
         *  <param name="innerException">The inner exception object.</param>
         */ \
        x( String^ message, System::Exception^ innerException ) \
          : GeodeException( message, innerException ) { } \
        \
      protected: \
      \
        /** <summary>
         *  Initializes a new instance of the class with serialized data.
         *  This allows deserialization of this exception in .NET remoting.
         *  </summary>
         *  <param name="info">
         *  holds the serialized object data about the exception being thrown
         *  </param>
         *  <param name="context">
         *  contains contextual information about the source or destination
         *  </param>
         */ \
        x( SerializationInfo^ info, StreamingContext context ) \
          : GeodeException( info, context ) { } \
      \
      internal: \
        x(const apache::geode::client::y& nativeEx) \
          : GeodeException(marshal_as<String^>(nativeEx.getMessage()), \
              gcnew GeodeException(GeodeException::GetStackTrace( \
                nativeEx))) { } \
        \
        x(const apache::geode::client::y& nativeEx, Exception^ innerException) \
          : GeodeException(marshal_as<String^>(nativeEx.getMessage()), \
              innerException) { } \
        \
        static GeodeException^ Create(const apache::geode::client::Exception& ex, \
            Exception^ innerException) \
        { \
          const apache::geode::client::y* nativeEx = dynamic_cast<const apache::geode::client::y*>( &ex ); \
          if (nativeEx != nullptr) { \
            if (innerException == nullptr) { \
              return gcnew x(*nativeEx); \
            } \
            else { \
              return gcnew x(*nativeEx, innerException); \
            } \
          } \
          return nullptr; \
        } \
        virtual std::shared_ptr<apache::geode::client::Exception> GetNative() override \
        { \
          auto message = marshal_as<std::string>(this->Message + ": " + this->StackTrace); \
          if (this->InnerException != nullptr) { \
            auto cause = GeodeException::GetNative(this->InnerException); \
            message += "Caused by: " + cause->getMessage(); \
          } \
          return std::make_shared<apache::geode::client::y>(message); \
        } \
      }

/// Creates a class named for each exception <c>x</c>.
#define _GF_MG_EXCEPTION_DEF3(x) _GF_MG_EXCEPTION_DEF4(x,x)


      // For all the native Geode C++ exceptions, a corresponding definition
      // should be added below *AND* it should also be added to the static array
      // in ExceptionTypesMN.cpp using _GF_MG_EXCEPTION_ADD3( x )

      /// <summary>
      /// A geode assertion exception.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( AssertionException );

      /// <summary>
      /// Thrown when an argument to a method is illegal.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( IllegalArgumentException );

      /// <summary>
      /// Thrown when the state of cache is manipulated to be illegal.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( IllegalStateException );

      /// <summary>
      /// Thrown when an attempt is made to create an existing cache.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheExistsException );

      /// <summary>
      /// Thrown when the cache xml is incorrect.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheXmlException );

      /// <summary>
      /// Thrown when a timout occurs.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( TimeoutException );

      /// <summary>
      /// Thrown when the cache writer aborts the operation.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheWriterException );

      /// <summary>
      /// Thrown when the cache listener throws an exception.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheListenerException );

      /// <summary>
      /// Thrown when an attempt is made to create an existing region.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( RegionExistsException );

      /// <summary>
      /// Thrown when an operation is attempted on a closed cache.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheClosedException );

      /// <summary>
      /// Thrown when lease of cache proxy has expired.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( LeaseExpiredException );

      /// <summary>
      /// Thrown when the cache loader aborts the operation.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheLoaderException );

      /// <summary>
      /// Thrown when an operation is attempted on a destroyed region.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( RegionDestroyedException );

      /// <summary>
      /// Thrown when an operation is attempted on a destroyed entry.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( EntryDestroyedException );

      /// <summary>
      /// Thrown when the connecting target is not running.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( NoSystemException );

      /// <summary>
      /// Thrown when an attempt is made to connect to
      /// DistributedSystem second time.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( AlreadyConnectedException );

      /// <summary>
      /// Thrown when a non-existing file is accessed.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( FileNotFoundException );

      /// <summary>
      /// Thrown when an operation is interrupted.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( InterruptedException );

      /// <summary>
      /// Thrown when an operation unsupported by the
      /// current configuration is attempted.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( UnsupportedOperationException );

      /// <summary>
      /// Thrown when statistics are invoked for a region where
      /// they are disabled.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( StatisticsDisabledException );

      /// <summary>
      /// Thrown when a concurrent operation fails.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( ConcurrentModificationException );

      /// <summary>
      /// An unknown exception occurred.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( UnknownException );

      /// <summary>
      /// Thrown when a cast operation fails.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( ClassCastException );

      /// <summary>
      /// Thrown when an operation is attempted on a non-existent entry.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( EntryNotFoundException );

      /// <summary>
      /// Thrown when there is an input/output error.
      /// </summary>
      _GF_MG_EXCEPTION_DEF4( GeodeIOException, GeodeIOException );

      /// <summary>
      /// Thrown when geode configuration file is incorrect.
      /// </summary>
      _GF_MG_EXCEPTION_DEF4( GeodeConfigException, GeodeConfigException );

      /// <summary>
      /// Thrown when a null argument is provided to a method
      /// where it is expected to be non-null.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( NullPointerException );

      /// <summary>
      /// Thrown when attempt is made to create an existing entry.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( EntryExistsException );

      /// <summary>
      /// Thrown when an operation is attempted before connecting
      /// to the distributed system.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( NotConnectedException );

      /// <summary>
      /// Thrown when there is an error in the cache proxy.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheProxyException );

      /// <summary>
      /// Thrown when the system cannot allocate any more memory.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( OutOfMemoryException );

      /// <summary>
      /// Thrown when an attempt is made to release a lock not
      /// owned by the thread.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( NotOwnerException );

      /// <summary>
      /// Thrown when a region is created in an incorrect scope.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( WrongRegionScopeException );

      /// <summary>
      /// Thrown when the internal buffer size is exceeded.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( BufferSizeExceededException );

      /// <summary>
      /// Thrown when a region creation operation fails.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( RegionCreationFailedException );

      /// <summary>
      /// Thrown when there is a fatal internal exception in Geode.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( FatalInternalException );

      /// <summary>
      /// Thrown by the persistence manager when a write
      /// fails due to disk failure.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( DiskFailureException );

      /// <summary>
      /// Thrown by the persistence manager when the data
      /// to be read from disk is corrupt.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( DiskCorruptException );

      /// <summary>
      /// Thrown when persistence manager fails to initialize.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( InitFailedException );

      /// <summary>
      /// Thrown when persistence manager fails to close properly.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( ShutdownFailedException );

      /// <summary>
      /// Thrown when an exception occurs on the cache server.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CacheServerException );

      /// <summary>
      /// Thrown when bound of array/vector etc. is exceeded.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( OutOfRangeException );

      /// <summary>
      /// Thrown when query exception occurs at the server.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( QueryException );

      /// <summary>
      /// Thrown when an unknown message is received from the server.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( MessageException );

      /// <summary>
      /// Thrown when a client operation is not authorized on the server.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( NotAuthorizedException );

      /// <summary>
      /// Thrown when authentication to the server fails.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( AuthenticationFailedException );

      /// <summary>
      /// Thrown when credentials are not provided to a server which expects them.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( AuthenticationRequiredException );

      /// <summary>
      /// Thrown when a duplicate durable client id is provided to the server.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( DuplicateDurableClientException );

      /// <summary>
      /// Thrown when a client is unable to contact any locators.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( NoAvailableLocatorsException );

      /// <summary>
      /// Thrown when all connections in a pool are in use..
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( AllConnectionsInUseException );

      /// <summary>
      /// Thrown when cq is invalid
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CqInvalidException );

      /// <summary>
      /// Thrown when function execution failed
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( FunctionExecutionException );

      /// <summary>
      /// Thrown during continuous query execution time.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CqException );

      /// <summary>
      /// Thrown if the Cq on which the operaion performed is closed
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CqClosedException );

      /// <summary>
      /// Thrown if the Cq Query failed
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CqQueryException );

      /// <summary>
      /// Thrown if a Cq by this name already exists on this client
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CqExistsException );

      _GF_MG_EXCEPTION_DEF3( InvalidDeltaException );

      /// <summary>
      /// Thrown if a Key is not present in the region.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( KeyNotFoundException );

      /// <summary>
      /// Thrown if commit fails.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( CommitConflictException );

	        /// <summary>
      /// Thrown if transaction delegate went down.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( TransactionDataNodeHasDepartedException );

	        /// <summary>
      /// Thrown if commit rebalance happens during a transaction.
      /// </summary>
      _GF_MG_EXCEPTION_DEF3( TransactionDataRebalancedException );
    }  // namespace Client
  }  // namespace Geode
}  // namespace Apache

