#include "inspector_io.h"
#if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR

    #include "inspector_socket_server.h"
    #include "env.h"
    #include "node.h"
    //cjh #include "node_crypto.h"
    #include "node_mutex.h"
    #include "v8-inspector.h"
    #include "util.h"
    #include "zlib.h"

    #include "libplatform/libplatform.h"

    #include <sstream>
//cjh #include <unicode/unistr.h>

    #include <string.h>
    #include <vector>

    #include "base/UTF8.h" //cjh added

namespace node {
namespace inspector {
namespace {
using AsyncAndAgent = std::pair<uv_async_t, Agent *>;
using v8_inspector::StringBuffer;
using v8_inspector::StringView;

template <typename Transport>
using TransportAndIo = std::pair<Transport *, InspectorIo *>;

std::string GetProcessTitle() {
    char title[2048];
    int err = uv_get_process_title(title, sizeof(title));
    if (err == 0) {
        return title;
    } else {
        // Title is too long, or could not be retrieved.
        return "Node.js";
    }
}

std::string ScriptPath(uv_loop_t *loop, const std::string &script_name) {
    std::string script_path;

    if (!script_name.empty()) {
        uv_fs_t req;
        req.ptr = nullptr;
        if (0 == uv_fs_realpath(loop, &req, script_name.c_str(), nullptr)) {
            CHECK_NE(req.ptr, nullptr);
            script_path = std::string(static_cast<char *>(req.ptr));
        }
        uv_fs_req_cleanup(&req);
    }

    return script_path;
}

// UUID RFC: https://www.ietf.org/rfc/rfc4122.txt
// Used ver 4 - with numbers
std::string GenerateID() {
    //  uint16_t buffer[8];

    //cjh same uuid
    uint16_t buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};

    //cjh
    //cjh  CHECK(crypto::EntropySource(reinterpret_cast<unsigned char*>(buffer),
    //                              sizeof(buffer)));

    char uuid[256];
    snprintf(uuid, sizeof(uuid), "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
             buffer[0],                     // time_low
             buffer[1],                     // time_mid
             buffer[2],                     // time_low
             (buffer[3] & 0x0fff) | 0x4000, // time_hi_and_version
             (buffer[4] & 0x3fff) | 0x8000, // clk_seq_hi clk_seq_low
             buffer[5],                     // node
             buffer[6],
             buffer[7]);
    return uuid;
}

std::string StringViewToUtf8(const StringView &view) {
    if (view.is8Bit()) {
        return std::string(reinterpret_cast<const char *>(view.characters8()),
                           view.length());
    }
    const uint16_t *source = view.characters16();

    std::u16string u16Str((char16_t *)source);
    std::string ret;
    cc::StringUtils::UTF16ToUTF8(u16Str, ret);
    return ret;
    //  const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
    //  static_assert(sizeof(*source) == sizeof(*unicodeSource),
    //                "sizeof(*source) == sizeof(*unicodeSource)");
    //
    //  size_t result_length = view.length() * sizeof(*source);
    //  std::string result(result_length, '\0');
    //cjh  UnicodeString utf16(unicodeSource, view.length());
    //  // ICU components for std::string compatibility are not enabled in build...
    //  bool done = false;
    //  while (!done) {
    //    CheckedArrayByteSink sink(&result[0], result_length);
    //    utf16.toUTF8(sink);
    //    result_length = sink.NumberOfBytesAppended();
    //    result.resize(result_length);
    //    done = !sink.Overflowed();
    //  }
    //  return result;

    return "";
}

void HandleSyncCloseCb(uv_handle_t *handle) {
    *static_cast<bool *>(handle->data) = true;
}

int CloseAsyncAndLoop(uv_async_t *async) {
    bool is_closed = false;
    async->data = &is_closed;
    uv_close(reinterpret_cast<uv_handle_t *>(async), HandleSyncCloseCb);
    while (!is_closed)
        uv_run(async->loop, UV_RUN_ONCE);
    async->data = nullptr;
    return uv_loop_close(async->loop);
}

// Delete main_thread_req_ on async handle close
void ReleasePairOnAsyncClose(uv_handle_t *async) {
    AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first,
                                            reinterpret_cast<uv_async_t *>(async));
    delete pair;
}

} // namespace

std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string &message) {
    //cjh  UnicodeString utf16 =
    //      UnicodeString::fromUTF8(StringPiece(message.data(), message.length()));

    std::u16string u16Str;
    cc::StringUtils::UTF8ToUTF16(message, u16Str);

    //  StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()),
    //                  utf16.length());

    StringView view(reinterpret_cast<const uint16_t *>(u16Str.c_str()),
                    u16Str.length());
    return StringBuffer::create(view);
}

