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

class ISourceHolder;
namespace Js
{
    template <typename TLoadCallback, typename TUnloadCallback>
    class JsrtSourceHolder sealed : public ISourceHolder
    {
    private:
        enum MapRequestFor { Source = 1, Length = 2 };

        FieldNoBarrier(TLoadCallback) scriptLoadCallback;
        FieldNoBarrier(TUnloadCallback) scriptUnloadCallback;
        Field(JsSourceContext) sourceContext;

#ifndef NTBUILD
        Field(JsValueRef) mappedScriptValue;
        Field(DetachedStateBase*) mappedSerializedScriptValue;
#endif
        Field(utf8char_t const *) mappedSource;
        Field(size_t) mappedSourceByteLength;
        Field(size_t) mappedAllocLength;

        // Wrapper methods with Asserts to ensure that we aren't trying to access unmapped source
        utf8char_t const * GetMappedSource()
        {
            AssertMsg(mappedSource != nullptr, "Our mapped source is nullptr, isSourceMapped (Assert above) should be false.");
            AssertMsg(scriptUnloadCallback != nullptr, "scriptUnloadCallback is null, this means that this object has been finalized.");
            return mappedSource;
        };

        size_t GetMappedSourceLength()
        {
            AssertMsg(mappedSource != nullptr, "Our mapped source is nullptr, isSourceMapped (Assert above) should be false.");
            AssertMsg(scriptUnloadCallback != nullptr, "scriptUnloadCallback is null, this means that this object has been finalized.");
            return mappedSourceByteLength;
        };

        void EnsureSource(MapRequestFor requestedFor, const WCHAR* reasonString);

    public:
        JsrtSourceHolder(_In_ TLoadCallback scriptLoadCallback,
            _In_ TUnloadCallback scriptUnloadCallback,
            _In_ JsSourceContext sourceContext,
            Js::ArrayBuffer* serializedScriptValue = nullptr) :
            scriptLoadCallback(scriptLoadCallback),
            scriptUnloadCallback(scriptUnloadCallback),
            sourceContext(sourceContext),
#ifndef NTBUILD
            mappedScriptValue(nullptr),
            mappedSerializedScriptValue(serializedScriptValue == nullptr ? nullptr : serializedScriptValue->DetachAndGetState(false /*queueForDelayFree*/)),
#endif
            mappedSourceByteLength(0),
            mappedSource(nullptr)
        {
            AssertMsg(scriptLoadCallback != nullptr, "script load callback given is null.");
            AssertMsg(scriptUnloadCallback != nullptr, "script unload callback given is null.");
        };

        virtual bool IsEmpty() override
        {
            return false;
        }

        // Following two methods do not attempt any source mapping
        LPCUTF8 GetSourceUnchecked()
        {
            return this->GetMappedSource();
        }

        // Following two methods are calls to EnsureSource before attempting to get the source
        virtual LPCUTF8 GetSource(const WCHAR* reasonString) override
        {
            this->EnsureSource(MapRequestFor::Source, reasonString);
            return this->GetMappedSource();
        }

        virtual size_t GetByteLength(const WCHAR* reasonString) override
        {
            this->EnsureSource(MapRequestFor::Length, reasonString);
            return this->GetMappedSourceLength();
        }

        virtual void Finalize(bool isShutdown) override;

        virtual void Dispose(bool isShutdown) override
        {
#ifndef NTBUILD
            if (this->mappedSerializedScriptValue != nullptr)
            {
                // We have to extend the buffer data's lifetime until Dispose because
                // it might be used during finalization of other objects, such as
                // FunctionEntryPointInfo which wants to log its name.
                this->mappedSerializedScriptValue->CleanUp();
            }
#endif
        }

        virtual void Mark(Recycler * recycler) override
        {
        }

        virtual void Unload() override;

        virtual bool Equals(ISourceHolder* other) override
        {
            return this == other ||
                (this->GetByteLength(_u("Equal Comparison")) == other->GetByteLength(_u("Equal Comparison"))
                    && (this->GetSource(_u("Equal Comparison")) == other->GetSource(_u("Equal Comparison"))
                        || memcmp(this->GetSource(_u("Equal Comparison")), other->GetSource(_u("Equal Comparison")), this->GetByteLength(_u("Equal Comparison"))) == 0));
        }

        virtual hash_t GetHashCode() override
        {
            LPCUTF8 source = GetSource(_u("Hash Code Calculation"));
            size_t byteLength = GetByteLength(_u("Hash Code Calculation"));
            Assert(byteLength < MAXUINT32);
            return JsUtil::CharacterBuffer<utf8char_t>::StaticGetHashCode(source, (charcount_t)byteLength);
        }


        virtual bool IsDeferrable() override
        {
            return !PHASE_OFF1(Js::DeferSourceLoadPhase);
        }
    };
}
