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

namespace Js
{
    template <typename Key, bool zero>
    struct SameValueComparerCommon
    {
        static bool Equals(Key, Key) { static_assert(false, "Can only use SameValueComparer with Var as the key type"); }
        static hash_t GetHashCode(Key) { static_assert(false, "Can only use SameValueComparer with Var as the key type"); }
    };

    template <typename Key> using SameValueComparer = SameValueComparerCommon<Key, false>;
    template <typename Key> using SameValueZeroComparer = SameValueComparerCommon<Key, true>;

    template <bool zero>
    struct SameValueComparerCommon<Var, zero>
    {
        static bool Equals(Var x, Var y)
        {
            if (zero)
            {
                return JavascriptConversion::SameValueZero(x, y);
            }
            else
            {
                return JavascriptConversion::SameValue(x, y);
            }
        }

        static hash_t HashDouble(double d)
        {
            if (JavascriptNumber::IsNan(d))
            {
                return 0;
            }

            if (zero)
            {
                // SameValueZero treats -0 and +0 the same, so normalize to get same hash code
                if (JavascriptNumber::IsNegZero(d))
                {
                    d = 0.0;
                }
            }

            __int64 v = *(__int64*)&d;
            return (uint)v ^ (uint)(v >> 32);
        }

        static hash_t GetHashCode(Var i)
        {
            switch (JavascriptOperators::GetTypeId(i))
            {
            case TypeIds_Integer:
                // int32 can be fully represented in a double, so hash it as a double
                // to ensure that tagged ints hash to the same value as JavascriptNumbers.
                return HashDouble((double)TaggedInt::ToInt32(i));

            case TypeIds_Int64Number:
            case TypeIds_UInt64Number:
                {
                    __int64 v = VarTo<JavascriptInt64Number>(i)->GetValue();
                    double d = (double) v;
                    if (v != (__int64) d)
                    {
                        // this int64 is too large to represent in a double
                        // and thus will never be equal to a double so hash it
                        // as an int64
                        return (uint)v ^ (uint)(v >> 32);
                    }

                    // otherwise hash it as a double
                    return HashDouble(d);
                }

            case TypeIds_Number:
                {
                    double d = JavascriptNumber::GetValue(i);
                    return HashDouble(d);
                }

            case TypeIds_String:
                {
                    JavascriptString* v = UnsafeVarTo<JavascriptString>(i);
                    return JsUtil::CharacterBuffer<WCHAR>::StaticGetHashCode(v->GetString(), v->GetLength());
                }

            default:
                return RecyclerPointerComparer<Var>::GetHashCode(i);
            }
        }
    };
}
