/****************************************************************************
 * Copyright (C) 2014-2015 Intel Corporation.   All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @file rdtsc_buckets.h
 *
 * @brief declaration for rdtsc buckets.
 *
 * Notes:
 *
 ******************************************************************************/
#pragma once

#include "os.h"
#include <vector>
#include <mutex>
#include <sstream>

#include "rdtsc_buckets_shared.h"


// unique thread id stored in thread local storage
extern THREAD UINT tlsThreadId;

//////////////////////////////////////////////////////////////////////////
/// @brief BucketManager encapsulates a single instance of the buckets
///        functionality. There can be one or many bucket managers active
///        at any time.  The manager owns all the threads and
///        bucket information that have been registered to it.
class BucketManager
{
public:
    BucketManager() {}
    ~BucketManager();

    // removes all registered thread data
    void ClearThreads()
    {
        mThreadMutex.lock();
        mThreads.clear();
        mThreadMutex.unlock();
    }

    // removes all registered buckets
    void ClearBuckets()
    {
        mThreadMutex.lock();
        mBuckets.clear();
        mThreadMutex.unlock();
    }

    /// Registers a new thread with the manager.
    /// @param name - name of thread, used for labels in reports and threadviz
    void RegisterThread(const std::string& name);

    /// Registers a new bucket type with the manager.  Returns a unique
    /// id which should be used in subsequent calls to start/stop the bucket
    /// @param desc - description of the bucket
    /// @return unique id
    UINT RegisterBucket(const BUCKET_DESC& desc);

    // print report
    void PrintReport(const std::string& filename);


    // start capturing
    void StartCapture();

    // stop capturing
    INLINE void StopCapture()
    {
        mCapturing = false;

        // wait for all threads to pop back to root bucket
        bool stillCapturing = true;
        while (stillCapturing)
        {
            stillCapturing = false;
            for (const BUCKET_THREAD& t : mThreads)
            {
                if (t.level > 0)
                {
                    stillCapturing = true;
                    continue;
                }
            }
        }

        mDoneCapturing = true;
        printf("Capture Stopped\n");
    }

    // start a bucket
    // @param id generated by RegisterBucket
    INLINE void StartBucket(UINT id)
    {
        if (!mCapturing)
            return;

        SWR_ASSERT(tlsThreadId < mThreads.size());

        BUCKET_THREAD& bt = mThreads[tlsThreadId];

        uint64_t tsc = __rdtsc();

        {
            if (bt.pCurrent->children.size() < mBuckets.size())
            {
                bt.pCurrent->children.resize(mBuckets.size());
            }
            BUCKET& child = bt.pCurrent->children[id];
            child.pParent = bt.pCurrent;
            child.id      = id;
            child.start   = tsc;

            // update thread's currently executing bucket
            bt.pCurrent = &child;
        }


        bt.level++;
    }

    // stop the currently executing bucket
    INLINE void StopBucket(UINT id)
    {
        SWR_ASSERT(tlsThreadId < mThreads.size());
        BUCKET_THREAD& bt = mThreads[tlsThreadId];

        if (bt.level == 0)
        {
            return;
        }

        uint64_t tsc = __rdtsc();

        {
            if (bt.pCurrent->start == 0)
                return;
            SWR_ASSERT(bt.pCurrent->id == id, "Mismatched buckets detected");

            bt.pCurrent->elapsed += (tsc - bt.pCurrent->start);
            bt.pCurrent->count++;

            // pop to parent
            bt.pCurrent = bt.pCurrent->pParent;
        }

        bt.level--;
    }

    INLINE void AddEvent(uint32_t id, uint32_t count)
    {
        if (!mCapturing)
            return;

        SWR_ASSERT(tlsThreadId < mThreads.size());

        BUCKET_THREAD& bt = mThreads[tlsThreadId];

        // don't record events for threadviz
        {
            if (bt.pCurrent->children.size() < mBuckets.size())
            {
                bt.pCurrent->children.resize(mBuckets.size());
            }
            BUCKET& child = bt.pCurrent->children[id];
            child.pParent = bt.pCurrent;
            child.id      = id;
            child.count += count;
        }
    }

private:
    void PrintBucket(
        FILE* f, UINT level, uint64_t threadCycles, uint64_t parentCycles, const BUCKET& bucket);
    void PrintThread(FILE* f, const BUCKET_THREAD& thread);

    // list of active threads that have registered with this manager
    std::vector<BUCKET_THREAD> mThreads;

    // list of buckets registered with this manager
    std::vector<BUCKET_DESC> mBuckets;

    // is capturing currently enabled
    volatile bool mCapturing{false};

    // has capturing completed
    volatile bool mDoneCapturing{false};

    std::mutex mThreadMutex;

    std::string mThreadVizDir;

};

// C helpers for jitter
void BucketManager_StartBucket(BucketManager* pBucketMgr, uint32_t id);
void BucketManager_StopBucket(BucketManager* pBucketMgr, uint32_t id);
