//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once

// TODO, this should be elsewhere
#define RecyclerNewImplicitRoot(recycler,T,...) AllocatorNewBase(Recycler, recycler, NoThrowAllocImplicitRoot, T, __VA_ARGS__)

// Forward reference
class RecyclerTestObject;

// A "Location" represents a location in memory where object references (ReccylerTestObject *s) are stored.
// It includes a "type" that defines how the reference should be read or written.
// This allows us to specify special handling for certain location types,
// such as pinned roots, software write barrier locations, tagged locations for tracked objects, etc.

class Location
{
private:
    enum class Type 
    {
        Scanned,        // For regular locations in scanned objects (or stack locations)
        Rooted,         // This is a non-heap location that's explicitly rooted.
        Barrier,        // Write barrier location
        Tagged,         // Tagged location (used in tracked objects)
        ImplicitRoot,   // Uses ImplicitRootBit to hold reference 
    };

    class ImplicitRootHolder
    {
    private:
        RecyclerTestObject * reference;

    public:
        ImplicitRootHolder(RecyclerTestObject * reference)
            : reference(reference)
        {
            VerifyCondition(reference != nullptr);
        }

        RecyclerTestObject * GetReference() const
        {
            VerifyCondition(this->reference != nullptr);
            return this->reference;
        }
    };
    
    Location(RecyclerTestObject ** location, Type type) :
        location(location), type(type)
    {
        VerifyCondition(location != nullptr);
    }

public:
    static Location Scanned(RecyclerTestObject ** location)
    {
        return Location(location, Type::Scanned);
    }
    
    static Location Rooted(RecyclerTestObject ** location)
    {
        return Location(location, Type::Rooted);
    }

    static Location Barrier(RecyclerTestObject ** location)
    {
        return Location(location, Type::Barrier);
    }

    static Location Tagged(RecyclerTestObject ** location)
    {
        return Location(location, Type::Tagged);
    }    

    static Location ImplicitRoot(RecyclerTestObject ** location)
    {
        return Location(location, Type::ImplicitRoot);
    }    
    
    RecyclerTestObject * Get() 
    {
        switch (type)
        {
            case Type::Scanned:
                return *location;

            case Type::Rooted:
                return *location;

            case Type::Barrier:
                return *location;

            case Type::Tagged:
                return Untag(*location);

            case Type::ImplicitRoot:
                // The stored location actually contains a pointer to the ImplicitRootHolder
                ImplicitRootHolder * holder = (ImplicitRootHolder *)(*location);
                if (holder == nullptr)
                {
                    return nullptr;
                }
                
                return holder->GetReference();
        }

        // Shouldn't get here
        VerifyCondition(false);
        return nullptr;
    }

    void Set(RecyclerTestObject * value) 
    {
        switch (type)
        {
            case Type::Scanned:
                *location = value;
                return;

            case Type::Rooted:
                Unroot();
                *location = value;
                Root();
                return;

            case Type::Barrier:
                *location = value;
                if (value != nullptr)
                {
                    RecyclerWriteBarrierManager::WriteBarrier(location);
                }
                return;

            case Type::Tagged:
                *location = Tag(value);
                return;

            case Type::ImplicitRoot:
                // The stored location actually contains a pointer to the ImplicitRootHolder
                ImplicitRootHolder * holder = (ImplicitRootHolder *)(*location);
                if (holder != nullptr)
                {
                    // Free the implicit root
                    // TODO: This should be better encapsulated
                    RecyclerHeapObjectInfo heapObject;
                    bool found = recyclerInstance->FindHeapObject(holder, FindHeapObjectFlags_VerifyFreeBitForAttribute, heapObject);
                    VerifyCondition(found);

                    // Zero pointers in order to eliminate false-positives
                    memset(holder, 0, heapObject.GetSize());

                    bool success = heapObject.ClearImplicitRootBit();
                    VerifyCondition(success);

                    *location = nullptr;
                }

                if (value != nullptr)
                {
                    // Create new holder object
                    holder = RecyclerNewImplicitRoot(recyclerInstance, ImplicitRootHolder, value);
                    VerifyCondition(holder != nullptr);

                    // Store holder back to the location
                    *location = (RecyclerTestObject *)holder;
                }

                return;
        }

        // Shouldn't get here
        VerifyCondition(false);
    }    

    static RecyclerTestObject * Tag(RecyclerTestObject * untagged)
    {
        if (untagged == nullptr)
        {
            return nullptr;
        }
        else
        {
            size_t value = (size_t)untagged;
            VerifyCondition((value & 0x1) == 0);
            value |= 0x1;
            return (RecyclerTestObject *)value;
        }
    }

    static RecyclerTestObject * Untag(RecyclerTestObject * tagged)
    {
        if (tagged == nullptr)
        {
            return nullptr;
        }
        else
        {
            size_t value = (size_t)tagged;
            VerifyCondition((value & 0x1) == 0x1);
            value &= ~(0x1);
            return (RecyclerTestObject *)value;
        }
    }

private:
    void Unroot()
    {
        if (*location != nullptr)
        {
            recyclerInstance->RootRelease(*location);
        }
    }
    
    void Root()
    {
        if (*location != nullptr)
        {
            recyclerInstance->RootAddRef(*location);
        }
    }
    
private:
    RecyclerTestObject ** location;
    Type type;
};



