//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
namespace Js
{
    class FunctionProxy;
    class FunctionBody;
    class ParseableFunctionInfo;
    class DeferDeserializeFunctionInfo;

    class FunctionInfo
    {
        friend class RemoteFunctionBody;

    public:
        enum Attributes : uint32
        {
            None                           = 0x00000,
            ErrorOnNew                     = 0x00001,
            SkipDefaultNewObject           = 0x00002,
            DoNotProfile                   = 0x00004,
            HasNoSideEffect                = 0x00008, // calling function doesn't cause an implicit flags to be set,
                                                      // the callee will detect and set implicit flags on its individual operations
            NeedCrossSiteSecurityCheck     = 0x00010,
            DeferredDeserialize            = 0x00020, // The function represents something that needs to be deserialized on use
            DeferredParse                  = 0x00040, // The function represents something that needs to be parsed on use
            CanBeHoisted                   = 0x00080, // The function return value won't be changed in a loop so the evaluation can be hoisted.
            SuperReference                 = 0x00100,
            ClassMethod                    = 0x00200, // The function is a class method
            ClassConstructor               = 0x00400, // The function is a class constructor
            Lambda                         = 0x01000,
            CapturesThis                   = 0x02000, // Only lambdas will set this; denotes whether the lambda referred to this, used by debugger
            Generator                      = 0x04000,
            BuiltInInlinableAsLdFldInlinee = 0x08000,
            Async                          = 0x10000,
            Module                         = 0x20000, // The function is the function body wrapper for a module
            EnclosedByGlobalFunc           = 0x40000,
            CanDefer                       = 0x80000,
            AllowDirectSuper               = 0x100000,
            BaseConstructorKind            = 0x200000,
            Method                         = 0x400000, // The function is a method
            ComputedName                   = 0x800000,
            ActiveScript                   = 0x1000000,
            HomeObj                        = 0x2000000,
            GeneratorWithComplexParams     = 0x8000000 // Generator function with non-simple params needs startup yield
        };
        FunctionInfo(JavascriptMethod entryPoint, Attributes attributes = None, LocalFunctionId functionId = Js::Constants::NoFunctionId, FunctionProxy* functionBodyImpl = nullptr);
        FunctionInfo(JavascriptMethod entryPoint, _no_write_barrier_tag, Attributes attributes = None, LocalFunctionId functionId = Js::Constants::NoFunctionId, FunctionProxy* functionBodyImpl = nullptr);
        FunctionInfo(FunctionInfo& that); // Todo: (leish)(swb) find a way to prevent non-static initializer calling this ctor

        static DWORD GetFunctionBodyImplOffset() { return offsetof(FunctionInfo, functionBodyImpl); }
        static BYTE GetOffsetOfFunctionProxy()
        {
            CompileAssert(offsetof(FunctionInfo, functionBodyImpl) <= UCHAR_MAX);
            return offsetof(FunctionInfo, functionBodyImpl);
        }
        static DWORD GetAttributesOffset() { return offsetof(FunctionInfo, attributes); }

        void VerifyOriginalEntryPoint() const;
        JavascriptMethod GetOriginalEntryPoint() const;
        JavascriptMethod GetOriginalEntryPoint_Unchecked() const;
        void SetOriginalEntryPoint(const JavascriptMethod originalEntryPoint);

        bool IsAsync() const { return ((this->attributes & Async) != 0); }
        bool IsDeferred() const { return ((this->attributes & (DeferredDeserialize | DeferredParse)) != 0); }
        static bool IsLambda(Attributes attributes) { return ((attributes & Lambda) != 0); }
        bool IsLambda() const { return IsLambda(this->attributes); }
        bool IsConstructor() const { return ((this->attributes & ErrorOnNew) == 0); }

        static bool IsGenerator(Attributes attributes) { return ((attributes & Generator) != 0); }
        bool IsGenerator() const { return IsGenerator(this->attributes); }

        bool IsClassConstructor() const { return ((this->attributes & ClassConstructor) != 0); }
        bool IsClassMethod() const { return ((this->attributes & ClassMethod) != 0); }
        bool IsMethod() const { return ((this->attributes & Method) != 0); }
        bool IsModule() const { return ((this->attributes & Module) != 0); }
        bool HasSuperReference() const { return ((this->attributes & SuperReference) != 0); }
        bool CanBeDeferred() const { return ((this->attributes & CanDefer) != 0); }
        static bool IsCoroutine(Attributes attributes) { return ((attributes & (Async | Generator)) != 0); }
        bool IsCoroutine() const { return IsCoroutine(this->attributes); }
        static bool HasComputedName(Attributes attributes) { return (attributes & Attributes::ComputedName) != 0; }
        bool HasComputedName() const { return HasComputedName(this->attributes); }
        static bool HasHomeObj(Attributes attributes) { return (attributes & Attributes::HomeObj) != 0; }
        bool HasHomeObj() const { return HasHomeObj(this->attributes); }

