// Copyright (c) 2012, 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/port.h"

#include <utility>

#include "include/dart_api.h"
#include "platform/utils.h"
#include "vm/dart_api_impl.h"
#include "vm/dart_api_message.h"
#include "vm/dart_entry.h"
#include "vm/isolate.h"
#include "vm/lockers.h"
#include "vm/message_handler.h"
#include "vm/message_snapshot.h"
#include "vm/os_thread.h"

namespace dart {

Mutex* PortMap::mutex_ = nullptr;
PortSet<PortMap::Entry>* PortMap::ports_ = nullptr;
Random* PortMap::prng_ = nullptr;

Dart_Port PortMap::AllocatePort() {
  Dart_Port result;

  ASSERT(mutex_->IsOwnedByCurrentThread());

  // Keep getting new values while we have an illegal port number or the port
  // number is already in use.
  do {
    // Ensure port ids are never valid object pointers so that reinterpreting
    // an object pointer as a port id never produces a used port id.
    const Dart_Port kMask2 = 0x3;

    // Ensure port ids are representable in JavaScript for the benefit of
    // vm-service clients such as Observatory.
    result = prng_->NextJSInt() | kMask2;

    // The two special marker ports are used for the hashset implementation and
    // cannot be used as actual ports.
    if (result == PortSet<Entry>::kFreePort ||
        result == PortSet<Entry>::kDeletedPort) {
      continue;
    }

    ASSERT(!static_cast<ObjectPtr>(static_cast<uword>(result))->IsWellFormed());
  } while (ports_->Contains(result));

  ASSERT(result != 0);
  ASSERT(!ports_->Contains(result));
  return result;
}

Dart_Port PortMap::CreatePort(PortHandler* handler) {
  ASSERT(handler != nullptr);
  PortMap::Locker ml;
  if (ports_ == nullptr) {
    return ILLEGAL_PORT;
  }

  const Dart_Port port = AllocatePort();
  if (auto ports = handler->ports(ml)) {
    ports->Insert(PortHandler::PortSetEntry{port});
  }
  ports_->Insert(Entry{port, handler});

  if (FLAG_trace_isolates) {
    OS::PrintErr(
        "[+] Opening port: \n"
        "\thandler:    %s\n"
        "\tport:       %" Pd64 "\n",
        handler->name(), port);
  }

  return port;
}

bool PortMap::ClosePort(Dart_Port port, PortHandler** port_handler) {
  if (port_handler != nullptr) *port_handler = nullptr;

  PortHandler* handler = nullptr;
  {
    PortMap::Locker ml;
    if (ports_ == nullptr) {
      return false;
    }
    auto it = ports_->TryLookup(port);
    if (it == ports_->end()) {
      return false;
    }
    Entry entry = *it;
    handler = entry.handler;
    ASSERT(handler != nullptr);

#if defined(DEBUG)
    handler->CheckAccess();
#endif

    it.Delete();
    ports_->Rebalance();

    if (auto ports = handler->ports(ml)) {
      auto isolate_it = ports->TryLookup(port);
      ASSERT(isolate_it != ports->end());
      isolate_it.Delete();
      ports->Rebalance();
    }
  }
  handler->OnPortClosed(port);
  if (port_handler != nullptr) *port_handler = handler;
  return true;
}

void PortMap::ClosePorts(MessageHandler* handler) {
  {
    PortMap::Locker ml;
    if (ports_ == nullptr) {
      return;
    }

    auto ports = handler->ports(ml);
    ASSERT(ports != nullptr);

    for (auto isolate_it = ports->begin(); isolate_it != ports->end();
         ++isolate_it) {
      auto it = ports_->TryLookup((*isolate_it).port);
      ASSERT(it != ports_->end());
      Entry entry = *it;
      ASSERT(entry.port == (*isolate_it).port);
      ASSERT(entry.handler == handler);
      it.Delete();
      isolate_it.Delete();
    }
    ASSERT(ports->IsEmpty());
    ports_->Rebalance();
  }
  handler->OnAllPortsClosed();
}

bool PortMap::PostMessage(std::unique_ptr<Message> message,
                          bool before_events) {
  Locker ml;
  if (ports_ == nullptr) {
    return false;
  }
  auto it = ports_->TryLookup(message->dest_port());
  if (it == ports_->end()) {
    // Ownership of external data remains with the poster.
    message->DropFinalizers();
    return false;
  }
  auto handler = (*it).handler;
  ASSERT(handler != nullptr);
  handler->PostMessage(std::move(message), before_events);
  return true;
}

#if defined(TESTING)
bool PortMap::PortExists(Dart_Port id) {
  Locker ml;
  if (ports_ == nullptr) {
    return false;
  }
  auto it = ports_->TryLookup(id);
  return it != ports_->end();
}

Isolate* PortMap::GetIsolate(Dart_Port id) {
  Locker ml;
  return GetIsolateLocked(ml, id);
}
#endif  // defined(TESTING)

Isolate* PortMap::GetIsolateLocked(const Locker& ml, Dart_Port id) {
  if (ports_ == nullptr) {
    return nullptr;
  }
  auto it = ports_->TryLookup(id);
  if (it == ports_->end()) {
    // Port does not exist.
    return nullptr;
  }

  auto handler = (*it).handler;
  return handler->isolate();
}

Dart_Port PortMap::GetOriginId(Dart_Port id) {
  Locker ml;
  if (ports_ == nullptr) {
    return ILLEGAL_PORT;
  }
  auto it = ports_->TryLookup(id);
  if (it == ports_->end()) {
    // Port does not exist.
    return ILLEGAL_PORT;
  }

  auto handler = (*it).handler;
  Isolate* isolate = handler->isolate();
  if (isolate == nullptr) {
    // Message handler is a native port instead of an isolate.
    return ILLEGAL_PORT;
  }
  return isolate->group()->id();
}

bool PortMap::IsOwned(Dart_Port id) {
  Locker ml;
  Isolate* isolate = GetIsolateLocked(ml, id);
  if (isolate == nullptr) {
    // Either the port is invalid, or the isolate has already shut down.
    return false;
  }
  return isolate->GetOwnerThread(&ml) != OSThread::kInvalidThreadId;
}

bool PortMap::IsOwnedByCurrentThread(Dart_Port id, bool require_permanent_pin) {
  Locker ml;
  Isolate* isolate = GetIsolateLocked(ml, id);
  if (isolate == nullptr) {
    // Either the port is invalid, or the isolate has already shut down.
    return false;
  }
  return isolate->GetOwnerThread(&ml) == OSThread::GetCurrentThreadId() &&
         (!require_permanent_pin || isolate->is_permanently_pinned());
}

IsolateAcquireResult PortMap::AcquireIsolateByControlPort(Dart_Port target_port,
                                                          Isolate** p_isolate) {
  ASSERT(p_isolate != nullptr);
  Locker ml;  // isolates are not exiting while we hold this lock
  if (ports_ == nullptr) {
    return IsolateAcquireResult::ISOLATE_NOT_AVAILABLE;
  }
  auto it = ports_->TryLookup(target_port);
  if (it == ports_->end()) {
    return IsolateAcquireResult::ISOLATE_NOT_AVAILABLE;
  }
  auto target_handler = (*it).handler;
  ASSERT(target_handler != nullptr);
  auto target_isolate = target_handler->isolate();

  if (!target_handler->isolate()->is_acquirable()) {
    return IsolateAcquireResult::HAS_MESSAGE_LOOP_OR_UNAVAILABLE;
  }

  if (!target_isolate->TryAcquireOwnership()) {
    return target_isolate->is_permanently_pinned()
               ? IsolateAcquireResult::PINNED_TO_ANOTHER_THREAD
               : IsolateAcquireResult::BUSY;
  }

  *p_isolate = target_isolate;
  return IsolateAcquireResult::SUCCESS;
}

#if defined(TESTING)
bool PortMap::HasPorts(MessageHandler* handler) {
  Locker ml;
  if (ports_ == nullptr) {
    return false;
  }
  // The MessageHandler::ports_ is only accessed by [PortMap], it is guarded
  // by the [PortMap::mutex_] we already hold.
  return !handler->ports_.IsEmpty();
}
#endif

bool PortMap::IsReceiverInThisIsolateGroupOrClosed(Dart_Port receiver,
                                                   IsolateGroup* group) {
  Locker ml;
  if (ports_ == nullptr) {
    // Port was closed.
    return true;
  }
  auto it = ports_->TryLookup(receiver);
  if (it == ports_->end()) {
    // Port was closed.
    return true;
  }
  auto isolate = (*it).handler->isolate();
  if (isolate == nullptr) {
    // Port belongs to a native port instead of an isolate.
    return false;
  }
  return isolate->group() == group;
}

void PortMap::Init() {
  if (mutex_ == nullptr) {
    mutex_ = new Mutex();
  }
  ASSERT(mutex_ != nullptr);
  if (prng_ == nullptr) {
    prng_ = new Random();
  }
  if (ports_ == nullptr) {
    ports_ = new PortSet<Entry>();
  }
}

void PortMap::Shutdown() {
  // Tell all handlers which are running their own thread pools to shutdown.
  for (auto& entry : *ports_) {
    entry.handler->Shutdown();
  }
}

void PortMap::Cleanup() {
  ASSERT(ports_ != nullptr);
  ASSERT(prng_ != nullptr);
  for (auto it = ports_->begin(); it != ports_->end(); ++it) {
    const auto& entry = *it;
    ASSERT(entry.handler != nullptr);
    delete entry.handler;
    it.Delete();
  }
  ports_->Rebalance();

  // Grab the mutex and delete the port set.
  Locker ml;
  delete prng_;
  prng_ = nullptr;
  delete ports_;
  ports_ = nullptr;
}

void PortMap::PrintPortsForMessageHandler(MessageHandler* handler,
                                          JSONStream* stream) {
#ifndef PRODUCT
  JSONObject jsobj(stream);
  jsobj.AddProperty("type", "_Ports");
  Object& msg_handler = Object::Handle();
  {
    JSONArray ports(&jsobj, "ports");
    SafepointMutexLocker ml(mutex_);
    if (ports_ == nullptr) {
      return;
    }
    for (auto& entry : *ports_) {
      if (entry.handler == handler) {
        JSONObject port(&ports);
        port.AddProperty("type", "_Port");
        port.AddPropertyF("name", "Isolate Port (%" Pd64 ")", entry.port);
        msg_handler = DartLibraryCalls::LookupHandler(entry.port);
        port.AddProperty("handler", msg_handler);
      }
    }
  }
#endif
}

PortHandler::~PortHandler() {}

#if defined(DEBUG)
void PortHandler::CheckAccess() const {
  // By default there is no checking.
}
#endif

}  // namespace dart