class IoSessionDelegate : public InspectorSessionDelegate {
public:
    explicit IoSessionDelegate(InspectorIo *io) : io_(io) {}
    bool WaitForFrontendMessageWhilePaused() override;
    void SendMessageToFrontend(const v8_inspector::StringView &message) override;

private:
    InspectorIo *io_;
};

// Passed to InspectorSocketServer to handle WS inspector protocol events,
// mostly session start, message received, and session end.
class InspectorIoDelegate : public node::inspector::SocketServerDelegate {
public:
    InspectorIoDelegate(InspectorIo *io, const std::string &script_path,
                        const std::string &script_name, bool wait);
    // Calls PostIncomingMessage() with appropriate InspectorAction:
    //   kStartSession
    bool StartSession(int session_id, const std::string &target_id) override;
    //   kSendMessage
    void MessageReceived(int session_id, const std::string &message) override;
    //   kEndSession
    void EndSession(int session_id) override;

    std::vector<std::string> GetTargetIds() override;
    std::string GetTargetTitle(const std::string &id) override;
    std::string GetTargetUrl(const std::string &id) override;
    bool IsConnected() { return connected_; }
    void ServerDone() override {
        io_->ServerDone();
    }

private:
    InspectorIo *io_;
    bool connected_;
    int session_id_;
    const std::string script_name_;
    const std::string script_path_;
    const std::string target_id_;
    bool waiting_;
};

void InterruptCallback(v8::Isolate *, void *agent) {
    InspectorIo *io = static_cast<Agent *>(agent)->io();
    if (io != nullptr)
        io->DispatchMessages();
}

class DispatchMessagesTask : public v8::Task {
public:
    explicit DispatchMessagesTask(Agent *agent) : agent_(agent) {}

    void Run() override {
        InspectorIo *io = agent_->io();
        if (io != nullptr)
            io->DispatchMessages();
    }

private:
    Agent *agent_;
};

InspectorIo::InspectorIo(Environment *env, v8::Platform *platform,
                         const std::string &path, const DebugOptions &options,
                         bool wait_for_connect)
: options_(options),
  thread_(),
  delegate_(nullptr),
  state_(State::kNew),
  parent_env_(env),
  thread_req_(),
  platform_(platform),
  dispatching_messages_(false),
  session_id_(0),
  script_name_(path),
  wait_for_connect_(wait_for_connect),
  port_(-1) {
    main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()});
    CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first,
                              InspectorIo::MainThreadReqAsyncCb));
    uv_unref(reinterpret_cast<uv_handle_t *>(&main_thread_req_->first));
    CHECK_EQ(0, uv_sem_init(&thread_start_sem_, 0));
}

InspectorIo::~InspectorIo() {
    uv_sem_destroy(&thread_start_sem_);
    uv_close(reinterpret_cast<uv_handle_t *>(&main_thread_req_->first),
             ReleasePairOnAsyncClose);
}

bool InspectorIo::Start() {
    CHECK_EQ(state_, State::kNew);
    CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0);
    uv_sem_wait(&thread_start_sem_);

    if (state_ == State::kError) {
        return false;
    }
    state_ = State::kAccepting;
    if (wait_for_connect_) {
        DispatchMessages();
    }
    return true;
}

void InspectorIo::Stop() {
    CHECK(state_ == State::kAccepting || state_ == State::kConnected);
    Write(TransportAction::kKill, 0, StringView());
    int err = uv_thread_join(&thread_);
    CHECK_EQ(err, 0);
    state_ = State::kShutDown;
    DispatchMessages();
}

bool InspectorIo::IsConnected() {
    return delegate_ != nullptr && delegate_->IsConnected();
}

bool InspectorIo::IsStarted() {
    return platform_ != nullptr;
}

void InspectorIo::WaitForDisconnect() {
    if (state_ == State::kAccepting)
        state_ = State::kDone;
    if (state_ == State::kConnected) {
        state_ = State::kShutDown;
        Write(TransportAction::kStop, 0, StringView());
        SE_LOGD("Waiting for the debugger to disconnect...\n");
        parent_env_->inspector_agent()->RunMessageLoop();
    }
}

// static
void InspectorIo::ThreadMain(void *io) {
    static_cast<InspectorIo *>(io)->ThreadMain<InspectorSocketServer>();
}

