/*
 * 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 "geode_defs.hpp"
#include "begin_native.hpp"
#include <geode/CacheableBuiltins.hpp>
#include "end_native.hpp"

#include "Serializable.hpp"
#include "ExceptionTypes.hpp"
#include "impl/PdxInstanceImpl.hpp"
#include "native_shared_ptr.hpp"
#include "native_unique_ptr.hpp"

using namespace System;
using namespace System::Collections::Generic;
#pragma managed

namespace Apache
{
  namespace Geode
  {
    namespace Client
    {

      namespace Internal
      {
        /// <summary>
        /// A mutable <c>ICacheableKey</c> hash set wrapper that can serve as
        /// a distributable object for caching.
        /// </summary>
        template <int8_t TYPEID, typename HSTYPE>
        public ref class CacheableHashSetType
          : public IDataSerializablePrimitive,
            public Serializable,
            public ICollection<Object^>
        {
        public:

          virtual void ToData(DataOutput^ output)
          {
            output->WriteArrayLen(this->Count);

            auto set = dynamic_cast<HSTYPE*>(m_nativeptr->get());
            for (const auto& iter : *set) {
              auto key = TypeRegistry::GetManagedValueGeneric<Object^>(iter);
              output->WriteObject(key);
            }

            GC::KeepAlive(this);
          }

          virtual void FromData(DataInput^ input)
          {
            int len = input->ReadArrayLen();
            if (len > 0)
            {
              for (int i = 0; i < len; i++)
              {
                Object^ key = (input->ReadObject());
                this->Add(key);
              }
            }
          }

          property System::UInt64 ObjectSize
          {
            System::UInt64 get() override
            {
              System::UInt64 size = 0;
              for each (Object^ key in this) {
                if (key != nullptr)
                  //size += key->ObjectSize; 
                  //TODO:: how should we do this now
                  size += 1;
              }
              return size;
            }
          }

          int GetHashCode() override
          {
            IEnumerator<Object^>^ ie = GetEnumerator();

            int h = 0;
            while (ie->MoveNext() == true)
            {
              h = h + PdxInstanceImpl::deepArrayHashCode(ie->Current);
            }
            return h;
          }

          bool Equals(Object^ other) override
          {
            if (other == nullptr)
              return false;

            CacheableHashSetType^ otherCHST = dynamic_cast<CacheableHashSetType^>(other);

            if (otherCHST == nullptr)
              return false;

            if (Count != otherCHST->Count)
              return false;

            IEnumerator<Object^>^ ie = GetEnumerator();

            while (ie->MoveNext() == true)
            {
              if (otherCHST->Contains(ie->Current))
                return true;
              else
                return false;
            }

            return true;
          }

          /// <summary>
          /// Enumerator for <c>CacheableHashSet</c> class.
          /// </summary>
          ref class Enumerator sealed
            : public IEnumerator<Object^>
          {
          public:
            // Region: IEnumerator<ICacheableKey^> Members

            /// <summary>
            /// Gets the element in the collection at the current
            /// position of the enumerator.
            /// </summary>
            /// <returns>
            /// The element in the collection at the current position
            /// of the enumerator.
            /// </returns>
            property Object^ Current
            {
              virtual Object^ get() =
                IEnumerator<Object^>::Current::get
                {
                  if (!m_started) {
                    throw gcnew System::InvalidOperationException(
                      "Call MoveNext first.");
                  }
                auto ret = TypeRegistry::GetManagedValueGeneric<Object^>(*(*(m_nativeptr->get())));
                GC::KeepAlive(this);
                return ret;
              }
            }

            // End Region: IEnumerator<ICacheableKey^> Members

            // Region: IEnumerator Members

            /// <summary>
            /// Advances the enumerator to the next element of the collection.
            /// </summary>
            /// <returns>
            /// true if the enumerator was successfully advanced to the next
            /// element; false if the enumerator has passed the end of
            /// the collection.
            /// </returns>
            virtual bool MoveNext()
            {
              auto nptr = m_nativeptr->get();
              bool isEnd = dynamic_cast<HSTYPE*>(m_set->m_nativeptr->get())->end() == *nptr;
              if (!m_started) {
                m_started = true;
              }
              else {
                if (!isEnd) {
                  (*nptr)++;
                  isEnd = dynamic_cast<HSTYPE*>(m_set->m_nativeptr->get())->end() == *nptr;
                }
              }
              GC::KeepAlive(this);
              return !isEnd;
            }

            /// <summary>
            /// Sets the enumerator to its initial position, which is before
            /// the first element in the collection.
            /// </summary>
            virtual void Reset()
            {
              try
              {
                m_nativeptr = gcnew native_unique_ptr<typename HSTYPE::iterator>(
                    std::make_unique<typename HSTYPE::iterator>(
                    dynamic_cast<HSTYPE*>(m_set->m_nativeptr->get())->begin()));
              }
              finally
              {
                GC::KeepAlive(m_nativeptr);
              }
              m_started = false;
            }

            !Enumerator() {}
            ~Enumerator() {}

            // End Region: IEnumerator Members

          internal:
            /// <summary>
            /// Internal constructor to wrap a native object pointer
            /// </summary>
            /// <param name="nativeptr">The native object pointer</param>
            inline Enumerator(CacheableHashSetType<TYPEID, HSTYPE>^ set)
                              : m_set(set) {
              Reset();
            }

          private:
            // Region: IEnumerator Members

            /// <summary>
            /// Gets the current element in the collection.
            /// </summary>
            /// <returns>
            ///     The current element in the collection.
            /// </returns>
            /// <exception cref="System.InvalidOperationException">
            /// The enumerator is positioned before the first element of
            /// the collection or after the last element.
            /// </exception>
            property Object^ ICurrent
            {
              virtual Object^ get() sealed =
                System::Collections::IEnumerator::Current::get
              {
                return Current;
              }
            }

            // End Region: IEnumerator Members

            bool m_started;

            CacheableHashSetType<TYPEID, HSTYPE>^ m_set;

            native_unique_ptr<typename HSTYPE::iterator>^ m_nativeptr;
          };

          property int8_t DsCode
          {
            virtual int8_t get()
            {
              return TYPEID;
            }
          }

          /// <summary>
          /// Get the largest possible size of the <c>CacheableHashSet</c>.
          /// </summary>
          property System::Int32 MaxSize
          {
            inline System::Int32 get()
            {
              try
              {
                return static_cast<int>(dynamic_cast<HSTYPE*>(m_nativeptr->get())->max_size());
              }
              finally
              {
                GC::KeepAlive(m_nativeptr);
              }
            }
          }

          /// <summary>
          /// True if the <c>CacheableHashSet</c>'s size is 0.
          /// </summary>
          property bool IsEmpty
          {
            inline bool get()
            {
              try
              {
                return dynamic_cast<HSTYPE*>(m_nativeptr->get())->empty();
              }
              finally
              {
                GC::KeepAlive(m_nativeptr);
              }
            }
          }

          /// <summary>
          /// Get the number of buckets used by the HashSet.
          /// </summary>
          property System::Int32 BucketCount
          {
            inline System::Int32 get()
            {
              try
              {
                return static_cast<int>(dynamic_cast<HSTYPE*>(m_nativeptr->get())->bucket_count());
              }
              finally
              {
                GC::KeepAlive(m_nativeptr);
              }
            }
          }

          /// <summary>
          /// Increases the bucket count to at least <c>size</c> elements.
          /// </summary>
          /// <param name="size">The new size of the HashSet.</param>
          virtual void Resize(System::Int32 size) sealed
          {
            try
            {
              dynamic_cast<HSTYPE*>(m_nativeptr->get())->reserve(size);
            }
            finally
            {
              GC::KeepAlive(m_nativeptr);
            }
          }

          /// <summary>
          /// Swap the contents of this <c>CacheableHashSet</c>
          /// with the given one.
          /// </summary>
          /// <param name="other">
          /// The other CacheableHashSet to use for swapping.
          /// </param>
          virtual void Swap(CacheableHashSetType<TYPEID, HSTYPE>^ other) sealed
          {
            try
            {
              if (other != nullptr) {
                dynamic_cast<HSTYPE*>(m_nativeptr->get())->swap(
                  *dynamic_cast<HSTYPE*>(other->m_nativeptr->get()));
              }
            }
            finally
            {
              GC::KeepAlive(m_nativeptr);
              GC::KeepAlive(other->m_nativeptr);
            }
          }

          // Region: ICollection<ICacheableKey^> Members

          /// <summary>
          /// Adds an item to the <c>CacheableHashSet</c>.
          /// </summary>
          /// <param name="item">
          /// The object to add to the collection.
          /// </param>
          virtual void Add(Object^ item)
          {
            _GF_MG_EXCEPTION_TRY2/* due to auto replace */

            try
            {
              dynamic_cast<HSTYPE*>(m_nativeptr->get())->insert(Serializable::GetUnmanagedValueGeneric(item));
            }
            finally
            {
              GC::KeepAlive(m_nativeptr);
            }

            _GF_MG_EXCEPTION_CATCH_ALL2/* due to auto replace */
          }

          /// <summary>
          /// Removes all items from the <c>CacheableHashSet</c>.
          /// </summary>
          virtual void Clear()
          {
            try
            {
              dynamic_cast<HSTYPE*>(m_nativeptr->get())->clear();
            }
            finally
            {
              GC::KeepAlive(m_nativeptr);
            }
          }

          /// <summary>
          /// Determines whether the <c>CacheableHashSet</c> contains
          /// a specific value.
          /// </summary>
          /// <param name="item">
          /// The object to locate in the <c>CacheableHashSet</c>.
          /// </param>
          /// <returns>
          /// true if item is found in the <c>CacheableHashSet</c>;
          /// otherwise false.
          /// </returns>
          virtual bool Contains(Object^ item)
          {
            try
            {
              return dynamic_cast<HSTYPE*>(m_nativeptr->get())->find(Serializable::GetUnmanagedValueGeneric(item)) != dynamic_cast<HSTYPE*>(m_nativeptr->get())->end();
            }
            finally
            {
              GC::KeepAlive(m_nativeptr);
            }
          }

          /// <summary>
          /// Copies the elements of the <c>CacheableHashSet</c> to an
          /// <c>System.Array</c>, starting at a particular
          /// <c>System.Array</c> index.
          /// </summary>
          /// <param name="array">
          /// The one-dimensional System.Array that is the destination of the
          /// elements copied from <c>CacheableHashSet</c>. The
          /// <c>System.Array</c> must have zero-based indexing.
          /// </param>
          /// <param name="arrayIndex">
          /// The zero-based index in array at which copying begins.
          /// </param>
          /// <exception cref="IllegalArgumentException">
          /// arrayIndex is less than 0 or array is null.
          /// </exception>
          /// <exception cref="OutOfRangeException">
          /// arrayIndex is equal to or greater than the length of array.
          /// -or-The number of elements in the source <c>CacheableHashSet</c>
          /// is greater than the available space from arrayIndex to the end
          /// of the destination array.
          /// </exception>
          virtual void CopyTo(array<Object^>^ array, System::Int32 arrayIndex)
          {
            if (array == nullptr || arrayIndex < 0) {
              throw gcnew IllegalArgumentException("CacheableHashSet.CopyTo():"
                                                   " array is null or array index is less than zero");
            }

            auto set = dynamic_cast<HSTYPE*>(m_nativeptr->get());
            System::Int32 index = arrayIndex;

            if (arrayIndex >= array->Length ||
                array->Length < (arrayIndex + (System::Int32)set->size())) {
              throw gcnew OutOfRangeException("CacheableHashSet.CopyTo():"
                                              " array index is beyond the HashSet or length of given "
                                              "array is less than that required to copy all the "
                                              "elements from HashSet");
            }
            for (const auto& iter : *set) {
              array[index++] = TypeRegistry::GetManagedValueGeneric<Object^>(iter);
            }

            GC::KeepAlive(m_nativeptr);
          }

          /// <summary>
          /// Gets the number of elements contained in the
          /// <c>CacheableHashSet</c>.
          /// </summary>
          virtual property System::Int32 Count
          {
            virtual System::Int32 get()
            {
              try
              {
                return static_cast<int>(dynamic_cast<HSTYPE*>(m_nativeptr->get())->size());
              }
              finally
              {
                GC::KeepAlive(m_nativeptr);
              }
            }
          }

          /// <summary>
          /// Removes the first occurrence of a specific object from the
          /// <c>CacheableHashSet</c>.
          /// </summary>
          /// <param name="item">
          /// The object to remove from the <c>CacheableHashSet</c>.
          /// </param>
          /// <returns>
          /// true if item was successfully removed from the
          /// <c>CacheableHashSet</c>; otherwise, false. This method also
          /// returns false if item is not found in the original
          /// <c>CacheableHashSet</c>.
          /// </returns>
          virtual bool Remove(Object^ item)
          {
            try
            {
              return (dynamic_cast<HSTYPE*>(m_nativeptr->get())->erase(Serializable::GetUnmanagedValueGeneric(item)) > 0);
            }
            finally
            {
              GC::KeepAlive(m_nativeptr);
            }
          }

          /// <summary>
          /// Gets a value indicating whether the collection is read-only.
          /// </summary>
          /// <returns>
          /// always false for <c>CacheableHashSet</c>
          /// </returns>
          virtual property bool IsReadOnly
          {
            virtual bool get()
            {
              return false;
            }
          }

          // End Region: ICollection<ICacheableKey^> Members

          // Region: IEnumerable<ICacheableKey^> Members

          /// <summary>
          /// Returns an enumerator that iterates through the
          /// <c>CacheableHashSet</c>.
          /// </summary>
          /// <returns>
          /// A <c>System.Collections.Generic.IEnumerator</c> that
          /// can be used to iterate through the <c>CacheableHashSet</c>.
          /// </returns>
          virtual IEnumerator<Object^>^ GetEnumerator()
          {
            return gcnew Enumerator(this);
          }

          // End Region: IEnumerable<ICacheableKey^> Members

        internal:
          /// <summary>
          /// Factory function to register wrapper
          /// </summary>
          static ISerializable^ Create(apache::geode::client::Serializable* obj)
          {
            return (obj != NULL ?
                    gcnew CacheableHashSetType<TYPEID, HSTYPE>(obj) : nullptr);
          }

        private:
          // Region: IEnumerable Members

          /// <summary>
          /// Returns an enumerator that iterates through a collection.
          /// </summary>
          /// <returns>
          /// An <c>System.Collections.IEnumerator</c> object that can be used
          /// to iterate through the collection.
          /// </returns>
          virtual System::Collections::IEnumerator^ GetIEnumerator() sealed =
            System::Collections::IEnumerable::GetEnumerator
          {
            return GetEnumerator();
          }

            // End Region: IEnumerable Members

        protected:
          /// <summary>
          /// Private constructor to wrap a native object pointer
          /// </summary>
          /// <param name="nativeptr">The native object pointer</param>
          inline CacheableHashSetType<TYPEID, HSTYPE>(std::shared_ptr<apache::geode::client::Serializable> nativeptr)
            : Serializable(nativeptr) { }

          /// <summary>
          /// Allocates a new empty instance.
          /// </summary>
          inline CacheableHashSetType<TYPEID, HSTYPE>()
            : Serializable(HSTYPE::createDeserializable())
          { }

          /// <summary>
          /// Allocates a new empty instance with given initial size.
          /// </summary>
          /// <param name="size">The initial size of the HashSet.</param>
          inline CacheableHashSetType<TYPEID, HSTYPE>(System::Int32 size)
            : Serializable(HSTYPE::create(size))
          { }
        };
      }

