/*
 * 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



#pragma warning(disable:4091)
#include <msclr/lock.h>

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Threading;
//using namespace System::WeakReference;

namespace Apache
{
  namespace Geode
  {
    namespace Client
    {

      namespace Internal
      {
        public ref class EntryNode
        {
          System::WeakReference^     m_weakValue;
          EntryNode^  m_nextEntryNode;

        public:
          EntryNode(Object^ val, EntryNode^ nextNode)
          {
            m_weakValue = gcnew WeakReference(val);
            m_nextEntryNode = nextNode;
          }

          bool isValEquals(Object^ other)
          {
            if( other == nullptr)
              return false;
            Object^ val = m_weakValue->Target;

            if(val != nullptr)
            {
              if(val->GetHashCode() == other->GetHashCode() && val->Equals(other))
                return true;
            }

            return false;
          }

          property Object^ Value
          {
            Object^ get()
            {
              return m_weakValue->Target;
            }
            void set(Object^ val)
            {
              m_weakValue = gcnew WeakReference(val);
            }
          }

          property EntryNode^ NextNode
          {
            EntryNode^ get()
            {
              return m_nextEntryNode;
            }
          }

        };

        public ref class MapEntry
        {
          EntryNode^ m_entryNode;

        public:
          MapEntry(Object^ newVal)
          {
            m_entryNode = gcnew EntryNode(newVal, nullptr);
          }

          void AddEntry(Object^ newVal)
          {
            //lock at entry level
            msclr::lock lockInstance(this);
            //1. see if hashCode and equals matches if yes then replace.
            //2. store node if val is null(GC collected ) replace that node(ie reuse)
            //3. create new node
            
            EntryNode^ startNode = m_entryNode;
            EntryNode^ nullNode = nullptr; //to replace

            while(startNode != nullptr)
            {
              if(startNode->isValEquals(newVal))
              {
                //replace value from new value;; will we ever come acrosss here ???
                startNode->Value = newVal;
                return;
              }
              else if(nullNode == nullptr && startNode->Value == nullptr)
              {
                //reuse the node once it gced
                nullNode = startNode;
              }
              startNode = startNode->NextNode;
            }

            if(nullNode != nullptr)
            {
              nullNode->Value = newVal;
            }
            else
            {
              //create new node
              EntryNode^ newNode = gcnew EntryNode(newVal, m_entryNode);
              m_entryNode = newNode;//append new node as put can happen immediately for this 
            }
          }

          Object^ Get(Object^ val)
          {
            EntryNode^ startNode = m_entryNode;

            while(startNode != nullptr)
            {
              if(startNode->isValEquals(val))
              {
                return startNode->Value;
              }
              startNode = startNode->NextNode;
            }            

            return nullptr;
          }
        };

        public ref class WeakHashMap
        {
          //not thread safe 
          Dictionary<int, MapEntry^>^ m_dictionary;
          ReaderWriterLock^ m_readerWriterLock;

        public:
          WeakHashMap()
          {
            m_dictionary = gcnew Dictionary<int, MapEntry^>(1000);
            m_readerWriterLock = gcnew ReaderWriterLock();
          }

					property int Count
					{
						int get(){return m_dictionary->Count;}
					}

          void Put(Object^ val)
          {
            int hash = val->GetHashCode();

            MapEntry^ ret = GetEntry(val);    

            if(ret != nullptr)
            {
              // this will take lock at entry level
              //so once entry is created it will remain there and we don't need to take writer lock
              ret->AddEntry(val);
            }
            else
            {
              try
              {
                m_readerWriterLock->AcquireWriterLock(-1);
                
                m_dictionary->TryGetValue(hash,ret);

                if(ret != nullptr)
                {
                  ret->AddEntry(val);
                }
                else
                {
                  ret = gcnew MapEntry(val);
                  m_dictionary[hash] = ret;
                }
              }finally
              {
                m_readerWriterLock->ReleaseWriterLock();
              }
            }
          }

          MapEntry^ GetEntry(Object^ val)
          {
            int hash = val->GetHashCode();

            MapEntry^ ret = nullptr;

            try
            {
              m_readerWriterLock->AcquireReaderLock(-1);//infinite timeout
              m_dictionary->TryGetValue(hash,ret);
            }finally
            {
              m_readerWriterLock->ReleaseReaderLock();
            }
            return ret; 
          }

          Object^ Get(Object^ val)
          {
            if(m_dictionary->Count > 0)
            {
              int hash = val->GetHashCode();

              MapEntry^ ret = nullptr;

              try
              {
                m_readerWriterLock->AcquireReaderLock(-1);//infinite timeout
                m_dictionary->TryGetValue(hash,ret);

                if(ret != nullptr)
                {
                  return ret->Get(val);
                }
              }finally
              {
                m_readerWriterLock->ReleaseReaderLock();
              }
            }
            return nullptr;
          }

          void Clear()
          {
            m_dictionary->Clear();
          }
        };
    }  // namespace Client
  }  // namespace Geode
}  // namespace Apache

}
