////////////////////////////////////////////////////////////////////////////////
//                                                                             /
// 2012-2019 (c) Baical                                                        /
//                                                                             /
// This library is free software; you can redistribute it and/or               /
// modify it under the terms of the GNU Lesser General Public                  /
// License as published by the Free Software Foundation; either                /
// version 3.0 of the License, or (at your option) any later version.          /
//                                                                             /
// This library is distributed in the hope that it will be useful,             /
// but WITHOUT ANY WARRANTY; without even the implied warranty of              /
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU           /
// Lesser General Public License for more details.                             /
//                                                                             /
// You should have received a copy of the GNU Lesser General Public            /
// License along with this library.                                            /
//                                                                             /
////////////////////////////////////////////////////////////////////////////////
#ifndef BK_P7_TRACE_H
#define BK_P7_TRACE_H

#define TRACE_FILE_EXTRA_THREADS                                            1ull
#define TRACE_FILE_EXTRA_MODULES                                            2ull

#define HEADER_ASSERT(cond) typedef int assert_type[(cond) ? 1 : -1]


//10110001111000001111110000000111111110000000001111111111000000b
#define FILE_HEADER_MARKER                               (0x2C783F01FE00FFC0ull) 
#define FILE_HEADER_VERSION                              (0x0005) 

#define FILE_HEADER_FLAG_UNSORTED                        (0x1)
#define FILE_HEADER_FLAG_TRACE_EXTENSION                 (0x2)

#define MAX_STREAM_NAME_LENGTH                           64

PRAGMA_PACK_ENTER(1)
struct sFile_Header
{
    tUINT64   qwMarker;
    tUINT16   wHeader_Version;

    //http://msdn.microsoft.com/en-us/library/windows/desktop/ms738531(v=vs.85).aspx
    tWCHAR    pNode_Name[MAX_NODE_NAME_LENGTH];
    tUINT16   wProtocol_Version;
    tUINT32   dwProcess_ID;
    FILETIME  sProcess_Time;
    tWCHAR    pProcess_Name[MAX_PROCESS_NAME_LENGTH];
    int       iFlags;

    FILETIME  sStream_Time;
    //Hi resolution timer value, we get this value when we retrieve current time.
    //using difference between this value and timer value for every trace we can
    //calculate time of the trace event with hi resolution
    tUINT64   qwTimer_Value;
    //timer's count heartbeats in second                       
    tUINT64   qwTimer_Frequency;                               
    tWCHAR    pStream_Name[MAX_STREAM_NAME_LENGTH];
    tUINT64   pCounters[256]; //sP7Trace_Data::bLevel - 1 byte
    tUINT64   qwTrace_Count;
    tUINT64   qwDesc_Offset;
    tUINT64   qwDesc_Size;
} ATTR_PACK(1);

struct sExtra_Header
{
    tUINT64   qwMarker;
    tUINT64   qwSize;
    tUINT32   dwType; //TRACE_FILE_EXTRA_THREADS or TRACE_FILE_EXTRA_MODULES
    tUINT32   dwCount;
} ATTR_PACK(1);

PRAGMA_PACK_EXIT()

// Data file will have this structure
// [sFile_Header] [Padding] [Traces Data] [Traces Descriptions] 
// Index file will have this structure
// [Offset][Offset][Offset][Offset][Offset][Offset][Offset]


#define TRACE_HEADER_RESERVED_SIZE                                      (0x2000)//8 kb

#define TRACE_WRITE_INDEX_BUFFER_LENGTH                                 (0x2000)//8 kb
#define TRACE_WRITE_DATA_BUFFER_LENGTH                                 (0x10000)//64 kb

#define TRACE_READ_INDEX_BUFFER_LENGTH                                 (0x20000)//128kb
#define TRACE_READ_INDEX_BUFFER_WINDOW (TRACE_READ_INDEX_BUFFER_LENGTH - 0x1000)//128kb - 4kb
#define TRACE_READ_DATA_BUFFER_LENGTH                                 (0x100000)//1 mb
#define TRACE_READ_DATA_BUFFER_WINDOW  (TRACE_READ_DATA_BUFFER_LENGTH - 0x10000)//1 mb - 65k

