//-------------------------------------------------------------------------------------------------------
// 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.
//-------------------------------------------------------------------------------------------------------
#pragma once

class MessageBase
{
private:
    unsigned int m_time;
    unsigned int m_id;

    static unsigned int s_messageCount;

    MessageBase(const MessageBase&);

public:
    MessageBase(unsigned int time) : m_time(time), m_id(s_messageCount++) { }
    virtual ~MessageBase() { }

    void BeginTimer() { m_time += GetTickCount(); };
    unsigned int GetTime() { return m_time; };
    unsigned int GetId() { return m_id; };

    virtual HRESULT Call(LPCSTR fileName) = 0;
};

template <typename T>
class SortedList 
{
    template <typename U>
    struct DListNode
    {
        U data;
        DListNode<U>* prev;
        DListNode<U>* next;

    public:
        DListNode(const U& data) :
            data(data),
            prev(nullptr),
            next(nullptr)
        { }
    };

public:
    SortedList():
        head(nullptr)
    {
    }

    ~SortedList()
    {
        while (head != nullptr)
        {
            Remove(head);
        }
    }

    // Scan through the sorted list
    // Insert before the first node that satisfies the LessThan function
    // This function maintains the invariant that the list is always sorted
    void Insert(const T& data)
    {
        DListNode<T>* curr = head;
        DListNode<T>* node = new DListNode<T>(data);
        DListNode<T>* prev = nullptr; 

        // Now, if we have to insert, we have to insert *after* some node
        while (curr != nullptr)
        {
            if (T::LessThan(data, curr->data))
            {
                break;
            }
            prev = curr;
            curr = curr->next;
        }

        InsertAfter(node, prev);
    }

    T Pop()
    {
        T data = head->data;
        Remove(head);
        return data;
    }

    template <typename PredicateFn>
    void Remove(PredicateFn fn)
    {
        DListNode<T>* node = head;

        while (node != nullptr)
        {
            if (fn(node->data))
            {
                Remove(node);
                return;
            }
        }
    }

    template <typename PredicateFn>
    void RemoveAll(PredicateFn fn)
    {
        while (head != nullptr)
        {
            fn(head->data);
            Remove(head);
        }
    }

    bool IsEmpty()
    {
        return head == nullptr;
    }

private:
    void Remove(DListNode<T>* node)
    {
        if (node->prev == nullptr)
        {
            head = node->next;
        }
        else
        {
            node->prev->next = node->next;
        }

        if (node->next != nullptr)
        {
            node->next->prev = node->prev;
        }

        delete node;
    }

    void InsertAfter(DListNode<T>* newNode, DListNode<T>* node)
    {
        // If the list is empty, just set head to newNode
        if (head == nullptr)
        {
            Assert(node == nullptr);
            head = newNode;
            return;
        }

        // If node is null here, we must be trying to insert before head
        if (node == nullptr)
        {
            newNode->next = head;
            head->prev = newNode;
            head = newNode;
            return;
        }

        Assert(node);
        newNode->next = node->next;
        newNode->prev = node;

        if (node->next)
        {
            node->next->prev = newNode;
        }

        node->next = newNode;
    }

    DListNode<T>* head;
};

class MessageQueue
{
    struct ListEntry
    {
        unsigned int time;
        MessageBase* message;

        ListEntry(unsigned int time, MessageBase* message):
            time(time),
            message(message)
        { }

        static bool LessThan(const ListEntry& first, const ListEntry& second)
        {
            return first.time < second.time;
        }
    };

    SortedList<ListEntry> m_queue;

public:
    void InsertSorted(MessageBase *message)
    {
        message->BeginTimer();
        unsigned int time = message->GetTime();
        m_queue.Insert(ListEntry(time, message));
    }

    MessageBase* PopAndWait()
    {
        Assert(!m_queue.IsEmpty());

        ListEntry entry = m_queue.Pop();
        MessageBase *tmp = entry.message;

        int waitTime = tmp->GetTime() - GetTickCount();
        if(waitTime > 0)
        {
            Sleep(waitTime);
        }

        return tmp;
    }

    bool IsEmpty()
    {
        return m_queue.IsEmpty();
    }

    void RemoveById(unsigned int id)
    {
        // Search for the message with the correct id, and delete it. Can be updated
        // to a hash to improve speed, if necessary.
        m_queue.Remove([id](const ListEntry& entry) 
        {
            MessageBase *msg = entry.message;
            if(msg->GetId() == id)
            {
                delete msg;
                return true;
            }

            return false;
        });
    }

    void RemoveAll()
    {
        m_queue.RemoveAll([](const ListEntry& entry) { 
            MessageBase* msg = entry.message;
            delete msg;
        });
    }

    HRESULT ProcessAll(LPCSTR fileName)
    {
        while(!IsEmpty())
        {
            MessageBase *msg = PopAndWait();

            // Omit checking return value for async function, since it shouldn't affect others.
            msg->Call(fileName);
            delete msg;

            ChakraRTInterface::JsTTDNotifyYield();
        }
        return S_OK;
    }
};

//
// A custom message helper class to assist defining messages handled by callback functions.
//
template <class Func, class CustomBase>
class CustomMessage : public CustomBase
{
private:
    Func m_func;

public:
    CustomMessage(unsigned int time, JsValueRef customArg, const Func& func) :
        CustomBase(time, customArg), m_func(func)
    {}

    virtual HRESULT Call(LPCSTR fileName) override
    {
        return m_func(*this);
    }
};
