///===-- Activity.cpp ---------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include <Availability.h>
#include <string>
#include <dlfcn.h>
#include <uuid/uuid.h>

#include "DNBDefs.h"
#include "Genealogy.h"
#include "GenealogySPI.h"
#include "MachThreadList.h"

//---------------------------
/// Constructor
//---------------------------

Genealogy::Genealogy () :
    m_os_activity_diagnostic_for_pid (nullptr),
    m_os_activity_iterate_processes (nullptr),
    m_os_activity_iterate_breadcrumbs (nullptr),
    m_os_activity_iterate_messages (nullptr),
    m_os_activity_iterate_activities (nullptr),
    m_os_trace_get_type (nullptr),
    m_os_trace_copy_formatted_message (nullptr),
    m_os_activity_for_thread (nullptr),
    m_os_activity_for_task_thread (nullptr),
    m_thread_activities(),
    m_process_executable_infos(),
    m_diagnosticd_call_timed_out(false)
{
    m_os_activity_diagnostic_for_pid = (bool (*)(pid_t, os_activity_t, uint32_t, os_diagnostic_block_t))dlsym (RTLD_DEFAULT, "os_activity_diagnostic_for_pid");
    m_os_activity_iterate_processes = (void (*)(os_activity_process_list_t, bool (^)(os_activity_process_t)))dlsym (RTLD_DEFAULT, "os_activity_iterate_processes");
    m_os_activity_iterate_breadcrumbs = (void (*)(os_activity_process_t, bool (^)(os_activity_breadcrumb_t))) dlsym (RTLD_DEFAULT, "os_activity_iterate_breadcrumbs");
    m_os_activity_iterate_messages = (void (*)(os_trace_message_list_t, os_activity_process_t, bool (^)(os_trace_message_t)))dlsym (RTLD_DEFAULT, "os_activity_iterate_messages");
    m_os_activity_iterate_activities = (void (*)(os_activity_list_t, os_activity_process_t, bool (^)(os_activity_entry_t)))dlsym (RTLD_DEFAULT, "os_activity_iterate_activities");
    m_os_trace_get_type = (uint8_t (*)(os_trace_message_t)) dlsym (RTLD_DEFAULT, "os_trace_get_type");
    m_os_trace_copy_formatted_message = (char *(*)(os_trace_message_t)) dlsym (RTLD_DEFAULT, "os_trace_copy_formatted_message");
    m_os_activity_for_thread = (os_activity_t (*)(os_activity_process_t, uint64_t)) dlsym (RTLD_DEFAULT, "os_activity_for_thread");
    m_os_activity_for_task_thread = (os_activity_t (*)(task_t, uint64_t)) dlsym (RTLD_DEFAULT, "os_activity_for_task_thread");
    m_os_activity_messages_for_thread = (os_trace_message_list_t (*) (os_activity_process_t process, os_activity_t activity, uint64_t thread_id)) dlsym (RTLD_DEFAULT, "os_activity_messages_for_thread");
}

Genealogy::ThreadActivitySP
Genealogy::GetGenealogyInfoForThread (pid_t pid, nub_thread_t tid, const MachThreadList &thread_list, task_t task, bool &timed_out)
{
    ThreadActivitySP activity;
    //
    // if we've timed out trying to get the activities, don't try again at this process stop.
    // (else we'll need to hit the timeout for every thread we're asked about.)
    // We'll try again at the next public stop.

    if (m_thread_activities.size() == 0 && m_diagnosticd_call_timed_out == false)
    {
        GetActivities(pid, thread_list, task);
    }
    std::map<nub_thread_t, ThreadActivitySP>::const_iterator search;
    search = m_thread_activities.find(tid);
    if (search != m_thread_activities.end())
    {
        activity = search->second;
    }
    timed_out = m_diagnosticd_call_timed_out;
    return activity;
}

void
Genealogy::Clear()
{
    m_thread_activities.clear();
    m_diagnosticd_call_timed_out = false;
}

