/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*****************************
 Windows implementation of probes, using xperf
 *****************************/
#include "perfprobe.h"

#include <evntrace.h>
#include <windows.h>
#include <wmistr.h>

namespace mozilla {
namespace probes {

#if defined(MOZ_LOGGING)
static LazyLogModule sProbeLog("SysProbe");
#  define LOG(x) MOZ_LOG(sProbeLog, mozilla::LogLevel::Debug, x)
#else
#  define LOG(x)
#endif

// Utility function
GUID CID_to_GUID(const nsCID& aCID) {
  GUID result;
  result.Data1 = aCID.m0;
  result.Data2 = aCID.m1;
  result.Data3 = aCID.m2;
  for (int i = 0; i < 8; ++i) {
    result.Data4[i] = aCID.m3[i];
  }
  return result;
}

// Implementation of Probe

Probe::Probe(const nsCID& aGUID, const nsACString& aName,
             ProbeManager* aManager)
    : mGUID(CID_to_GUID(aGUID)), mName(aName), mManager(aManager) {}

nsresult Probe::Trigger() {
  if (!(mManager->mIsActive)) {
    // Do not trigger if there is no session
    return NS_OK;
  }

  _EVENT_TRACE_HEADER event;
  ZeroMemory(&event, sizeof(event));
  event.Size = sizeof(event);
  event.Flags = WNODE_FLAG_TRACED_GUID;
  event.Guid = (const GUID)mGUID;
  event.Class.Type = 1;
  event.Class.Version = 0;
  event.Class.Level = TRACE_LEVEL_INFORMATION;

  ULONG result = TraceEvent(mManager->mSessionHandle, &event);

  LOG(("Probes: Triggered %s, %s, %ld", mName.get(),
       result == ERROR_SUCCESS ? "success" : "failure", result));

  nsresult rv;
  switch (result) {
    case ERROR_SUCCESS:
      rv = NS_OK;
      break;
    case ERROR_INVALID_FLAG_NUMBER:
    case ERROR_MORE_DATA:
    case ERROR_INVALID_PARAMETER:
      rv = NS_ERROR_INVALID_ARG;
      break;
    case ERROR_INVALID_HANDLE:
      rv = NS_ERROR_FAILURE;
      break;
    case ERROR_NOT_ENOUGH_MEMORY:
    case ERROR_OUTOFMEMORY:
      rv = NS_ERROR_OUT_OF_MEMORY;
      break;
    default:
      rv = NS_ERROR_UNEXPECTED;
  }
  return rv;
}

// Implementation of ProbeManager

ProbeManager::~ProbeManager() {
  // If the manager goes out of scope, stop the session.
  if (mIsActive && mRegistrationHandle) {
    StopSession();
  }
}

ProbeManager::ProbeManager(const nsCID& aApplicationUID,
                           const nsACString& aApplicationName)
    : mIsActive(false),
      mApplicationUID(aApplicationUID),
      mApplicationName(aApplicationName),
      mSessionHandle(0),
      mRegistrationHandle(0),
      mInitialized(false) {
#if defined(MOZ_LOGGING)
  char cidStr[NSID_LENGTH];
  aApplicationUID.ToProvidedString(cidStr);
  LOG(("ProbeManager::Init for application %s, %s", mApplicationName.get(),
       cidStr));
#endif
}

// Note: The Windows API is just a little bit scary there.
// The only way to obtain the session handle is to
//- ignore the session handle obtained from RegisterTraceGuids
//- pass a callback
//- in that callback, request the session handle through
//      GetTraceLoggerHandle and some opaque value received by the callback

ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, PVOID aContext,
                             ULONG* aReserved, PVOID aBuffer) {
  ProbeManager* context = (ProbeManager*)aContext;
  switch (aRequestCode) {
    case WMI_ENABLE_EVENTS: {
      context->mIsActive = true;
      TRACEHANDLE sessionHandle = GetTraceLoggerHandle(aBuffer);
      // Note: We only accept one handle
      if ((HANDLE)sessionHandle == INVALID_HANDLE_VALUE) {
        ULONG result = GetLastError();
        LOG(("Probes: ControlCallback failed, %lu", result));
        return result;
      } else if (context->mIsActive && context->mSessionHandle &&
                 context->mSessionHandle != sessionHandle) {
        LOG(
            ("Probes: Can only handle one context at a time, "
             "ignoring activation"));
        return ERROR_SUCCESS;
      } else {
        context->mSessionHandle = sessionHandle;
        LOG(("Probes: ControlCallback activated"));
        return ERROR_SUCCESS;
      }
    }

    case WMI_DISABLE_EVENTS:
      context->mIsActive = false;
      context->mSessionHandle = 0;
      LOG(("Probes: ControlCallback deactivated"));
      return ERROR_SUCCESS;

    default:
      LOG(("Probes: ControlCallback does not know what to do with %d",
           aRequestCode));
      return ERROR_INVALID_PARAMETER;
  }
}

already_AddRefed<Probe> ProbeManager::GetProbe(const nsCID& aEventUID,
                                               const nsACString& aEventName) {
  RefPtr<Probe> result(new Probe(aEventUID, aEventName, this));
  mAllProbes.AppendElement(result);
  return result.forget();
}

nsresult ProbeManager::StartSession() { return StartSession(mAllProbes); }

nsresult ProbeManager::StartSession(nsTArray<RefPtr<Probe>>& aProbes) {
  const size_t probesCount = aProbes.Length();
  _TRACE_GUID_REGISTRATION* probes = new _TRACE_GUID_REGISTRATION[probesCount];
  for (unsigned int i = 0; i < probesCount; ++i) {
    const Probe* probe = aProbes[i];
    const Probe* probeX = static_cast<const Probe*>(probe);
    probes[i].Guid = (LPCGUID)&probeX->mGUID;
  }
  ULONG result =
      RegisterTraceGuids(&ControlCallback
                         /*RequestAddress: Sets mSessions appropriately.*/,
                         this
                         /*RequestContext: Passed to ControlCallback*/,
                         (LPGUID)&mApplicationUID
                         /*ControlGuid:    Tracing GUID
                          the cast comes from MSDN examples*/
                         ,
                         probesCount
                         /*GuidCount:      Number of probes*/,
                         probes
                         /*TraceGuidReg:   Probes registration*/,
                         nullptr
                         /*MofImagePath:   Must be nullptr, says MSDN*/,
                         nullptr
                         /*MofResourceName:Must be nullptr, says MSDN*/,
                         &mRegistrationHandle
                         /*RegistrationHandle: Handler.
                          used only for unregistration*/
      );
  delete[] probes;
  if (NS_WARN_IF(result != ERROR_SUCCESS)) {
    return NS_ERROR_UNEXPECTED;
  }
  return NS_OK;
}

nsresult ProbeManager::StopSession() {
  LOG(("Probes: Stopping measures"));
  if (mSessionHandle != 0) {
    ULONG result = UnregisterTraceGuids(mSessionHandle);
    mSessionHandle = 0;
    if (result != ERROR_SUCCESS) {
      return NS_ERROR_INVALID_ARG;
    }
  }
  return NS_OK;
}

}  // namespace probes
}  // namespace mozilla
