//
// Copyright © 2020 Arm Ltd and Contributors. All rights reserved.
// SPDX-License-Identifier: MIT
//

#include "TestTimelinePacketHandler.hpp"

#include <client/src/IProfilingConnection.hpp>

#include <common/include/LabelsAndEventClasses.hpp>

#include <chrono>
#include <iostream>

namespace arm
{

namespace pipe
{

std::vector<uint32_t> TestTimelinePacketHandler::GetHeadersAccepted()
{
    std::vector<uint32_t> headers;
    headers.push_back(m_DirectoryHeader); // message directory
    headers.push_back(m_MessageHeader); // message
    return headers;
}

void TestTimelinePacketHandler::HandlePacket(const arm::pipe::Packet& packet)
{
    if (packet.GetHeader() == m_DirectoryHeader)
    {
        ProcessDirectoryPacket(packet);
    }
    else if (packet.GetHeader() == m_MessageHeader)
    {
        ProcessMessagePacket(packet);
    }
    else
    {
        std::stringstream ss;
        ss << "Received a packet with unknown header [" << packet.GetHeader() << "]";
        throw arm::pipe::ProfilingException(ss.str());
    }
}

void TestTimelinePacketHandler::Stop()
{
    m_Connection->Close();
}

void TestTimelinePacketHandler::WaitOnInferenceCompletion(unsigned int timeout)
{
    std::unique_lock<std::mutex> lck(m_InferenceCompletedMutex);

    auto start = std::chrono::high_resolution_clock::now();
    // Here we we will go back to sleep after a spurious wake up if
    // m_InferenceCompleted is not yet true.
    if (!m_InferenceCompletedConditionVariable.wait_for(lck,
                                                        std::chrono::milliseconds(timeout),
                                                        [&]{return m_InferenceCompleted == true;}))
    {
        auto finish = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double, std::milli> elapsed = finish - start;
        std::stringstream ss;
        ss << "Timed out waiting on inference completion for " << elapsed.count() << " ms";
        throw arm::pipe::TimeoutException(ss.str());
    }
    return;
}

void TestTimelinePacketHandler::SetInferenceComplete()
{
    {   // only lock when we are updating the inference completed variable
        std::unique_lock<std::mutex> lck(m_InferenceCompletedMutex);
        m_InferenceCompleted = true;
    }
    m_InferenceCompletedConditionVariable.notify_one();
}

void TestTimelinePacketHandler::ProcessDirectoryPacket(const arm::pipe::Packet& packet)
{
    m_DirectoryDecoder(packet);
}

void TestTimelinePacketHandler::ProcessMessagePacket(const arm::pipe::Packet& packet)
{
    m_Decoder(packet);
}

// TimelineMessageDecoder functions
arm::pipe::ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateEntity(const Entity& entity)
{
    m_TimelineModel.AddEntity(entity.m_Guid);
    return arm::pipe::ITimelineDecoder::TimelineStatus::TimelineStatus_Success;
}

arm::pipe::ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateEventClass(
    const arm::pipe::ITimelineDecoder::EventClass& eventClass)
{
    m_TimelineModel.AddEventClass(eventClass);
    return arm::pipe::ITimelineDecoder::TimelineStatus::TimelineStatus_Success;
}

arm::pipe::ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateEvent(
    const arm::pipe::ITimelineDecoder::Event& event)
{
    m_TimelineModel.AddEvent(event);
    return arm::pipe::ITimelineDecoder::TimelineStatus::TimelineStatus_Success;
}

arm::pipe::ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateLabel(
    const arm::pipe::ITimelineDecoder::Label& label)
{
    m_TimelineModel.AddLabel(label);
    return arm::pipe::ITimelineDecoder::TimelineStatus::TimelineStatus_Success;
}

arm::pipe::ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateRelationship(
    const arm::pipe::ITimelineDecoder::Relationship& relationship)
{
    m_TimelineModel.AddRelationship(relationship);
    // check to see if this is an execution link to an inference of event class end of life
    // if so the inference has completed so send out a notification...
    if (relationship.m_RelationshipType == RelationshipType::ExecutionLink &&
        m_TimelineModel.IsInferenceGuid(relationship.m_HeadGuid))
    {
        ProfilingStaticGuid attributeGuid(relationship.m_AttributeGuid);
        if (attributeGuid == LabelsAndEventClasses::ARMNN_PROFILING_EOL_EVENT_CLASS)
        {
            if (m_PacketHandler != nullptr)
            {
                m_PacketHandler->SetInferenceComplete();
            }
        }
    }
    return arm::pipe::ITimelineDecoder::TimelineStatus::TimelineStatus_Success;
}

} // namespace pipe

} // namespace arm