#define _GFCLI_CACHEABLEHASHSET_DEF_GENERIC(m, HSTYPE)                               \
	public ref class m : public Internal::CacheableHashSetType<static_cast<int8_t>(native::internal::DSCode::m), HSTYPE>      \
            {                                                                       \
      public:                                                                 \
        /** <summary>
      *  Allocates a new empty instance.
      *  </summary>
      */                                                                   \
      inline m()                                                            \
      : Internal::CacheableHashSetType<static_cast<int8_t>(native::internal::DSCode::m), HSTYPE>() {}                      \
      \
      /** <summary>
       *  Allocates a new instance with the given size.
       *  </summary>
       *  <param name="size">the initial size of the new instance</param>
       */                                                                   \
       inline m(System::Int32 size)                                                 \
       : Internal::CacheableHashSetType<static_cast<int8_t>(native::internal::DSCode::m), HSTYPE>(size) {}                  \
       \
       /** <summary>
        *  Static function to create a new empty instance.
        *  </summary>
        */                                                                   \
        inline static m^ Create()                                             \
      {                                                                     \
      return gcnew m();                                                   \
      }                                                                     \
      \
      /** <summary>
       *  Static function to create a new instance with the given size.
       *  </summary>
       */                                                                   \
       inline static m^ Create(System::Int32 size)                                  \
      {                                                                     \
      return gcnew m(size);                                               \
      }                                                                     \
      \
      /* <summary>
       * Factory function to register this class.
       * </summary>
       */                                                                   \
       static ISerializable^ CreateDeserializable()                        \
      {                                                                     \
      return gcnew m();                                                   \
      }                                                                     \
      \
            internal:                                                               \
              static ISerializable^ Create(std::shared_ptr<apache::geode::client::Serializable> obj)            \
      {                                                                     \
      return gcnew m(obj);                                                \
      }                                                                     \
      \
            private:                                                                \
              inline m(std::shared_ptr<apache::geode::client::Serializable> nativeptr)                            \
              : Internal::CacheableHashSetType<static_cast<int8_t>(native::internal::DSCode::m), HSTYPE>(nativeptr) { }             \
      };

      /// <summary>
      /// A mutable <c>ICacheableKey</c> hash set wrapper that can serve as
      /// a distributable object for caching.
      /// </summary>
      _GFCLI_CACHEABLEHASHSET_DEF_GENERIC(CacheableHashSet,
                                          apache::geode::client::CacheableHashSet);

      /// <summary>
      /// A mutable <c>ICacheableKey</c> hash set wrapper that can serve as
      /// a distributable object for caching. This is provided for compability
      /// with java side though is functionally identical to
      /// <c>CacheableHashSet</c> i.e. does not provide the linked semantics of
      /// java <c>LinkedHashSet</c>.
      /// </summary>
      _GFCLI_CACHEABLEHASHSET_DEF_GENERIC(CacheableLinkedHashSet,
                                          apache::geode::client::CacheableLinkedHashSet);
    }  // namespace Client
  }  // namespace Geode
}  // namespace Apache