// static
template <typename Transport>
void InspectorIo::IoThreadAsyncCb(uv_async_t *async) {
    TransportAndIo<Transport> *transport_and_io =
        static_cast<TransportAndIo<Transport> *>(async->data);
    if (transport_and_io == nullptr) {
        return;
    }
    Transport *transport = transport_and_io->first;
    InspectorIo *io = transport_and_io->second;
    MessageQueue<TransportAction> outgoing_message_queue;
    io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_message_queue);
    for (const auto &outgoing : outgoing_message_queue) {
        switch (std::get<0>(outgoing)) {
            case TransportAction::kKill:
                transport->TerminateConnections();
                // Fallthrough
            case TransportAction::kStop:
                transport->Stop(nullptr);
                break;
            case TransportAction::kSendMessage:
                std::string message = StringViewToUtf8(std::get<2>(outgoing)->string());
                transport->Send(std::get<1>(outgoing), message);
                break;
        }
    }
}

template <typename Transport>
void InspectorIo::ThreadMain() {
    uv_loop_t loop;
    loop.data = nullptr;
    int err = uv_loop_init(&loop);
    CHECK_EQ(err, 0);
    thread_req_.data = nullptr;
    err = uv_async_init(&loop, &thread_req_, IoThreadAsyncCb<Transport>);
    CHECK_EQ(err, 0);
    std::string script_path = ScriptPath(&loop, script_name_);
    InspectorIoDelegate delegate(this, script_path, script_name_,
                                 wait_for_connect_);
    delegate_ = &delegate;
    Transport server(&delegate, &loop, options_.host_name(), options_.port());
    TransportAndIo<Transport> queue_transport(&server, this);
    thread_req_.data = &queue_transport;
    if (!server.Start()) {
        state_ = State::kError; // Safe, main thread is waiting on semaphore
        CHECK_EQ(0, CloseAsyncAndLoop(&thread_req_));
        uv_sem_post(&thread_start_sem_);
        return;
    }
    port_ = server.Port(); // Safe, main thread is waiting on semaphore.
    if (!wait_for_connect_) {
        uv_sem_post(&thread_start_sem_);
    }
    uv_run(&loop, UV_RUN_DEFAULT);
    thread_req_.data = nullptr;
    CHECK_EQ(uv_loop_close(&loop), 0);
    delegate_ = nullptr;
}

template <typename ActionType>
bool InspectorIo::AppendMessage(MessageQueue<ActionType> *queue,
                                ActionType action, int session_id,
                                std::unique_ptr<StringBuffer> buffer) {
    Mutex::ScopedLock scoped_lock(state_lock_);
    bool trigger_pumping = queue->empty();
    queue->push_back(std::make_tuple(action, session_id, std::move(buffer)));
    return trigger_pumping;
}

template <typename ActionType>
void InspectorIo::SwapBehindLock(MessageQueue<ActionType> *vector1,
                                 MessageQueue<ActionType> *vector2) {
    Mutex::ScopedLock scoped_lock(state_lock_);
    vector1->swap(*vector2);
}

void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id,
                                      const std::string &message) {
    if (AppendMessage(&incoming_message_queue_, action, session_id,
                      Utf8ToStringView(message))) {
        Agent *agent = main_thread_req_->second;
        v8::Isolate *isolate = parent_env_->isolate();
        platform_->GetForegroundTaskRunner(isolate)->PostTask(std::make_unique<DispatchMessagesTask>(agent));
        isolate->RequestInterrupt(InterruptCallback, agent);
        CHECK_EQ(0, uv_async_send(&main_thread_req_->first));
    }
    NotifyMessageReceived();
}

std::vector<std::string> InspectorIo::GetTargetIds() const {
    return delegate_ ? delegate_->GetTargetIds() : std::vector<std::string>();
}

void InspectorIo::WaitForFrontendMessageWhilePaused() {
    dispatching_messages_ = false;
    Mutex::ScopedLock scoped_lock(state_lock_);
    if (incoming_message_queue_.empty())
        incoming_message_cond_.Wait(scoped_lock);
}

void InspectorIo::NotifyMessageReceived() {
    Mutex::ScopedLock scoped_lock(state_lock_);
    incoming_message_cond_.Broadcast(scoped_lock);
}

