////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                     /
// 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.                   /
//                                                                                                                     /
// 2012-2021 (c) Baical                                                                                                /
//                                                                                                                     /
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// This header file provide printing to text file                              /
////////////////////////////////////////////////////////////////////////////////

#ifndef CLTEXTFILE_H
#define CLTEXTFILE_H

#include "IFile.h"
#include "PFile.h"

#define TXT_FILE_DEF_FILES_COUNT  0
#define TXT_FILE_EXT              TM("txt")
#define TXT_FILE_WRITE_PERIOD     1000u

////////////////////////////////////////////////////////////////////////////////
class CClTextFile
    : public CClTextSink
{
    enum eRolling
    {
        EROLLING_NONE,
        EROLLING_MEGABYTES,
        EROLLING_HOURS,
        EROLLING_TIME,

        EROLLING_MAX
    };

    CPFile             m_cFile;
    CWString           m_pDir;
    eRolling           m_eRolling;
    tUINT64            m_qwRolling_Value;
    tUINT32            m_dwFile_Tick;
    tUINT32            m_dwWrite_Tick;
    tUINT64            m_qwFile_Size;
    tUINT32            m_dwFiles_Max_Count;
    tUINT64            m_qwFiles_Max_Size;
    CBList<CWString*>  m_cFiles;
    tXCHAR            *m_pBuffer;
    size_t             m_szBuffer;
    size_t             m_szBufferOffs;
    CUintList          m_cSecondsList;
    tUINT32            m_uiSecond;
    tUINT32            m_dwIndex;


public:
    ////////////////////////////////////////////////////////////////////////////
    //CClTextFile
    CClTextFile()
        : m_eRolling(EROLLING_NONE)
        , m_qwRolling_Value(0ull)
        , m_dwFile_Tick(0u)
        , m_dwWrite_Tick(0ull)
        , m_qwFile_Size(0ull)
        , m_dwFiles_Max_Count(0u)
        , m_qwFiles_Max_Size(0u)
        , m_pBuffer(NULL)
        , m_szBuffer(0x10000)
        , m_szBufferOffs(0)
        , m_dwIndex(0)
    {
        m_dwWrite_Tick = GetTickCount();
        m_uiSecond     = GetSecondOfDay(); 
    }

    ////////////////////////////////////////////////////////////////////////////
    //~CClTextFile
    virtual ~CClTextFile()
    {
        m_cFile.Write((tUINT8*)m_pBuffer, m_szBufferOffs * sizeof(tXCHAR), FALSE);
        m_cFile.Close(TRUE);

        m_cFiles.Clear(TRUE);
        if (m_pBuffer)
        {
            free(m_pBuffer);
            m_pBuffer = NULL;
        }
        m_szBufferOffs = 0;

        m_cSecondsList.Clear(TRUE);
    }

    ////////////////////////////////////////////////////////////////////////////
    //Initialize
    virtual eClient_Status Initialize(tXCHAR **i_pArgs, tINT32 i_iCount)
    {
        eClient_Status  l_eReturn  = ECLIENT_STATUS_OK;
        tXCHAR         *l_pArgV    = NULL;
        pAList_Cell     l_pStart   = NULL;
        tUINT32         l_dwDirLen = 0;

        m_pDir.Realloc(4096);

        m_pBuffer = (tXCHAR*)malloc(m_szBuffer * sizeof(tXCHAR));
        if (!m_pBuffer)
        {
            PRINTF(TM("P7:Can't allocate memory\n"));
            l_eReturn = ECLIENT_STATUS_INTERNAL_ERROR;
            P7_Set_Last_Error(eP7_Error_MemoryAllocation);
            goto l_lblExit;
        }

        //unicode marker
    #ifdef UTF8_ENCODING
        m_pBuffer[0]   = (char)0xEF;
        m_pBuffer[1]   = (char)0xBB;
        m_pBuffer[2]   = (char)0xBF;
        m_szBufferOffs = 3;
        m_qwFile_Size  = 3;
    #else
        m_pBuffer[0]   = 0xFEFF;
        m_szBufferOffs = 1;
        m_qwFile_Size  = sizeof(tXCHAR);
    #endif                             

        ////////////////////////////////////////////////////////////////////////////
        //get maximum count of the log files
        l_pArgV = Get_Argument_Text_Value(i_pArgs, i_iCount,
                                          (tXCHAR*)CLIENT_COMMAND_LINE_FILES_COUNT_MAX
                                         );
        if (l_pArgV)
        {
            m_dwFiles_Max_Count = (tUINT32)PStrToInt(l_pArgV);

            if (4096 < m_dwFiles_Max_Count)
            {
                m_dwFiles_Max_Count = TXT_FILE_DEF_FILES_COUNT;
            }
        }


        l_pArgV = Get_Argument_Text_Value(i_pArgs, i_iCount,
                                          (tXCHAR*)CLIENT_COMMAND_LINE_FILES_SIZE_MAX
                                         );
        if (l_pArgV)
        {
            m_qwFiles_Max_Size = (tUINT64)PStrToInt(l_pArgV) * 1048576ull;
        }


        ////////////////////////////////////////////////////////////////////////////
        //get path to storage
        l_pArgV = Get_Argument_Text_Value(i_pArgs, i_iCount,
                                          (tXCHAR*)CLIENT_COMMAND_LINE_DIR
                                         );
        if (l_pArgV)
        {
            m_pDir.Set(l_pArgV);
        }
        else
        {
            CProc::Get_Process_Path(m_pDir.Get(), m_pDir.Max_Length());
            m_pDir.Append(1, TM("/P7logs/"));
        }

        if (FALSE == CFSYS::Directory_Exists(m_pDir.Get()))
        {
            if (FALSE == CFSYS::Directory_Create(m_pDir.Get()))
            {
                P7_Set_Last_Error(eP7_Error_FolderCreation);
                PRINTF(TM("P7:Can't create directory: %s\n"), l_pArgV);
                l_eReturn = ECLIENT_STATUS_NOT_ALLOWED;
                goto l_lblExit;
            }
        }

        ////////////////////////////////////////////////////////////////////////////
        //enumerate files & sorting ....
        if (    (TXT_FILE_DEF_FILES_COUNT != m_dwFiles_Max_Count)
             || (m_qwFiles_Max_Size)
           )
        {
            CFSYS::Enumerate_Files(&m_cFiles, &m_pDir, TM("*.") TXT_FILE_EXT, 0);

            l_pStart   = NULL;
            l_dwDirLen = m_pDir.Length();

            while ((l_pStart = m_cFiles.Get_Next(l_pStart)))
            {
                pAList_Cell l_pMin  = l_pStart;
                pAList_Cell l_pIter = l_pStart;

                while ((l_pIter = m_cFiles.Get_Next(l_pIter)))
                {
                    CWString *l_pPathM = m_cFiles.Get_Data(l_pMin);
                    CWString *l_pPathI = m_cFiles.Get_Data(l_pIter);
                    tXCHAR   *l_pNameM = l_pPathM->Get() + l_dwDirLen;
                    tXCHAR   *l_pNameI = l_pPathI->Get() + l_dwDirLen;

                    if (0 < PStrICmp(l_pNameM, l_pNameI))
                    {
                        l_pMin = l_pIter;
                    }
                }

                if (l_pMin != l_pStart)
                {
                    m_cFiles.Extract(l_pMin);
                    m_cFiles.Put_After(m_cFiles.Get_Prev(l_pStart), l_pMin);
                    l_pStart = l_pMin;
                }
            } //while (l_pStart = m_cFiles.Get_Next(l_pStart))
        }

        ////////////////////////////////////////////////////////////////////////////
        //create file
        l_eReturn = Create_File();
        if (ECLIENT_STATUS_OK != l_eReturn)
        {
            P7_Set_Last_Error(eP7_Error_FileCreation);
            PRINTF(TM("P7:File creation failed"));
            goto l_lblExit;
        }


        ////////////////////////////////////////////////////////////////////////////
        //get rolling type
        l_pArgV = Get_Argument_Text_Value(i_pArgs, i_iCount,
                                          (tXCHAR*)CLIENT_COMMAND_LINE_FILE_ROLLING
                                         );
        if (l_pArgV)
        {
            CWString l_cRolling(l_pArgV);
            tUINT32  l_dwLen  = l_cRolling.Length();
            tBOOL    l_bError = FALSE;

            if (3 <= l_dwLen)
            {
                tXCHAR *l_pSuffix = l_cRolling.Get() + l_dwLen - 2;
                if (0 == PStrICmp(l_pSuffix, TM("hr")))
                {
                    m_eRolling = EROLLING_HOURS;
                }
                else if (0 == PStrICmp(l_pSuffix, TM("mb")))
                {
                    m_eRolling = EROLLING_MEGABYTES;
                }
                else if (0 == PStrICmp(l_pSuffix, TM("tm")))
                {
                    m_eRolling = EROLLING_TIME;
                }
                else
                {
                    l_bError = TRUE;
                }

                //cut off suffix
                l_cRolling.Trim(l_dwLen - 2);
            }
            else
            {
                l_bError = TRUE;
            }

            if (FALSE == l_bError)
            {
                if (m_eRolling != EROLLING_TIME)
                {
                    m_qwRolling_Value = PStrToInt(l_cRolling.Get());

                    if (0 >= m_qwRolling_Value)
                    {
                        m_eRolling = EROLLING_NONE;
                        l_bError   = TRUE;
                    }
                }
                else
                {
                    if (!Parse_Rolling_Time(l_cRolling.Get()))
                    {
                        m_eRolling = EROLLING_NONE;
                        l_bError   = TRUE;
                    }
                }
            }

            if (l_bError)
            {
                PRINTF(TM("P7:Rolling value is not correct = %s\n"), l_cRolling.Get());
            }

            if (EROLLING_MEGABYTES == m_eRolling)
            {
                m_qwRolling_Value *= 0x100000ULL; //megabytes
            }
            else if (EROLLING_HOURS == m_eRolling)
            {
                if (m_qwRolling_Value > 1000)
                {
                    PRINTF(TM("P7:Rolling value is more than 1000 hours, cutting\n"));
                    m_qwRolling_Value = 1000;
                }

                m_qwRolling_Value *= 3600000ULL; //milliseconds in one hour
            }
        }//if (l_pArgV)

    l_lblExit:
        return l_eReturn;
    }


    ////////////////////////////////////////////////////////////////////////////////
    //Parse_Rolling_Time
    tBOOL Parse_Rolling_Time(const tXCHAR *i_pTime)
    {
        const tXCHAR *l_pIter   = i_pTime;
        const tXCHAR *l_pBegin  = i_pTime;
        tBOOL         l_bReturn = TRUE;

        while (*l_pIter)
        {
            tUINT32 l_uiSeconds = 0;
            tUINT32 l_uiValue   = 0;

            l_pBegin = l_pIter; //scan hours
            while (    (*l_pIter >= TM('0')) 
                    && (*l_pIter <= TM('9'))
                    )
            {
                l_uiValue = l_uiValue * 10 + (*l_pIter - TM('0'));
                l_pIter ++;
            }

            if (l_pBegin == l_pIter) //no digits
            {
                l_bReturn = FALSE;
                break;
            }

            l_uiSeconds = l_uiValue * 3600; 
                    
            if (*l_pIter != TM(':')) //error
            {
                l_bReturn = FALSE;
                break;
            }

            l_pIter++;

            l_pBegin  = l_pIter; //scan minutes
            l_uiValue = 0;
            while (    (*l_pIter >= TM('0'))
                    && (*l_pIter <= TM('9'))
                    )
            {
                l_uiValue = l_uiValue * 10 + (*l_pIter - TM('0'));
                l_pIter ++;
            }

            if (l_pBegin == l_pIter) //no digits
            {
                l_bReturn = FALSE;
                break;
            }

            l_uiSeconds += l_uiValue * 60; 

            m_cSecondsList.Add_After(m_cSecondsList.Get_Last(), l_uiSeconds);

            if (*l_pIter == TM(','))
            {
                l_pIter++;
            }
        }

        if (!m_cSecondsList.Count())
        {
            l_bReturn = FALSE;
        }

        return l_bReturn;
    }//Parse_Rolling_Time


    ////////////////////////////////////////////////////////////////////////////
    //Log
    virtual eClient_Status Log(const CClTextSink::sLog &i_rRawLog, 
                               const tXCHAR            *i_pFmtLog, 
                               size_t                   i_szFmtLog
                              )
    {
        UNUSED_ARG(i_rRawLog);
        if (    ((i_szFmtLog + 16) > (m_szBuffer - m_szBufferOffs))
             || (CTicks::Difference(GetTickCount(), m_dwWrite_Tick) >= TXT_FILE_WRITE_PERIOD)
           )
        {
            if ((i_szFmtLog + 16) > m_szBuffer)
            {
                if (m_szBufferOffs)
                {
                    m_cFile.Write((tUINT8*)m_pBuffer, m_szBufferOffs * sizeof(tXCHAR), FALSE);
                    m_szBufferOffs = 0u;
                }

                m_cFile.Write((tUINT8*)i_pFmtLog, i_szFmtLog * sizeof(tXCHAR), FALSE);
                m_qwFile_Size += (i_szFmtLog + 2) * sizeof(tXCHAR);
                *(m_pBuffer)     = TM('\r');
                *(m_pBuffer + 1) = TM('\n');
                m_szBufferOffs  += 2;

                TryRoll();

                return ECLIENT_STATUS_OK;
            }
            else
            {
                m_cFile.Write((tUINT8*)m_pBuffer, m_szBufferOffs * sizeof(tXCHAR), FALSE);
                m_szBufferOffs = 0;

                TryRoll();
            }

            m_dwWrite_Tick = GetTickCount();
        }

        memcpy(m_pBuffer + m_szBufferOffs, i_pFmtLog, sizeof(tXCHAR) * i_szFmtLog);
        m_szBufferOffs += i_szFmtLog;
        *(m_pBuffer + m_szBufferOffs)     = TM('\r');
        *(m_pBuffer + m_szBufferOffs + 1) = TM('\n');
        m_szBufferOffs += 2;
        m_qwFile_Size  += (i_szFmtLog + 2) * sizeof(tXCHAR);

        return ECLIENT_STATUS_OK;
    }

    ////////////////////////////////////////////////////////////////////////////
    // TryRoll                                      
    eClient_Status TryRoll()
    {
        if (   (    (EROLLING_HOURS == m_eRolling)
                 && (CTicks::Difference(GetTickCount(), m_dwFile_Tick) > m_qwRolling_Value)
               )
            || (    (EROLLING_MEGABYTES == m_eRolling)
                 && (m_qwRolling_Value <= m_qwFile_Size)
               )
           )
        {
            Roll();
        }
        else if (EROLLING_TIME == m_eRolling)
        {
            tUINT32     l_uiNextSecond = GetSecondOfDay(); 
            pAList_Cell l_pEl          = NULL;

            while ((l_pEl = m_cSecondsList.Get_Next(l_pEl)))
            {
                tUINT32 l_uiIem = m_cSecondsList.Get_Data(l_pEl);
                if (    (m_uiSecond <= l_uiIem)
                     && (l_uiNextSecond > l_uiIem)
                   )
                {
                    Roll();
                    break;
                }
            }

            m_uiSecond = l_uiNextSecond;
        }

        return ECLIENT_STATUS_OK; 
    }// TryRoll


    ////////////////////////////////////////////////////////////////////////////
    // Roll                                      
    void Roll()
    {
        m_cFile.Write((tUINT8*)m_pBuffer, m_szBufferOffs * sizeof(tXCHAR), TRUE);
        m_cFile.Close(TRUE);
        m_qwFile_Size  = 0ull;
        m_szBufferOffs = 0u;
        Create_File();
        m_dwWrite_Tick = GetTickCount();
    }// Roll

    ////////////////////////////////////////////////////////////////////////////
    // Create_File                                      
    eClient_Status Create_File()
    {
        eClient_Status l_eReturn = ECLIENT_STATUS_OK;
        tUINT32        l_dwYear  = 0;
        tUINT32        l_dwMonth = 0;
        tUINT32        l_dwDay   = 0; 
        tUINT32        l_dwHr    = 0; 
        tUINT32        l_dwMin   = 0;
        tUINT32        l_dwSec   = 0; 
        tUINT32        l_dwmSec  = 0;
        CWString       l_cFilePath;
        tXCHAR         l_pFile_Name[64];

        m_cFile.Close(TRUE);

        CSys::Get_DateTime(l_dwYear, 
                           l_dwMonth, 
                           l_dwDay, 
                           l_dwHr, 
                           l_dwMin, 
                           l_dwSec, 
                           l_dwmSec
                          );
        l_dwmSec = 0;
        do
        {
            PSPrint(l_pFile_Name, 
                    LENGTH(l_pFile_Name), 
                    TM("/%04d%02d%02d-%02d%02d%02d%03d.") TXT_FILE_EXT,
                    l_dwYear, 
                    l_dwMonth,
                    l_dwDay,
                    l_dwHr,
                    l_dwMin,
                    l_dwSec,
                    m_dwIndex
                   );

            l_cFilePath.Set(m_pDir.Get());
            l_cFilePath.Append(1, l_pFile_Name);

            m_dwIndex ++;
        } while (TRUE == CFSYS::File_Exists(l_cFilePath.Get()));

        if (FALSE == m_cFile.Open(l_cFilePath.Get(), 
                                  IFile::ECREATE | IFile::EACCESS_WRITE | IFile::ESHARE_READ
                                 )
           )
        {
            PRINTF(TM("P7:Can't create file: %s"), l_cFilePath.Get());
            l_eReturn = ECLIENT_STATUS_INTERNAL_ERROR;
        }
        else 
        {
            m_cFiles.Add_After(m_cFiles.Get_Last(), new CWString(l_cFilePath.Get()));

            if (TXT_FILE_DEF_FILES_COUNT != m_dwFiles_Max_Count)
            {
                while (m_dwFiles_Max_Count < m_cFiles.Count())
                {
                    pAList_Cell l_pEl   = m_cFiles.Get_First();
                    CWString   *l_pPath = m_cFiles.Get_Data(l_pEl);
                    if (l_pPath)
                    {
                        if (!CFSYS::Delete_File(l_pPath->Get()))
                        {
                            PRINTF(TM("P7:Can't delete file: %s"), l_pPath->Get());
                        }
                    }

                    m_cFiles.Del(l_pEl, TRUE);
                }
            }

            if (0 != m_qwFiles_Max_Size)
            {
                pAList_Cell l_pEl = NULL;
                tUINT64     l_qwTotalSize = 0;
                tUINT64     l_qwFileSize = 0;

                while ((l_pEl = m_cFiles.Get_Next(l_pEl)))
                {
                    CWString *l_pPath = m_cFiles.Get_Data(l_pEl);
                    if (l_pPath)
                    {
                        l_qwTotalSize += CFSYS::Get_File_Size(l_pPath->Get());
                    }
                }

                while (l_qwTotalSize > (m_qwFiles_Max_Size))
                {
                    pAList_Cell l_pEl   = m_cFiles.Get_First();
                    CWString   *l_pPath = m_cFiles.Get_Data(l_pEl);
                    if (l_pPath)
                    {
                        l_qwFileSize = CFSYS::Get_File_Size(l_pPath->Get());

                        if (l_qwFileSize < l_qwTotalSize)
                        {
                            l_qwTotalSize -= l_qwFileSize;
                        }
                        else
                        {
                            l_qwTotalSize = 0ull;
                        }

                        if (!CFSYS::Delete_File(l_pPath->Get()))
                        {
                            PRINTF(TM("Can't delete file: %s"), l_pPath->Get());
                        }
                    }

                    m_cFiles.Del(l_pEl, TRUE);
                }
            }
        }

        m_dwFile_Tick = GetTickCount();

        return l_eReturn;
    }// Create_File

    virtual eClient_Status DumpBuffers()
    {
        if (m_szBufferOffs)
        {
            m_cFile.Write((tUINT8*)m_pBuffer, m_szBufferOffs * sizeof(tXCHAR), TRUE);
            m_szBufferOffs = 0;
        }

        return ECLIENT_STATUS_OK;
    }

};

#endif //CLTEXTFILE_H
