// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/service_event.h"

#include "vm/debugger.h"
#include "vm/message_handler.h"
#include "vm/service_isolate.h"
#include "vm/timeline.h"

namespace dart {

#ifndef PRODUCT

ServiceEvent::ServiceEvent(EventKind event_kind)
    : ServiceEvent(nullptr, nullptr, event_kind) {}

ServiceEvent::ServiceEvent(IsolateGroup* isolate_group, EventKind event_kind)
    : ServiceEvent(isolate_group, nullptr, event_kind) {}

ServiceEvent::ServiceEvent(Isolate* isolate, EventKind event_kind)
    : ServiceEvent(isolate != nullptr ? isolate->group() : nullptr,
                   isolate,
                   event_kind) {}

ServiceEvent::ServiceEvent(IsolateGroup* isolate_group,
                           Isolate* isolate,
                           EventKind event_kind)
    : isolate_(isolate),
      isolate_group_(isolate_group),
      kind_(event_kind),
      flag_name_(nullptr),
      flag_new_value_(nullptr),
      previous_tag_(nullptr),
      updated_tag_(nullptr),
      embedder_kind_(nullptr),
      embedder_stream_id_(nullptr),
      breakpoint_(nullptr),
      top_frame_(nullptr),
      timeline_event_block_(nullptr),
      extension_rpc_(nullptr),
      exception_(nullptr),
      reload_error_(nullptr),
      spawn_token_(nullptr),
      spawn_error_(nullptr),
      at_async_jump_(false),
      inspectee_(nullptr),
      gc_stats_(nullptr),
      bytes_(nullptr),
      bytes_length_(0),
      milliseconds_overdue_(0),
      timestamp_(OS::GetCurrentTimeMillis()) {
  // VM-internal isolates should never post service events outside of the
  // following cases:
  //   - The `Isolate` service object uses a service event to represent the
  //     current running state of the isolate, so we need to allow for system
  //     isolates to create `kResume` and `kNone` events for this purpose. The
  //     `kResume` event represents a running isolate and the `kNone` event is
  //     returned for an isolate that has not yet been marked as runnable (see
  //     "pauseEvent" in Isolate::PrintJSON).
  //   - `kEmbedder` events relaying output printed on stdout/stderr may be
  //      posted from any isolate.
  //   - `kCpuSamples` events may be posted from any isolate.
  //   - `kTimerSignificantlyOverdue` events may be posted from any isolate.
  ASSERT(isolate == nullptr || !Isolate::IsVMInternalIsolate(isolate) ||
         (Isolate::IsVMInternalIsolate(isolate) &&
          (event_kind == ServiceEvent::kResume ||
           event_kind == ServiceEvent::kNone ||
           event_kind == ServiceEvent::kEmbedder ||
           event_kind == ServiceEvent::kCpuSamples ||
           event_kind == ServiceEvent::kTimerSignificantlyOverdue)));

  if ((event_kind == ServiceEvent::kPauseStart) ||
      (event_kind == ServiceEvent::kPauseExit)) {
    timestamp_ = isolate_->message_handler()->paused_timestamp();
  } else if (event_kind == ServiceEvent::kResume) {
    timestamp_ = isolate_->last_resume_timestamp();
  }
  ASSERT(timestamp_ > -1);
}

void ServiceEvent::UpdateTimestamp() {
  timestamp_ = OS::GetCurrentTimeMillis();
}

const char* ServiceEvent::KindAsCString() const {
  switch (kind()) {
    case kVMUpdate:
      return "VMUpdate";
    case kVMFlagUpdate:
      return "VMFlagUpdate";
    case kIsolateStart:
      return "IsolateStart";
    case kIsolateRunnable:
      return "IsolateRunnable";
    case kIsolateExit:
      return "IsolateExit";
    case kIsolateUpdate:
      return "IsolateUpdate";
    case kServiceExtensionAdded:
      return "ServiceExtensionAdded";
    case kIsolateReload:
      return "IsolateReload";
    case kPauseStart:
      return "PauseStart";
    case kPauseExit:
      return "PauseExit";
    case kPauseBreakpoint:
      return "PauseBreakpoint";
    case kPauseInterrupted:
      return "PauseInterrupted";
    case kPauseException:
      return "PauseException";
    case kPausePostRequest:
      return "PausePostRequest";
    case kNone:
      return "None";
    case kResume:
      return "Resume";
    case kBreakpointAdded:
      return "BreakpointAdded";
    case kBreakpointResolved:
      return "BreakpointResolved";
    case kBreakpointRemoved:
      return "BreakpointRemoved";
    case kBreakpointUpdated:
      return "BreakpointUpdated";
    case kGC:
      return "GC";  // TODO(koda): Change to GarbageCollected.
    case kInspect:
      return "Inspect";
    case kEmbedder:
      return embedder_kind();
    case kLogging:
      return "Logging";
    case kTimerSignificantlyOverdue:
      return "TimerSignificantlyOverdue";
    case kDebuggerSettingsUpdate:
      return "_DebuggerSettingsUpdate";
    case kIllegal:
      return "Illegal";
    case kExtension:
      return "Extension";
    case kTimelineEvents:
      return "TimelineEvents";
    case kTimelineStreamSubscriptionsUpdate:
      return "TimelineStreamSubscriptionsUpdate";
    case kUserTagChanged:
      return "UserTagChanged";
    case kCpuSamples:
      return "CpuSamples";
    default:
      UNREACHABLE();
      return "Unknown";
  }
}

const StreamInfo* ServiceEvent::stream_info() const {
  switch (kind()) {
    case kVMUpdate:
    case kVMFlagUpdate:
      return &Service::vm_stream;

    case kIsolateStart:
    case kIsolateRunnable:
    case kIsolateExit:
    case kIsolateUpdate:
    case kIsolateReload:
    case kServiceExtensionAdded:
      return &Service::isolate_stream;

    case kPauseStart:
    case kPauseExit:
    case kPauseBreakpoint:
    case kPauseInterrupted:
    case kPauseException:
    case kPausePostRequest:
    case kNone:
    case kResume:
    case kBreakpointAdded:
    case kBreakpointResolved:
    case kBreakpointRemoved:
    case kBreakpointUpdated:
    case kInspect:
    case kDebuggerSettingsUpdate:
      return &Service::debug_stream;

    case kGC:
      return &Service::gc_stream;

    case kLogging:
      return &Service::logging_stream;

    case kTimerSignificantlyOverdue:
      return &Service::timer_stream;

    case kExtension:
      return &Service::extension_stream;

    case kTimelineEvents:
    case kTimelineStreamSubscriptionsUpdate:
      return &Service::timeline_stream;

    case kEmbedder:
      return nullptr;

    case kCpuSamples:
    case kUserTagChanged:
      return &Service::profiler_stream;

    default:
      UNREACHABLE();
      return nullptr;
  }
}

const char* ServiceEvent::stream_id() const {
  const StreamInfo* stream = stream_info();
  if (stream == nullptr) {
    ASSERT(kind() == kEmbedder);
    return embedder_stream_id_;
  } else {
    return stream->id();
  }
}

void ServiceEvent::PrintJSON(JSONStream* js) const {
  JSONObject jsobj(js);
  PrintJSONHeader(&jsobj);
  if (kind() == kVMFlagUpdate) {
    jsobj.AddProperty("flag", flag_name());
    // For backwards compatibility, "new_value" is also provided.
    jsobj.AddProperty("newValue", flag_new_value());
  }
  if (kind() == kUserTagChanged) {
    jsobj.AddProperty("previousTag", previous_tag());
    jsobj.AddProperty("updatedTag", updated_tag());
  }
  if (kind() == kIsolateReload) {
    if (reload_error_ == nullptr) {
      jsobj.AddProperty("status", "success");
    } else {
      jsobj.AddProperty("status", "failure");
      jsobj.AddProperty("reloadFailureReason",
                        reload_error()->ToErrorCString());
    }
  }
  if (kind() == kServiceExtensionAdded) {
    ASSERT(extension_rpc_ != nullptr);
    jsobj.AddProperty("extensionRPC", extension_rpc_->ToCString());
  }
  if (kind() == kPauseBreakpoint) {
    JSONArray jsarr(&jsobj, "pauseBreakpoints");
    // TODO(rmacnak): If we are paused at more than one breakpoint,
    // provide it here.
    if (breakpoint() != nullptr) {
      jsarr.AddValue(breakpoint());
    }
  } else {
    if (breakpoint() != nullptr) {
      jsobj.AddProperty("breakpoint", breakpoint());
    }
  }
  if (kind() == kTimelineEvents) {
    jsobj.AddProperty("timelineEvents", timeline_event_block_);
  }
  if (kind() == kTimelineStreamSubscriptionsUpdate) {
    JSONArray arr(&jsobj, "updatedStreams");
    Timeline::PrintFlagsToJSONArray(&arr);
  }
  if (kind() == kDebuggerSettingsUpdate) {
    JSONObject jssettings(&jsobj, "_debuggerSettings");
    isolate()->debugger()->PrintSettingsToJSONObject(&jssettings);
  }
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (top_frame() != nullptr) {
    JSONObject jsFrame(&jsobj, "topFrame");
    top_frame()->PrintToJSONObject(&jsFrame);
    intptr_t index = 0;  // Avoid ambiguity in call to AddProperty.
    jsFrame.AddProperty("index", index);
  }
#endif
  if (exception() != nullptr) {
    jsobj.AddProperty("exception", *(exception()));
  }
  if (at_async_jump()) {
    jsobj.AddProperty("atAsyncSuspension", true);
  }
  if (inspectee() != nullptr) {
    jsobj.AddProperty("inspectee", *(inspectee()));
  }
  if (gc_stats() != nullptr) {
    jsobj.AddProperty("reason", Heap::GCReasonToString(gc_stats()->reason_));
    jsobj.AddProperty("gcType", Heap::GCTypeToString(gc_stats()->type_));
    isolate_group()->heap()->PrintToJSONObject(Heap::kNew, &jsobj);
    isolate_group()->heap()->PrintToJSONObject(Heap::kOld, &jsobj);
  }
  if (bytes() != nullptr) {
    jsobj.AddPropertyBase64("bytes", bytes(), bytes_length());
  }
  if (kind() == kLogging) {
    JSONObject logRecord(&jsobj, "logRecord");
    logRecord.AddProperty("type", "LogRecord");
    logRecord.AddProperty64("sequenceNumber", log_record_.sequence_number);
    logRecord.AddPropertyTimeMillis("time", log_record_.timestamp);
    logRecord.AddProperty64("level", log_record_.level);
    logRecord.AddProperty("loggerName", *(log_record_.name));
    logRecord.AddProperty("message", *(log_record_.message));
    logRecord.AddProperty("zone", *(log_record_.zone));
    logRecord.AddProperty("error", *(log_record_.error));
    logRecord.AddProperty("stackTrace", *(log_record_.stack_trace));
  }
  if (kind() == kTimerSignificantlyOverdue) {
    jsobj.AddPropertyF(
        "details",
        "A timer should have fired %" Pd
        " ms ago, but just fired now. This means that timers were blocked from "
        "firing for nearly %" Pd
        " ms. Some possible causes of this are: asynchronous operations were "
        "blocked by uninterruptible synchronous ones, or your program was "
        "frozen by the OS to conserve resources.",
        milliseconds_overdue(), milliseconds_overdue());
  }
  if (kind() == kExtension) {
    js->AppendSerializedObject("extensionData",
                               extension_event_.event_data->ToCString());
  }

  if (kind() == kCpuSamples) {
    JSONObject cpu_profile(&jsobj, "cpuSamples");
    cpu_profile_->PrintProfileJSON(&cpu_profile, /*include_code_samples=*/false,
                                   /*is_event=*/true);
  }
}

void ServiceEvent::PrintJSONHeader(JSONObject* jsobj) const {
  ASSERT(jsobj != nullptr);
  jsobj->AddProperty("type", "Event");
  jsobj->AddProperty("kind", KindAsCString());
  if (kind() == kExtension) {
    ASSERT(extension_event_.event_kind != nullptr);
    jsobj->AddProperty("extensionKind",
                       extension_event_.event_kind->ToCString());
  }
  if (isolate_group() != nullptr) {
    jsobj->AddProperty("isolateGroup", isolate_group());
  }
  if (isolate() != nullptr) {
    ASSERT(isolate_group() != nullptr);
    jsobj->AddProperty("isolate", isolate());
  }
  if (isolate_group() == nullptr && isolate() == nullptr) {
    jsobj->AddPropertyVM("vm");
  }
  ASSERT(timestamp_ != -1);
  jsobj->AddPropertyTimeMillis("timestamp", timestamp_);
}

#endif  // !PRODUCT

}  // namespace dart
