// Copyright (c) 2012-2019 Intel Corporation
//
// 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 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.

#if defined(MFX_DISPATCHER_LOG)

#include "mfx_dispatcher_log.h"
#include "mfxstructures.h"
#include <windows.h>
#if defined(DISPATCHER_LOG_REGISTER_EVENT_PROVIDER)
#include <evntprov.h>
#include <winmeta.h>
#endif
#include <stdarg.h>
#include <algorithm>
#include <string>
#include <sstream>

struct CodeStringTable
{
    int code;
    const char *string;
} LevelStrings []= 
{
    {DL_INFO,  "INFO:   "},
    {DL_WRN,   "WARNING:"},
    {DL_ERROR, "ERROR:  "}
};

#define DEFINE_CODE(code)\
    {code, #code}

static CodeStringTable StringsOfImpl[] = {
    DEFINE_CODE(MFX_IMPL_AUTO),       
    DEFINE_CODE(MFX_IMPL_SOFTWARE),
    DEFINE_CODE(MFX_IMPL_HARDWARE),     
    DEFINE_CODE(MFX_IMPL_AUTO_ANY),     
    DEFINE_CODE(MFX_IMPL_HARDWARE_ANY), 
    DEFINE_CODE(MFX_IMPL_HARDWARE2), 
    DEFINE_CODE(MFX_IMPL_HARDWARE3), 
    DEFINE_CODE(MFX_IMPL_HARDWARE4), 

    DEFINE_CODE(MFX_IMPL_UNSUPPORTED)
};

static CodeStringTable StringsOfImplVIA[] = {
    DEFINE_CODE(MFX_IMPL_VIA_ANY),
    DEFINE_CODE(MFX_IMPL_VIA_D3D9),
    DEFINE_CODE(MFX_IMPL_VIA_D3D11),
};

static CodeStringTable StringsOfStatus[] =
{
    DEFINE_CODE(MFX_ERR_NONE                    ),
    DEFINE_CODE(MFX_ERR_UNKNOWN                 ),
    DEFINE_CODE(MFX_ERR_NULL_PTR                ),
    DEFINE_CODE(MFX_ERR_UNSUPPORTED             ),
    DEFINE_CODE(MFX_ERR_MEMORY_ALLOC            ),
    DEFINE_CODE(MFX_ERR_NOT_ENOUGH_BUFFER       ),
    DEFINE_CODE(MFX_ERR_INVALID_HANDLE          ),
    DEFINE_CODE(MFX_ERR_LOCK_MEMORY             ),
    DEFINE_CODE(MFX_ERR_NOT_INITIALIZED         ),
    DEFINE_CODE(MFX_ERR_NOT_FOUND               ),
    DEFINE_CODE(MFX_ERR_MORE_DATA               ),
    DEFINE_CODE(MFX_ERR_MORE_SURFACE            ),
    DEFINE_CODE(MFX_ERR_ABORTED                 ),
    DEFINE_CODE(MFX_ERR_DEVICE_LOST             ),
    DEFINE_CODE(MFX_ERR_INCOMPATIBLE_VIDEO_PARAM),
    DEFINE_CODE(MFX_ERR_INVALID_VIDEO_PARAM     ),
    DEFINE_CODE(MFX_ERR_UNDEFINED_BEHAVIOR      ),
    DEFINE_CODE(MFX_ERR_DEVICE_FAILED           ),
    DEFINE_CODE(MFX_WRN_IN_EXECUTION            ),
    DEFINE_CODE(MFX_WRN_DEVICE_BUSY             ),
    DEFINE_CODE(MFX_WRN_VIDEO_PARAM_CHANGED     ),
    DEFINE_CODE(MFX_WRN_PARTIAL_ACCELERATION    ),
    DEFINE_CODE(MFX_WRN_INCOMPATIBLE_VIDEO_PARAM),
    DEFINE_CODE(MFX_WRN_VALUE_NOT_CHANGED       ),
    DEFINE_CODE(MFX_WRN_OUT_OF_RANGE            ),
    
};

#define CODE_TO_STRING(code,  array)\
    CodeToString(code, array, sizeof(array)/sizeof(array[0]))

const char* CodeToString(int code, CodeStringTable array[], int len )
{
    for (int i = 0 ; i < len; i++)
    {
        if (array[i].code == code)
            return array[i].string;
    }
    return "undef";
}

std::string DispatcherLog_GetMFXImplString(int impl)
{
    std::string str1 = CODE_TO_STRING(impl & ~(-MFX_IMPL_VIA_ANY), StringsOfImpl);
    std::string str2 = CODE_TO_STRING(impl & (-MFX_IMPL_VIA_ANY), StringsOfImplVIA);

    return str1 + (str2 == "undef" ? "" : "|"+str2);
}

const char *DispatcherLog_GetMFXStatusString(int sts)
{
    return CODE_TO_STRING(sts, StringsOfStatus);
}

//////////////////////////////////////////////////////////////////////////


void DispatcherLogBracketsHelper::Write(const char * str, ...)
{
    va_list argsptr;
    va_start(argsptr, str);
    DispatchLog::get().Write(m_level, m_opcode, str, argsptr);
    va_end(argsptr);
}

void DispatchLogBlockHelper::Write(const char * str, ...)
{
    va_list argsptr;
    va_start(argsptr, str);
    DispatchLog::get().Write(m_level, DL_EVENT_START, str, argsptr);
    va_end(argsptr);
}

DispatchLogBlockHelper::~DispatchLogBlockHelper()
{
    DispatchLog::get().Write(m_level, DL_EVENT_STOP, NULL, NULL);
}

//////////////////////////////////////////////////////////////////////////

DispatchLog::DispatchLog()
 : m_DispatcherLogSink(DL_SINK_PRINTF)
{

}

void   DispatchLog::SetSink(int nSink, IMsgHandler * pHandler)
{
    DetachAllSinks();
    AttachSink(nSink, pHandler);
}

void   DispatchLog::AttachSink(int nsink, IMsgHandler *pHandler)
{
    m_DispatcherLogSink |= nsink;
    if (NULL != pHandler)
        m_Recepients.push_back(pHandler);
}

void   DispatchLog::DetachSink(int nsink, IMsgHandler *pHandler)
{
    if (nsink & DL_SINK_IMsgHandler)
    {
        m_Recepients.remove(pHandler);
    }

    m_DispatcherLogSink &= ~nsink;
}

void   DispatchLog::ExchangeSink(int nsink, IMsgHandler *oldHdl, IMsgHandler *newHdl)
{
    if (nsink & DL_SINK_IMsgHandler)
    {
        std::list<IMsgHandler*> :: iterator it = std::find(m_Recepients.begin(), m_Recepients.end(), oldHdl);
        
        //cannot exchange in that case
        if (m_Recepients.end() == it)
            return;

        *it = newHdl;
    }
}


void   DispatchLog::DetachAllSinks()
{
    m_Recepients.clear();
    m_DispatcherLogSink = DL_SINK_NULL;
}

void   DispatchLog::Write(int level, int opcode, const char * msg, va_list argptr)
{
    int sinkTable[] =
    {
        DL_SINK_PRINTF,
        DL_SINK_IMsgHandler,
    };

    for (size_t i = 0; i < sizeof(sinkTable) / sizeof(sinkTable[0]); i++)
    {
        switch(m_DispatcherLogSink & sinkTable[i])
        {
            case  DL_SINK_NULL:
                break;
            
            case DL_SINK_PRINTF:
            {
                char msg_formated[8048] = {0};

                if (NULL != msg && level != DL_LOADED_LIBRARY)
                {
#if _MSC_VER >= 1400
                    vsprintf_s(msg_formated, sizeof(msg_formated)/sizeof(msg_formated[0]), msg, argptr);
#else
                    vsnprintf(msg_formated, sizeof(msg_formated)/sizeof(msg_formated[0]), msg, argptr);
#endif
                    //TODO: improve this , add opcode handling
                    printf("%s %s", CODE_TO_STRING(level, LevelStrings), msg_formated);
                }
                break;
            }

            case DL_SINK_IMsgHandler:
            {
                std::list<IMsgHandler*>::iterator it;

                for (it = m_Recepients.begin(); it != m_Recepients.end(); ++it)
                {
                    (*it)->Write(level, opcode, msg, argptr);
                }
                break;
            }
        }
    }
}

#if defined(DISPATCHER_LOG_REGISTER_EVENT_PROVIDER)
class ETWHandler : public IMsgHandler
{
public:
    ETWHandler(const wchar_t * guid_str)
      : m_bUseFormatter(DISPATCHER_LOG_USE_FORMATING)
      , m_EventHandle()
      , m_bProviderEnable()
    {
        GUID rguid = GUID_NULL;
        if (FAILED(CLSIDFromString(guid_str, &rguid)))
        {
            return;
        }
        
        EventRegister(&rguid, NULL, NULL, &m_EventHandle);

        m_bProviderEnable = 0 != EventProviderEnabled(m_EventHandle, 1,0);
    }

    ~ETWHandler()
    {
        if (m_EventHandle)
        {
            EventUnregister(m_EventHandle);
        }
    }

    virtual void Write(int level, int opcode, const char * msg, va_list argptr)
    {
        //event not registered
        if (0==m_EventHandle)
        {
            return;
        }
        if (!m_bProviderEnable)
        {
            return;
        }
        if (level == DL_LOADED_LIBRARY)
        {
            return;
        }

        char msg_formated[1024];
        EVENT_DESCRIPTOR descriptor;
        EVENT_DATA_DESCRIPTOR data_descriptor;

        EventDescZero(&descriptor);
        
        descriptor.Opcode = (UCHAR)opcode; 
        descriptor.Level  = (UCHAR)level;
        
        if (m_bUseFormatter)
        {
            if (NULL != msg)
            {
#if _MSC_VER >= 1400
                vsprintf_s(msg_formated, sizeof (msg_formated) / sizeof (msg_formated[0]), msg, argptr);
#else
                vsnprintf(msg_formated, sizeof (msg_formated) / sizeof (msg_formated[0]), msg, argptr);
#endif
                EventDataDescCreate(&data_descriptor, msg_formated, (ULONG)(strlen(msg_formated) + 1));
            }else
            {
                EventDataDescCreate(&data_descriptor, NULL, 0);
            }
        }else
        {
            //TODO: non formated events supports under zbb 
        }

        EventWrite(m_EventHandle, &descriptor, 1, &data_descriptor);
    }

protected:

    //we may not use formatter in some cases described in dispatch_log macro
    //it significantly increases performance by eliminating any vsprintf operations
    bool      m_bUseFormatter;
    //consumer is attached, dispatcher trace to reduce formating overhead 
    //submits event only if consumer attached
    bool      m_bProviderEnable;
    REGHANDLE m_EventHandle;
};
//


IMsgHandler *ETWHandlerFactory::GetSink(const wchar_t* sguid)
{
    _storage_type::iterator it;
    it = m_storage.find(sguid);
    if (it == m_storage.end())
    {
        ETWHandler * handler = new ETWHandler(sguid);
        _storage_type::_Pairib it_bool = m_storage.insert(_storage_type::value_type(sguid, handler));
        it = it_bool.first;
    }

   return it->second;
}

ETWHandlerFactory::~ETWHandlerFactory()
{
    for each(_storage_type::value_type val in m_storage)
    {
        delete val.second;
    }
}

class EventRegistrator : public IMsgHandler
{
    const wchar_t * m_sguid;
public:
    EventRegistrator(const wchar_t* sguid = DISPATCHER_LOG_EVENT_GUID)
        :m_sguid(sguid)
    {
        DispatchLog::get().AttachSink( DL_SINK_IMsgHandler
                                      , this);
    }

    virtual void Write(int level, int opcode, const char * msg, va_list argptr)
    {
        //we cannot call attach sink since we may have been called from iteration
        //we axchanging preserve that placeholding
        IMsgHandler * pSink = NULL;
        DispatchLog::get().ExchangeSink(DL_SINK_IMsgHandler,
                                        this,
                                        pSink = ETWHandlerFactory::get().GetSink(m_sguid));
        //need to call only once here all next calls will be done inside dispatcherlog
        if (NULL != pSink)
        {
            pSink->Write(level, opcode, msg, argptr);
        }
    }
};
#endif

template <class TSink>
class SinkRegistrator
{
};

#if defined(DISPATCHER_LOG_REGISTER_EVENT_PROVIDER)
template <>
class SinkRegistrator<ETWHandlerFactory>
{
public:
    SinkRegistrator(const wchar_t* sguid = DISPATCHER_LOG_EVENT_GUID)
    {
        DispatchLog::get().AttachSink( DL_SINK_IMsgHandler
                                      , ETWHandlerFactory::get().GetSink(sguid));
    }
};
#endif

#if defined(DISPATCHER_LOG_REGISTER_FILE_WRITER)
template <>
class SinkRegistrator<FileSink>
{
public:
    SinkRegistrator()
    {
        DispatchLog::get().AttachSink( DL_SINK_IMsgHandler, &FileSink::get(DISPACTHER_LOG_FW_PATH));
    }
};

void FileSink::Write(int level, int /*opcode*/, const char * msg, va_list argptr)
{
    if (NULL != m_hdl && NULL != msg)
    {
        fprintf(m_hdl, "%s", CODE_TO_STRING(level, LevelStrings));
        vfprintf(m_hdl, msg, argptr);
    }
}
#endif

//////////////////////////////////////////////////////////////////////////
//singletons initialization section


#ifdef  DISPATCHER_LOG_REGISTER_EVENT_PROVIDER
    static SinkRegistrator<ETWHandlerFactory> g_registrator1;
#endif


#ifdef DISPATCHER_LOG_REGISTER_FILE_WRITER
    static SinkRegistrator<FileSink> g_registrator2;
#endif


#endif//(MFX_DISPATCHER_LOG)