        BOOL HasBody() const { return functionBodyImpl != NULL; }
        BOOL HasParseableInfo() const { return this->HasBody() && !this->IsDeferredDeserializeFunction(); }

        FunctionProxy * GetFunctionProxy() const
        {
            return functionBodyImpl;
        }
        void SetFunctionProxy(FunctionProxy * proxy)
        {
            functionBodyImpl = proxy;
        }
        ParseableFunctionInfo* GetParseableFunctionInfo() const
        {
            Assert(functionBodyImpl == nullptr || !IsDeferredDeserializeFunction());
            return (ParseableFunctionInfo*)GetFunctionProxy();
        }
        void SetParseableFunctionInfo(ParseableFunctionInfo* func)
        {
            Assert(functionBodyImpl == nullptr || !IsDeferredDeserializeFunction());
            SetFunctionProxy((FunctionProxy*)func);
        }
        DeferDeserializeFunctionInfo* GetDeferDeserializeFunctionInfo() const
        {
            Assert(functionBodyImpl == nullptr || IsDeferredDeserializeFunction());
            return (DeferDeserializeFunctionInfo*)PointerValue(functionBodyImpl);
        }
        FunctionBody * GetFunctionBody() const;

        Attributes GetAttributes() const { return attributes; }
        static Attributes GetAttributes(Js::RecyclableObject * function);
        void SetAttributes(Attributes attr) { attributes = attr; }

        LocalFunctionId GetLocalFunctionId() const { return functionId; }
        void SetLocalFunctionId(LocalFunctionId functionId) { this->functionId = functionId; }

        uint GetCompileCount() const { return compileCount; }
        void SetCompileCount(uint count) { compileCount = count; }

        bool IsBuiltInApplyFunction();
        bool IsBuiltInCallFunction();
        
        BOOL IsDeferredDeserializeFunction() const { return ((this->attributes & DeferredDeserialize) == DeferredDeserialize); }
        BOOL IsDeferredParseFunction() const { return ((this->attributes & DeferredParse) == DeferredParse); }
        void SetCapturesThis() { attributes = (Attributes)(attributes | Attributes::CapturesThis); }
        bool GetCapturesThis() const { return (attributes & Attributes::CapturesThis) != 0; }
        void SetEnclosedByGlobalFunc() { attributes = (Attributes)(attributes | Attributes::EnclosedByGlobalFunc ); }
        bool GetEnclosedByGlobalFunc() const { return (attributes & Attributes::EnclosedByGlobalFunc) != 0; }
        void SetAllowDirectSuper() { attributes = (Attributes)(attributes | Attributes::AllowDirectSuper); }
        bool GetAllowDirectSuper() const { return (attributes & Attributes::AllowDirectSuper) != 0; }
        void SetBaseConstructorKind() { attributes = (Attributes)(attributes | Attributes::BaseConstructorKind); }
        bool GetBaseConstructorKind() const { return (attributes & Attributes::BaseConstructorKind) != 0; }
        bool IsActiveScript() const { return ((this->attributes & Attributes::ActiveScript) != 0); }
        void SetIsActiveScript() { attributes = (Attributes)(attributes | Attributes::ActiveScript); }
        bool GetGeneratorWithComplexParams() {return (attributes & Attributes::GeneratorWithComplexParams) != 0; }
    protected:
        FieldNoBarrier(JavascriptMethod) originalEntryPoint;
        FieldWithBarrier(FunctionProxy *) functionBodyImpl;     // Implementation of the function- null if the function doesn't have a body
        Field(LocalFunctionId) functionId;        // Per host source context (source file) function Id
        Field(uint) compileCount;
        Field(Attributes) attributes;
    };

    // Helper FunctionInfo for builtins that we don't want to profile (script profiler).
    class NoProfileFunctionInfo : public FunctionInfo
    {
    public:
        NoProfileFunctionInfo(JavascriptMethod entryPoint)
            : FunctionInfo(entryPoint, Attributes::DoNotProfile)
        {}

        NoProfileFunctionInfo(JavascriptMethod entryPoint, _no_write_barrier_tag)
            : FunctionInfo(FORCE_NO_WRITE_BARRIER_TAG(entryPoint), Attributes::DoNotProfile)
        {}
    };

    class AutoDisableRedeferral
    {
    public:
        AutoDisableRedeferral(FunctionInfo * functionInfo) : functionInfo(functionInfo), canDefer(false) 
        {
            if (functionInfo)
            {
                canDefer = functionInfo->CanBeDeferred();
                functionInfo->SetAttributes((FunctionInfo::Attributes)(functionInfo->GetAttributes() & ~FunctionInfo::Attributes::CanDefer));
            }
        }
        ~AutoDisableRedeferral() 
        {
            if (functionInfo && canDefer)
            {
                functionInfo->SetAttributes((FunctionInfo::Attributes)(functionInfo->GetAttributes() | FunctionInfo::Attributes::CanDefer));
            }
        }
    private:
            FunctionInfo * functionInfo;
            bool canDefer;
    };

};
