/****************************************************************************
* 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);