#define TRACE_TEXT_LENGTH                                               (0x2000)//8912
#define TRACE_VARARGS_LENGTH                                            (0x1000)//4096


//if this assert is false - look to sFile_Header::pCounters, the length of 
//this array is not enough to store all possible counters
HEADER_ASSERT(MEMBER_SIZE(sP7Trace_Data, bLevel) <= 1);

HEADER_ASSERT(sizeof(sFile_Header) <= TRACE_HEADER_RESERVED_SIZE);


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct sTrace_Desc
{
    tUINT8       *m_pBuffer;
    tUINT32       m_dwSize;
    tUINT16       m_wLine;
    tXCHAR       *m_pFormat;
    tXCHAR       *m_pFile_Path;
    tXCHAR       *m_pFile_Name; //pointer to m_pFile_Path
    tXCHAR       *m_pFunction;
    tUINT32       m_dwModuleID;
    sP7Trace_Arg *m_pArgs;
    tUINT32       m_dwArgs_Len;

    sTrace_Desc(sP7Trace_Format *i_pData);
    ~sTrace_Desc();
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct sThread
{
    tUINT32 dwThreadID;                        //Thread ID
    tUINT64 qwStart;                           //High resolution timer value start
    tUINT64 qwStop;                            //High resolution timer value stop
    char    pName[P7TRACE_THREAD_NAME_LENGTH]; //Thread name (UTF-8) 
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct sThreads
{
    tUINT32  dwCount;
    tUINT32  dwUsed;
    sThread *pThreads;

    sThreads(tUINT32 i_dwCount)
        : dwCount(i_dwCount)
        , dwUsed(0)
        , pThreads((sThread*)malloc(i_dwCount * sizeof(sThread)))
    {
    }

    virtual ~sThreads()
    {
        if (pThreads)
        {
            free(pThreads);
            pThreads = NULL;
        }
        dwCount = 0;
        dwUsed  = 0;
    }
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct sRbThread
{
    sThread   *pThread;
    sRbThread *pNext; 

    sRbThread(sThread *i_pThread, sRbThread *i_pNext = NULL)
    {
        pThread = i_pThread;
        pNext   = i_pNext;
    }

    virtual ~sRbThread()
    {
        if (pNext)
        {
            delete pNext;
            pNext = NULL;
        }
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CThreads_Tree:
    public CRBTree<sRbThread*, tUINT32>
{
protected:
    //////////////////////////////////////////////////////////////////////////// 
    //Return
    //TRUE  - if (i_pKey < i_pData::key)
    //FALSE - otherwise
    tBOOL Is_Key_Less(tUINT32 i_dwKey, sRbThread *i_pData) 
    {
        return (i_dwKey < i_pData->pThread->dwThreadID) ? TRUE : FALSE;
    }

    //////////////////////////////////////////////////////////////////////////// 
    //Return
    //TRUE  - if (i_pKey == i_pData::key)
    //FALSE - otherwise
    tBOOL Is_Qual(tUINT32 i_dwKey, sRbThread *i_pData) 
    {
        return (i_dwKey == i_pData->pThread->dwThreadID) ? TRUE : FALSE;
    }
};


class CTrace_File; //pre-declaration 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CReader
    : public Bk::ITraceReader
{
    //put volatile variables at the top, to obtain 32 bit alignment. 
    //Project has 8 bytes alignment by default
    tINT32 volatile m_lReference;
    CTrace_File    *m_pParent;

public:
    tUINT8  *m_pData_Read;
    size_t   m_dwData_Read_Used;
    tUINT64  m_qwData_Read_Offset;
    tUINT64 *m_pIDX_Read;
    size_t   m_dwIDX_Read_Used;
    tUINT64  m_qwIDX_Read_Start;

public:
    CReader(CTrace_File *i_pParent, tBOOL &o_rError);
    virtual ~CReader();

    Bk::eResult __stdcall Get_Counters(tUINT64 **o_pCounters);
    Bk::eResult __stdcall Get_Count(tUINT64 *o_pCount);
    Bk::eResult __stdcall Get_Trace(tUINT64 i_qwIndex, Bk::stTrace *o_pItem, tBOOL i_bFormat);
    Bk::eResult __stdcall Set_Filter(Bk::ITraceFilter *i_pFilter, const tXCHAR *i_pPath, Bk::ITraceReader **o_pReader);
    Bk::eResult __stdcall Get_Filter_Status(tINT32 *o_pPercent);
    Bk::eResult __stdcall Clr_Filter(tBOOL i_bDelete);

    tINT32 __stdcall Add_Ref()
    {
        return ATOMIC_INC(&m_lReference);
    }

    tINT32 __stdcall Release()
    {
        tINT32 l_lResult = ATOMIC_DEC(&m_lReference);
        if ( 0 >= l_lResult )
        {
            delete this;
        }

        return l_lResult;
    }
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CTrace_File
    : public Bk::IStorage
    , public Bk::IStorageReader
{
protected:
    //put volatile variables at the top, to obtain 32 bit alignment. 
    //Project has 8 bytes alignment by default
    tINT32 volatile          m_lReference;

    sFile_Header            *m_pHeader; 
    tUINT8                  *m_pVarArgs;
    tXCHAR                  *m_pText;
    Bk::stStorageInfo        m_sInfo;
    tBOOL                    m_bIsInfo;
    tBOOL                    m_bReadOnly;
    tBOOL                    m_bDelete;
    CTrace_File             *m_pFiltered;
    volatile tINT32          m_lFilter_Progress; //0 .. 100 %

    CMEvent                  m_cFilter_Exit_Event; 
    CThShell::tTHREAD        m_hFilter_Thread;
    tBOOL                    m_bIsFilter_Thread;

    Bk::ITraceFilter        *m_pFilter;

    IP7_Trace               *m_iP7_Trace;
    IP7_Trace::hModule       m_hP7_TraceModule;
    IP7_Telemetry           *m_iP7_Tel;

    ////////////////////////////////////////////////////////////////////////////
    CWString                 m_cPath_Root;
    CWString                 m_cPath_Directory;
    CWString                 m_cPath_File_Data;
    CWString                 m_cPath_File_IDX;

    ////////////////////////////////////////////////////////////////////////////
    // Data file variables
    CPFile                   m_cFile_Data;
                            
    tUINT8                  *m_pData_Write;
    tUINT32                  m_dwData_Write_Used;
    tUINT64                  m_qwData_Write_Offset;
                            
    tUINT64                  m_qwData_Offset;
                     
    CBList<sTrace_Desc*>    *m_pDesc;
    CBList<CFormatter*>      m_cFormatters;
    CFormatter::sBuffer     *m_pFmtBuffer;
    CBList<sThreads*>        m_cThreads;
    CThreads_Tree            m_cThreads_Tree;
    CBList<sP7Trace_Module*> m_cModules;


    ////////////////////////////////////////////////////////////////////////////
    // Index file variables
    CPFile                   m_cFile_IDX;
                           
    tUINT64                 *m_pIDX_Write;
    tUINT32                  m_dwIDX_Write_Used;
    tUINT64                  m_qwIDX_Write_Start;
                           
    ////////////////////////////////////////////////////////////////////////////
    // service variables
    tLOCK                    m_hCS;
    Bk::IBNode              *m_pNode;
    CProperty               *m_pProp;
    tBOOL                    m_bSuccess;
    tBOOL                    m_bInitialized;
    tBOOL                    m_bIs_LittleEndian;
    tBOOL                    m_bIs_Extension;

    Bk::IStreamExTrace      *m_pExtra;

public:
    CTrace_File(Bk::IBNode *i_pNode, CProperty *i_pProp, const tXCHAR *i_pPath = NULL);

    virtual ~CTrace_File();

    Bk::eResult             Init_Reader(const tXCHAR *i_pFile_Name);

    Bk::eResult   __stdcall Get_Info(Bk::stStorageInfo *o_pInfo);

    Bk::eResult   __stdcall Get_Path(tXCHAR **o_pPath);

    Bk::eResult   __stdcall Get_Size(tUINT64 *o_pSize);

    Bk::eResult   __stdcall Get_Description(Bk::stStorageDesc &io_rDesc);


    Bk::eResult             Init_Writer(Bk::stStreamInfo *i_pInfo);
    Bk::eResult   __stdcall Uninit_Writer();
    Bk::eResult   __stdcall Put_Packet(Bk::stDataChunk *i_pChunk);

    Bk::eResult   __stdcall Put_Stream_Ex(Bk::IStreamEx *i_pEx);

    Bk::eResult   __stdcall Query_Interface(Bk::eInterface i_eId, void *&o_rUnknown);
                        
    Bk::eResult   __stdcall Delete();
                        
                                         
    Bk::eResult   __stdcall Get_Counters(tUINT64 **o_pCounters);
                        
    Bk::eResult   __stdcall Get_Count(tUINT64 *o_pCount);
                        
    Bk::eResult   __stdcall Get_Trace(tUINT64        i_qwIndex,
                                      Bk::stTrace *o_pItem,
                                      tBOOL          i_bFormat,
                                      CReader      *i_pReader
                                     );
                        
    Bk::eResult   __stdcall Set_Filter(Bk::ITraceFilter *i_pFilter, const tXCHAR *i_pPath, Bk::ITraceReader **o_pReader);
                        
    Bk::eResult   __stdcall Get_Filter_Status(tINT32 *o_pPercent);
                        
    Bk::eResult   __stdcall Clr_Filter(tBOOL i_bDelete);
                        
    void                    Filtering();
                           
    tBOOL                   Set_Directory(const tXCHAR *i_pDirectory);
                        
    Bk::eResult             Clone_Header(CTrace_File *i_pFile);

    //N.B. This function is not thread safe !!! you should use m_hCS critical
    //     section to protect data extraction
    tUINT8                 *Get_Raw_Trace(tUINT64 i_qwIndex, CReader *i_pReader);

    


    tINT32 __stdcall Add_Ref()
    {
        return ATOMIC_INC(&m_lReference);
    }

    tINT32 __stdcall Release()
    {
        tINT32 l_lResult = ATOMIC_DEC(&m_lReference);
        if ( 0 >= l_lResult )
        {
            delete this;
        }

        return l_lResult;
    }

    tBOOL Is_Failed()
    {
        return (! m_bSuccess);
    }

protected:
    inline Bk::eResult Format_Data(tUINT8 *i_pPacket, Bk::stTrace *o_pItem, tBOOL i_bFormat);

    tBOOL              Create_Files();
    inline Bk::eResult Process_Packet(sP7Ext_Header *i_pPacket);

    tBOOL              Update_Threads_Tree(sThread *i_pThread);
                       
    tBOOL              Write_Buffer(CPFile  &i_rFile, 
                                    tUINT8  *i_pBuffer,
                                    size_t   i_szBuffer, 
                                    tBOOL    i_bAppend = TRUE
                                   );
                       
    tBOOL              Read_Buffer(CPFile  &i_rFile, 
                                   tUINT64  i_qwOffset,
                                   tUINT8  *i_pBuffer,
                                   size_t   i_szBuffer, 
                                   size_t  *o_pRead
                                  );
                       

    static THSHELL_RET_TYPE THSHELL_CALL_TYPE Static_Filtering(void *i_pContext)
    {
        CTrace_File * l_pRoutine = (CTrace_File *)i_pContext;
        if (l_pRoutine)
        {
            l_pRoutine->Filtering();
        }

        CThShell::Cleanup();
        return THSHELL_RET_OK;
    } 
};

#endif //BK_P7_TRACE_H