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

// In-house headers:
#include "MICmnLog.h"
#include "MICmnLogMediumFile.h"
#include "MIDriverMgr.h"
#include "MICmnResources.h"
#include "MIUtilDateTimeStd.h"

//++ ------------------------------------------------------------------------------------
// Details: CMICmnLog constructor.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMICmnLog::CMICmnLog()
    : m_bEnabled(false)
    , m_bInitializingATM(false)
{
    // Do not use this constructor, use Initialize()
}

//++ ------------------------------------------------------------------------------------
// Details: CMICmnLog destructor.
// Type:    Method.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
CMICmnLog::~CMICmnLog()
{
    Shutdown();
}

//++ ------------------------------------------------------------------------------------
// Details: Initialize resources for *this Logger.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::Initialize()
{
    m_clientUsageRefCnt++;

    if (m_bInitialized)
        return MIstatus::success;

    ClrErrorDescription();

    // Mediums set inside because explicitly initing in MIDriverMain.cpp causes compile errors with CAtlFile
    CMICmnLogMediumFile &rFileLog(CMICmnLogMediumFile::Instance());
    bool bOk = RegisterMedium(rFileLog);
    if (bOk)
    {
        // Set the Log trace file's header
        const CMIUtilString &rCR(rFileLog.GetLineReturn());
        CMIUtilDateTimeStd date;
        CMIUtilString msg;
        msg = CMIUtilString::Format("%s\n", CMIDriverMgr::Instance().GetAppVersion().c_str());
        CMIUtilString logHdr(msg);
        msg = CMIUtilString::Format(MIRSRC(IDS_LOG_MSG_CREATION_DATE), date.GetDate().c_str(), date.GetTime().c_str(), rCR.c_str());
        logHdr += msg;
        msg = CMIUtilString::Format(MIRSRC(IDS_LOG_MSG_FILE_LOGGER_PATH), rFileLog.GetFileNamePath().c_str(), rCR.c_str());
        logHdr += msg;

        bOk = rFileLog.SetHeaderTxt(logHdr);

        // Note log file medium's status is not available until we write at least once to the file (so just write the title 1st line)
        m_bInitializingATM = true;
        CMICmnLog::WriteLog(".");
        if (!rFileLog.IsOk())
        {
            const CMIUtilString msg(
                CMIUtilString::Format(MIRSRC(IDS_LOG_ERR_FILE_LOGGER_DISABLED), rFileLog.GetErrorDescription().c_str()));
            CMICmnLog::WriteLog(msg);
        }
        m_bInitializingATM = false;
    }

    m_bInitialized = bOk;

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Release resources for *this Logger.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::Shutdown()
{
    if (--m_clientUsageRefCnt > 0)
        return MIstatus::success;

    if (!m_bInitialized)
        return MIstatus::success;

    ClrErrorDescription();

    const bool bOk = UnregisterMediumAll();

    m_bInitialized = bOk;

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Enabled or disable *this Logger from writing any data to registered clients.
// Type:    Method.
// Args:    vbYes   - (R) True = Logger enabled, false = disabled.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::SetEnabled(const bool vbYes)
{
    m_bEnabled = vbYes;

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve state whether *this Logger is enabled writing data to registered clients.
// Type:    Method.
// Args:    None.
// Return:  True = Logger enable.
//          False = disabled.
// Throws:  None.
//--
bool
CMICmnLog::GetEnabled() const
{
    return m_bEnabled;
}

//++ ------------------------------------------------------------------------------------
// Details: Unregister all the Mediums registered with *this Logger.
// Type:    Method.
// Args:    None.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::UnregisterMediumAll()
{
    MapMediumToName_t::const_iterator it = m_mapMediumToName.begin();
    for (; it != m_mapMediumToName.end(); it++)
    {
        IMedium *pMedium = (*it).first;
        pMedium->Shutdown();
    }

    m_mapMediumToName.clear();

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Register a Medium with *this Logger.
// Type:    Method.
// Args:    vrMedium    - (R) The medium to register.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::RegisterMedium(const IMedium &vrMedium)
{
    if (HaveMediumAlready(vrMedium))
        return MIstatus::success;

    IMedium *pMedium = const_cast<IMedium *>(&vrMedium);
    if (!pMedium->Initialize())
    {
        const CMIUtilString &rStrMedName(pMedium->GetName());
        const CMIUtilString &rStrMedErr(pMedium->GetError());
        SetErrorDescription(CMIUtilString::Format(MIRSRC(IDS_LOG_MEDIUM_ERR_INIT), rStrMedName.c_str(), rStrMedErr.c_str()));
        return MIstatus::failure;
    }

    MapPairMediumToName_t pr(pMedium, pMedium->GetName());
    m_mapMediumToName.insert(pr);

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: Query the Logger to see if a medium is already registered.
// Type:    Method.
// Args:    vrMedium    - (R) The medium to query.
// Return:  True - registered.
//          False - not registered.
// Throws:  None.
//--
bool
CMICmnLog::HaveMediumAlready(const IMedium &vrMedium) const
{
    IMedium *pMedium = const_cast<IMedium *>(&vrMedium);
    const MapMediumToName_t::const_iterator it = m_mapMediumToName.find(pMedium);
    if (it != m_mapMediumToName.end())
        return true;

    return false;
}

//++ ------------------------------------------------------------------------------------
// Details: Unregister a medium from the Logger.
// Type:    Method.
// Args:    vrMedium    - (R) The medium to unregister.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::UnregisterMedium(const IMedium &vrMedium)
{
    IMedium *pMedium = const_cast<IMedium *>(&vrMedium);
    m_mapMediumToName.erase(pMedium);

    return MIstatus::success;
}

//++ ------------------------------------------------------------------------------------
// Details: The callee client uses this function to write to the Logger. The data to be
//          written is given out to all the mediums registered. The verbosity type parameter
//          indicates to the medium(s) the type of data or message given to it. The medium has
//          modes of verbosity and depending on the verbosity set determines which writes
//          go in to the logger.
//          The logger must be initialized successfully before a write to any registered
//          can be carried out.
// Type:    Method.
// Args:    vData       - (R) The data to write to the logger.
//          veType      - (R) Verbosity type.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::Write(const CMIUtilString &vData, const ELogVerbosity veType)
{
    if (!m_bInitialized && !m_bInitializingATM)
        return MIstatus::success;
    if (m_bRecursiveDive)
        return MIstatus::success;
    if (!m_bEnabled)
        return MIstatus::success;

    m_bRecursiveDive = true;

    MIuint cnt = 0;
    MIuint cntErr = 0;
    {
        MapMediumToName_t::const_iterator it = m_mapMediumToName.begin();
        while (it != m_mapMediumToName.end())
        {
            IMedium *pMedium = (*it).first;
            const CMIUtilString &rNameMedium = (*it).second;
            MIunused(rNameMedium);
            if (pMedium->Write(vData, veType))
                cnt++;
            else
                cntErr++;

            // Next
            ++it;
        }
    }

    bool bOk = MIstatus::success;
    const MIuint mediumCnt = m_mapMediumToName.size();
    if ((cnt == 0) && (mediumCnt > 0))
    {
        SetErrorDescription(MIRSRC(IDS_LOG_MEDIUM_ERR_WRITE_ANY));
        bOk = MIstatus::failure;
    }
    if (bOk && (cntErr != 0))
    {
        SetErrorDescription(MIRSRC(IDS_LOG_MEDIUM_ERR_WRITE_MEDIUMFAIL));
        bOk = MIstatus::failure;
    }

    m_bRecursiveDive = false;

    return bOk;
}

//++ ------------------------------------------------------------------------------------
// Details: Short cut function call to write only to the Log file.
//          The logger must be initialized successfully before a write to any registered
//          can be carried out.
// Type:    Static.
// Args:    vData   - (R) The data to write to the logger.
// Return:  MIstatus::success - Functional succeeded.
//          MIstatus::failure - Functional failed.
// Throws:  None.
//--
bool
CMICmnLog::WriteLog(const CMIUtilString &vData)
{
    return CMICmnLog::Instance().Write(vData, CMICmnLog::eLogVerbosity_Log);
}

//++ ------------------------------------------------------------------------------------
// Details: Retrieve a string detailing the last error.
// Type:    Method.
// Args:    None,
// Return:  CMIUtilString.
// Throws:  None.
//--
const CMIUtilString &
CMICmnLog::GetErrorDescription() const
{
    return m_strMILastErrorDescription;
}

//++ ------------------------------------------------------------------------------------
// Details: Set the internal description of the last error.
// Type:    Method.
// Args:    (R) String containing a description of the last error.
// Return:  None.
// Throws:  None.
//--
void
CMICmnLog::SetErrorDescription(const CMIUtilString &vrTxt) const
{
    m_strMILastErrorDescription = vrTxt;
}

//++ ------------------------------------------------------------------------------------
// Details: Clear the last error.
// Type:    None.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
void
CMICmnLog::ClrErrorDescription() const
{
    m_strMILastErrorDescription = CMIUtilString("");
}
