//
// Copyright (c) 2002-2010 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// debug.cpp: Debugging utilities.

#include "common/debug.h"

#include <stdarg.h>

#include <cstdio>
#include <fstream>
#include <iostream>
#include <vector>

#include "common/angleutils.h"
#include "common/platform.h"
#include "common/Optional.h"

namespace gl
{

namespace
{

class FormattedString final : angle::NonCopyable
{
  public:
    FormattedString(const char *format, va_list vararg) : mFormat(format)
    {
        va_copy(mVarArg, vararg);
    }

    const char *c_str() { return str().c_str(); }

    const std::string &str()
    {
        if (!mMessage.valid())
        {
            mMessage = FormatString(mFormat, mVarArg);
        }
        return mMessage.value();
    }

    size_t length()
    {
        c_str();
        return mMessage.value().length();
    }

  private:
    const char *mFormat;
    va_list mVarArg;
    Optional<std::string> mMessage;
};
enum DebugTraceOutputType
{
   DebugTraceOutputTypeNone,
   DebugTraceOutputTypeSetMarker,
   DebugTraceOutputTypeBeginEvent
};

DebugAnnotator *g_debugAnnotator = nullptr;

void output(bool traceInDebugOnly, MessageType messageType, DebugTraceOutputType outputType,
            const char *format, va_list vararg)
{
    if (DebugAnnotationsActive())
    {
        static std::vector<char> buffer(512);
        size_t len = FormatStringIntoVector(format, vararg, buffer);
        std::wstring formattedWideMessage(buffer.begin(), buffer.begin() + len);

        ASSERT(g_debugAnnotator != nullptr);
        switch (outputType)
        {
          case DebugTraceOutputTypeNone:
            break;
          case DebugTraceOutputTypeBeginEvent:
            g_debugAnnotator->beginEvent(formattedWideMessage.c_str());
            break;
          case DebugTraceOutputTypeSetMarker:
            g_debugAnnotator->setMarker(formattedWideMessage.c_str());
            break;
        }
    }

    FormattedString formattedMessage(format, vararg);

    if (messageType == MESSAGE_ERR)
    {
        std::cerr << formattedMessage.c_str();
#if !defined(NDEBUG) && defined(_MSC_VER)
        OutputDebugStringA(formattedMessage.c_str());
#endif  // !defined(NDEBUG) && defined(_MSC_VER)
    }

#if defined(ANGLE_ENABLE_DEBUG_TRACE)
#if defined(NDEBUG)
    if (traceInDebugOnly)
    {
        return;
    }
#endif  // NDEBUG
    static std::ofstream file(TRACE_OUTPUT_FILE, std::ofstream::app);
    if (file)
    {
        file.write(formattedMessage.c_str(), formattedMessage.length());
        file.flush();
    }

#if defined(ANGLE_ENABLE_DEBUG_TRACE_TO_DEBUGGER)
    OutputDebugStringA(formattedMessage.c_str());
#endif  // ANGLE_ENABLE_DEBUG_TRACE_TO_DEBUGGER

#endif  // ANGLE_ENABLE_DEBUG_TRACE
}

} // namespace

bool DebugAnnotationsActive()
{
#if defined(ANGLE_ENABLE_DEBUG_ANNOTATIONS)
    return g_debugAnnotator != nullptr && g_debugAnnotator->getStatus();
#else
    return false;
#endif
}

void InitializeDebugAnnotations(DebugAnnotator *debugAnnotator)
{
    UninitializeDebugAnnotations();
    g_debugAnnotator = debugAnnotator;
}

void UninitializeDebugAnnotations()
{
    // Pointer is not managed.
    g_debugAnnotator = nullptr;
}

void trace(bool traceInDebugOnly, MessageType messageType, const char *format, ...)
{
    va_list vararg;
    va_start(vararg, format);
    output(traceInDebugOnly, messageType, DebugTraceOutputTypeSetMarker, format, vararg);
    va_end(vararg);
}

ScopedPerfEventHelper::ScopedPerfEventHelper(const char* format, ...)
{
#if !defined(ANGLE_ENABLE_DEBUG_TRACE)
    if (!DebugAnnotationsActive())
    {
        return;
    }
#endif // !ANGLE_ENABLE_DEBUG_TRACE
    va_list vararg;
    va_start(vararg, format);
    output(true, MESSAGE_EVENT, DebugTraceOutputTypeBeginEvent, format, vararg);
    va_end(vararg);
}

ScopedPerfEventHelper::~ScopedPerfEventHelper()
{
    if (DebugAnnotationsActive())
    {
        g_debugAnnotator->endEvent();
    }
}

}