void
Genealogy::GetActivities(pid_t pid, const MachThreadList &thread_list, task_t task)
{
    if (m_os_activity_diagnostic_for_pid != nullptr 
        && m_os_activity_iterate_processes != nullptr
        && m_os_activity_iterate_breadcrumbs != nullptr
        && m_os_activity_iterate_messages != nullptr
        && m_os_activity_iterate_activities != nullptr
        && m_os_trace_get_type != nullptr
        && m_os_trace_copy_formatted_message != nullptr
        && (m_os_activity_for_thread != nullptr || m_os_activity_for_task_thread != nullptr)
       )
    {
        __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        __block BreadcrumbList breadcrumbs;
        __block ActivityList activities;
        __block MessageList messages;
        __block std::map<nub_thread_t, uint64_t> thread_activity_mapping;

        os_activity_diagnostic_flag_t flags = OS_ACTIVITY_DIAGNOSTIC_ALL_ACTIVITIES | OS_ACTIVITY_DIAGNOSTIC_PROCESS_ONLY;
        if (m_os_activity_diagnostic_for_pid (pid, 0, flags, ^(os_activity_process_list_t processes, int error)
        {
            if (error == 0)
            {
                m_os_activity_iterate_processes (processes, ^bool(os_activity_process_t process_info)
                {
                    if (pid == process_info->pid)
                    {
                        // Collect all the Breadcrumbs
                        m_os_activity_iterate_breadcrumbs (process_info, ^bool(os_activity_breadcrumb_t breadcrumb)
                        {
                            Breadcrumb bc;
                            bc.breadcrumb_id = breadcrumb->breadcrumb_id;
                            bc.activity_id = breadcrumb->activity_id;
                            bc.timestamp = breadcrumb->timestamp;
                            if (breadcrumb->name) 
                                bc.name = breadcrumb->name;
                            breadcrumbs.push_back (bc);
                            return true;
                        });

                        // Collect all the Activites
                        m_os_activity_iterate_activities (process_info->activities, process_info, ^bool(os_activity_entry_t activity)
                        {
                            Activity ac;
                            ac.activity_start = activity->activity_start;
                            ac.activity_id = activity->activity_id;
                            ac.parent_id = activity->parent_id;
                            if (activity->activity_name)
                                ac.activity_name = activity->activity_name;
                            if (activity->reason)
                                ac.reason = activity->reason;
                            activities.push_back (ac);
                            return true;
                        });


                        // Collect all the Messages -- messages not associated with any thread
                        m_os_activity_iterate_messages (process_info->messages, process_info, ^bool(os_trace_message_t trace_msg)
                        {
                            Message msg;
                            msg.timestamp = trace_msg->timestamp;
                            msg.trace_id = trace_msg->trace_id;
                            msg.thread = trace_msg->thread;
                            msg.type = m_os_trace_get_type (trace_msg);
                            msg.activity_id = 0;
                            if (trace_msg->image_uuid && trace_msg->image_path)
                            {
                                ProcessExecutableInfoSP process_info_sp (new ProcessExecutableInfo());
                                uuid_copy (process_info_sp->image_uuid, trace_msg->image_uuid);
                                process_info_sp->image_path = trace_msg->image_path;
                                msg.process_info_index = AddProcessExecutableInfo (process_info_sp);
                            }
                            const char *message_text = m_os_trace_copy_formatted_message (trace_msg);
                            if (message_text)
                                msg.message = message_text;
                            messages.push_back (msg);
                            return true;
                        });

                        // Discover which activities are said to be running on threads currently
                        const nub_size_t num_threads = thread_list.NumThreads();
                        for (nub_size_t i = 0; i < num_threads; ++i)
                        {
                            nub_thread_t thread_id = thread_list.ThreadIDAtIndex(i);
                            os_activity_t act = 0;
                            if (m_os_activity_for_task_thread != nullptr)
                            {
                                act = m_os_activity_for_task_thread (task, thread_id);
                            }
                            else if (m_os_activity_for_thread != nullptr)
                            {
                                act = m_os_activity_for_thread (process_info, thread_id);
                            }
                            if (act != 0)
                                thread_activity_mapping[thread_id] = act;
                        }

                        // Collect all Messages -- messages associated with a thread

                        // When there's no genealogy information, an early version of os_activity_messages_for_thread
                        // can crash in rare circumstances.  Check to see if this process has any activities before
                        // making the call to get messages.
                        if (process_info->activities != nullptr && thread_activity_mapping.size() > 0)
                        {
                            std::map<nub_thread_t, uint64_t>::const_iterator iter;
                            for (iter = thread_activity_mapping.begin(); iter != thread_activity_mapping.end(); ++iter)
                            {
                                nub_thread_t thread_id = iter->first;
                                os_activity_t act = iter->second;
                                os_trace_message_list_t this_thread_messages = m_os_activity_messages_for_thread (process_info, act, thread_id);
                                m_os_activity_iterate_messages (this_thread_messages, process_info, ^bool(os_trace_message_t trace_msg)
                                {
                                    Message msg;
                                    msg.timestamp = trace_msg->timestamp;
                                    msg.trace_id = trace_msg->trace_id;
                                    msg.thread = trace_msg->thread;
                                    msg.type = m_os_trace_get_type (trace_msg);
                                    msg.activity_id = act;
                                    if (trace_msg->image_uuid && trace_msg->image_path)
                                    {
                                        ProcessExecutableInfoSP process_info_sp (new ProcessExecutableInfo());
                                        uuid_copy (process_info_sp->image_uuid, trace_msg->image_uuid);
                                        process_info_sp->image_path = trace_msg->image_path;
                                        msg.process_info_index = AddProcessExecutableInfo (process_info_sp);
                                    }
                                    const char *message_text = m_os_trace_copy_formatted_message (trace_msg);
                                    if (message_text)
                                        msg.message = message_text;
                                    messages.push_back (msg);
                                    return true;
                                });
                            }
                        }
                    }
                return true;
                });
            }
            dispatch_semaphore_signal(semaphore);
        }) == true)
        {
            // Wait for the diagnosticd xpc calls to all finish up -- or half a second to elapse.
            dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
            bool success = dispatch_semaphore_wait(semaphore, timeout) == 0;
            if (!success)
            {
                m_diagnosticd_call_timed_out = true;
                return;
            }
        }

        // breadcrumbs, activities, and messages have all now been filled in.

        std::map<nub_thread_t, uint64_t>::const_iterator iter;
        for (iter = thread_activity_mapping.begin(); iter != thread_activity_mapping.end(); ++iter)
        {
            nub_thread_t thread_id = iter->first;
            uint64_t activity_id = iter->second;
            ActivityList::const_iterator activity_search;
            for (activity_search = activities.begin(); activity_search != activities.end(); ++activity_search)
            {
                if (activity_search->activity_id == activity_id)
                {
                    ThreadActivitySP thread_activity_sp (new ThreadActivity());
                    thread_activity_sp->current_activity = *activity_search;
    
                    BreadcrumbList::const_iterator breadcrumb_search;
                    for (breadcrumb_search = breadcrumbs.begin(); breadcrumb_search != breadcrumbs.end(); ++breadcrumb_search)
                    {
                        if (breadcrumb_search->activity_id == activity_id)
                        {
                            thread_activity_sp->breadcrumbs.push_back (*breadcrumb_search);
                        }
                    }
                    MessageList::const_iterator message_search;
                    for (message_search = messages.begin(); message_search != messages.end(); ++message_search)
                    {
                        if (message_search->thread == thread_id)
                        {
                            thread_activity_sp->messages.push_back (*message_search);
                        }
                    }
    
                    m_thread_activities[thread_id] = thread_activity_sp;
                    break;
                }
            }
        }
    }
}

uint32_t
Genealogy::AddProcessExecutableInfo (ProcessExecutableInfoSP process_exe_info)
{
    const uint32_t info_size = static_cast<uint32_t>(m_process_executable_infos.size());
    for (uint32_t idx = 0; idx < info_size; ++idx)
    {
        if (uuid_compare (m_process_executable_infos[idx]->image_uuid, process_exe_info->image_uuid) == 0)
        {
            return idx + 1;
        }
    }
    m_process_executable_infos.push_back (process_exe_info);
    return info_size + 1;
}

Genealogy::ProcessExecutableInfoSP
Genealogy::GetProcessExecutableInfosAtIndex(size_t idx)
{
    ProcessExecutableInfoSP info_sp;
    if (idx > 0)
    {
        idx--;
        if (idx <= m_process_executable_infos.size())
        {
            info_sp = m_process_executable_infos[idx];
        }
    }
    return info_sp;
}

