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

#include "ignite/impl/handle_registry.h"

using namespace ignite::common::concurrent;

namespace ignite
{
    namespace impl
    {
        HandleRegistrySegment::HandleRegistrySegment() : 
            map(),
            mux()
        {
            // No-op.
        }

        HandleRegistrySegment::~HandleRegistrySegment()
        {
            // No-op.
        }

        SharedPointer<void> HandleRegistrySegment::Get(int64_t hnd)
        {
            typedef std::map<int64_t, SharedPointer<void> > Map;

            CsLockGuard guard(mux);

            Map::const_iterator it = map.find(hnd);
            if (it == map.end())
                return SharedPointer<void>();

            return it->second;
        }

        void HandleRegistrySegment::Put(int64_t hnd, const SharedPointer<void>& entry)
        {
            CsLockGuard guard(mux);

            map[hnd] = entry;
        }

        void HandleRegistrySegment::Remove(int64_t hnd)
        {
            CsLockGuard guard(mux);

            map.erase(hnd);
        }

        void HandleRegistrySegment::Clear()
        {
            CsLockGuard guard(mux);

            map.clear();
        }

        HandleRegistry::HandleRegistry(int32_t fastCap, int32_t slowSegmentCnt) :
            fastCap(fastCap),
            fastCtr(0),
            fast(new SharedPointer<void>[fastCap]),
            slowSegmentCnt(slowSegmentCnt),
            slowCtr(fastCap),
            slow(new HandleRegistrySegment*[slowSegmentCnt]),
            closed(0)
        {
            for (int32_t i = 0; i < fastCap; i++)
                fast[i] = SharedPointer<void>();

            for (int32_t i = 0; i < slowSegmentCnt; i++)
                slow[i] = new HandleRegistrySegment();

            Memory::Fence();
        }

        HandleRegistry::~HandleRegistry()
        {
            Close();

            delete[] fast;

            for (int i = 0; i < slowSegmentCnt; i++)
                delete slow[i];

            delete[] slow;
        }

        int64_t HandleRegistry::Allocate(const SharedPointer<void>& target)
        {
            return Allocate0(target, false, false);
        }

        int64_t HandleRegistry::AllocateCritical(const SharedPointer<void>& target)
        {
            return Allocate0(target, true, false);
        }

        int64_t HandleRegistry::AllocateSafe(const SharedPointer<void>& target)
        {
            return Allocate0(target, false, true);
        }

        int64_t HandleRegistry::AllocateCriticalSafe(const SharedPointer<void>& target)
        {
            return Allocate0(target, true, true);
        }

        void HandleRegistry::Release(int64_t hnd)
        {
            if (hnd < 0)
                return;
            else if (hnd < fastCap)
                fast[static_cast<int32_t>(hnd)] = SharedPointer<void>();
            else
            {
                HandleRegistrySegment* segment = slow[hnd % slowSegmentCnt];

                segment->Remove(hnd);
            }

            Memory::Fence();
        }

        SharedPointer<void> HandleRegistry::Get(int64_t hnd)
        {
            Memory::Fence();

            if (hnd < fastCap)
                return fast[static_cast<int32_t>(hnd)];
            else
            {
                HandleRegistrySegment* segment = slow[hnd % slowSegmentCnt];

                return segment->Get(hnd);
            }
        }

        void HandleRegistry::Close()
        {
            if (Atomics::CompareAndSet32(&closed, 0, 1))
            {
                // Cleanup fast-path handles.
                for (int32_t i = 0; i < fastCap; i++)
                    fast[i] = SharedPointer<void>();

                // Cleanup slow-path handles.
                for (int32_t i = 0; i < slowSegmentCnt; i++)
                    slow[i]->Clear();
            }
        }

        int64_t HandleRegistry::Allocate0(const SharedPointer<void>& target, bool critical, bool safe)
        {
            // Check closed state.
            Memory::Fence();

            if (closed == 1)
                return -1;

            // Try allocating entry on critical path.
            if (critical)
            {
                if (fastCtr < fastCap)
                {
                    int32_t fastIdx = Atomics::IncrementAndGet32(&fastCtr) - 1;

                    if (fastIdx < fastCap)
                    {
                        fast[fastIdx] = target;

                        // Double-check for closed state if safe mode is on.
                        Memory::Fence();

                        if (safe && closed == 1)
                        {
                            fast[fastIdx] = SharedPointer<void>();

                            return -1;
                        }
                        else
                            return fastIdx;
                    }
                }
            }

            // Either allocating on slow-path, or fast-path can no longer accomodate more entries.
            int64_t slowIdx = Atomics::IncrementAndGet64(&slowCtr) - 1;

            HandleRegistrySegment* segment = slow[slowIdx % slowSegmentCnt];

            segment->Put(slowIdx, target);

            // Double-check for closed state if safe mode is on.
            Memory::Fence();

            if (safe && closed == 1)
            {
                segment->Remove(slowIdx);

                return -1;
            }

            return slowIdx;
        }
    }
}