void InspectorIo::DispatchMessages() {
    // This function can be reentered if there was an incoming message while
    // V8 was processing another inspector request (e.g. if the user is
    // evaluating a long-running JS code snippet). This can happen only at
    // specific points (e.g. the lines that call inspector_ methods)
    if (dispatching_messages_)
        return;
    dispatching_messages_ = true;
    bool had_messages = false;
    do {
        if (dispatching_message_queue_.empty())
            SwapBehindLock(&incoming_message_queue_, &dispatching_message_queue_);
        had_messages = !dispatching_message_queue_.empty();
        while (!dispatching_message_queue_.empty()) {
            MessageQueue<InspectorAction>::value_type task;
            std::swap(dispatching_message_queue_.front(), task);
            dispatching_message_queue_.pop_front();
            StringView message = std::get<2>(task)->string();
            switch (std::get<0>(task)) {
                case InspectorAction::kStartSession:
                    CHECK_EQ(session_delegate_, nullptr);
                    session_id_ = std::get<1>(task);
                    state_ = State::kConnected;
                    SE_LOGD("Debugger attached.\n");
                    session_delegate_ = std::unique_ptr<InspectorSessionDelegate>(
                        new IoSessionDelegate(this));
                    parent_env_->inspector_agent()->Connect(session_delegate_.get());
                    break;
                case InspectorAction::kEndSession:
                    CHECK_NE(session_delegate_, nullptr);
                    if (state_ == State::kShutDown) {
                        state_ = State::kDone;
                    } else {
                        state_ = State::kAccepting;
                    }
                    parent_env_->inspector_agent()->Disconnect();
                    session_delegate_.reset();
                    break;
                case InspectorAction::kSendMessage:
                    parent_env_->inspector_agent()->Dispatch(message);
                    break;
            }
        }
    } while (had_messages);
    dispatching_messages_ = false;
}

// static
void InspectorIo::MainThreadReqAsyncCb(uv_async_t *req) {
    AsyncAndAgent *pair = node::ContainerOf(&AsyncAndAgent::first, req);
    // Note that this may be called after io was closed or even after a new
    // one was created and ran.
    InspectorIo *io = pair->second->io();
    if (io != nullptr)
        io->DispatchMessages();
}

void InspectorIo::Write(TransportAction action, int session_id,
                        const StringView &inspector_message) {
    AppendMessage(&outgoing_message_queue_, action, session_id,
                  StringBuffer::create(inspector_message));
    int err = uv_async_send(&thread_req_);
    CHECK_EQ(0, err);
}

InspectorIoDelegate::InspectorIoDelegate(InspectorIo *io,
                                         const std::string &script_path,
                                         const std::string &script_name,
                                         bool wait)
: io_(io),
  connected_(false),
  session_id_(0),
  script_name_(script_name),
  script_path_(script_path),
  target_id_(GenerateID()),
  waiting_(wait) {}

bool InspectorIoDelegate::StartSession(int session_id,
                                       const std::string &target_id) {
    if (connected_)
        return false;
    connected_ = true;
    session_id_++;
    io_->PostIncomingMessage(InspectorAction::kStartSession, session_id, "");
    return true;
}

void InspectorIoDelegate::MessageReceived(int session_id,
                                          const std::string &message) {
    // REFINE(pfeldman): Instead of blocking execution while debugger
    // engages, node should wait for the run callback from the remote client
    // and initiate its startup. This is a change to node.cc that should be
    // upstreamed separately.
    if (waiting_) {
        if (message.find("\"Runtime.runIfWaitingForDebugger\"") !=
            std::string::npos) {
            waiting_ = false;
            io_->ResumeStartup();
        }
    }
    io_->PostIncomingMessage(InspectorAction::kSendMessage, session_id,
                             message);
}

void InspectorIoDelegate::EndSession(int session_id) {
    connected_ = false;
    io_->PostIncomingMessage(InspectorAction::kEndSession, session_id, "");
}

std::vector<std::string> InspectorIoDelegate::GetTargetIds() {
    return {target_id_};
}

std::string InspectorIoDelegate::GetTargetTitle(const std::string &id) {
    return script_name_.empty() ? GetProcessTitle() : script_name_;
}

std::string InspectorIoDelegate::GetTargetUrl(const std::string &id) {
    return "file://" + script_path_;
}

bool IoSessionDelegate::WaitForFrontendMessageWhilePaused() {
    io_->WaitForFrontendMessageWhilePaused();
    return true;
}

void IoSessionDelegate::SendMessageToFrontend(
    const v8_inspector::StringView &message) {
    io_->Write(TransportAction::kSendMessage, io_->session_id_, message);
}

} // namespace inspector
} // namespace node

#endif // #if (SCRIPT_ENGINE_TYPE == SCRIPT_ENGINE_V8) && SE_ENABLE_INSPECTOR
