// Copyright 2014 the V8 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.

#include "src/inspector/v8-debugger-script.h"

#include "src/inspector/protocol-platform.h"
#include "src/inspector/string-util.h"

namespace v8_inspector {

static const char hexDigits[17] = "0123456789ABCDEF";

static void appendUnsignedAsHex(uint64_t number, String16Builder* destination) {
  for (size_t i = 0; i < 8; ++i) {
    UChar c = hexDigits[number & 0xF];
    destination->append(c);
    number >>= 4;
  }
}

// Hash algorithm for substrings is described in "Über die Komplexität der
// Multiplikation in
// eingeschränkten Branchingprogrammmodellen" by Woelfe.
// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
static String16 calculateHash(const String16& str) {
  static uint64_t prime[] = {0x3FB75161, 0xAB1F4E4F, 0x82675BC5, 0xCD924D35,
                             0x81ABE279};
  static uint64_t random[] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476,
                              0xC3D2E1F0};
  static uint32_t randomOdd[] = {0xB4663807, 0xCC322BF5, 0xD4F91BBD, 0xA7BEA11D,
                                 0x8F462907};

  uint64_t hashes[] = {0, 0, 0, 0, 0};
  uint64_t zi[] = {1, 1, 1, 1, 1};

  const size_t hashesSize = arraysize(hashes);

  size_t current = 0;
  const uint32_t* data = nullptr;
  size_t sizeInBytes = sizeof(UChar) * str.length();
  data = reinterpret_cast<const uint32_t*>(str.characters16());
  for (size_t i = 0; i < sizeInBytes / 4; i += 4) {
    uint32_t v = data[i];
    uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
    hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
    zi[current] = (zi[current] * random[current]) % prime[current];
    current = current == hashesSize - 1 ? 0 : current + 1;
  }
  if (sizeInBytes % 4) {
    uint32_t v = 0;
    for (size_t i = sizeInBytes - sizeInBytes % 4; i < sizeInBytes; ++i) {
      v <<= 8;
      v |= reinterpret_cast<const uint8_t*>(data)[i];
    }
    uint64_t xi = v * randomOdd[current] & 0x7FFFFFFF;
    hashes[current] = (hashes[current] + zi[current] * xi) % prime[current];
    zi[current] = (zi[current] * random[current]) % prime[current];
    current = current == hashesSize - 1 ? 0 : current + 1;
  }

  for (size_t i = 0; i < hashesSize; ++i)
    hashes[i] = (hashes[i] + zi[i] * (prime[i] - 1)) % prime[i];

  String16Builder hash;
  for (size_t i = 0; i < hashesSize; ++i) appendUnsignedAsHex(hashes[i], &hash);
  return hash.toString();
}

V8DebuggerScript::V8DebuggerScript(v8::Isolate* isolate,
                                   v8::Local<v8::DebugInterface::Script> script,
                                   bool isLiveEdit) {
  m_isolate = script->GetIsolate();
  m_id = String16::fromInteger(script->Id());
  v8::Local<v8::String> tmp;
  if (script->Name().ToLocal(&tmp)) m_url = toProtocolString(tmp);
  if (script->SourceURL().ToLocal(&tmp)) {
    m_sourceURL = toProtocolString(tmp);
    if (m_url.isEmpty()) m_url = toProtocolString(tmp);
  }
  if (script->SourceMappingURL().ToLocal(&tmp))
    m_sourceMappingURL = toProtocolString(tmp);
  m_startLine = script->LineOffset();
  m_startColumn = script->ColumnOffset();
  std::vector<int> lineEnds = script->LineEnds();
  CHECK(lineEnds.size());
  int source_length = lineEnds[lineEnds.size() - 1];
  if (lineEnds.size()) {
    m_endLine = static_cast<int>(lineEnds.size()) + m_startLine - 1;
    if (lineEnds.size() > 1) {
      m_endColumn = source_length - lineEnds[lineEnds.size() - 2] - 1;
    } else {
      m_endColumn = source_length + m_startColumn;
    }
  } else {
    m_endLine = m_startLine;
    m_endColumn = m_startColumn;
  }

  if (script->ContextData().ToLocal(&tmp)) {
    String16 contextData = toProtocolString(tmp);
    size_t firstComma = contextData.find(",", 0);
    size_t secondComma = firstComma != String16::kNotFound
                             ? contextData.find(",", firstComma + 1)
                             : String16::kNotFound;
    if (secondComma != String16::kNotFound) {
      String16 executionContextId =
          contextData.substring(firstComma + 1, secondComma - firstComma - 1);
      bool isOk = false;
      m_executionContextId = executionContextId.toInteger(&isOk);
      if (!isOk) m_executionContextId = 0;
      m_executionContextAuxData = contextData.substring(secondComma + 1);
    }
  }

  m_isLiveEdit = isLiveEdit;

  if (script->Source().ToLocal(&tmp)) {
    m_source.Reset(m_isolate, tmp);
    String16 source = toProtocolString(tmp);
    m_hash = calculateHash(source);
    // V8 will not count last line if script source ends with \n.
    if (source.length() > 1 && source[source.length() - 1] == '\n') {
      m_endLine++;
      m_endColumn = 0;
    }
  }

  m_script.Reset(m_isolate, script);
}

V8DebuggerScript::~V8DebuggerScript() {}

const String16& V8DebuggerScript::sourceURL() const {
  return m_sourceURL.isEmpty() ? m_url : m_sourceURL;
}

v8::Local<v8::String> V8DebuggerScript::source(v8::Isolate* isolate) const {
  return m_source.Get(isolate);
}

void V8DebuggerScript::setSourceURL(const String16& sourceURL) {
  m_sourceURL = sourceURL;
}

void V8DebuggerScript::setSourceMappingURL(const String16& sourceMappingURL) {
  m_sourceMappingURL = sourceMappingURL;
}

void V8DebuggerScript::setSource(v8::Local<v8::String> source) {
  m_source.Reset(m_isolate, source);
  m_hash = calculateHash(toProtocolString(source));
}

bool V8DebuggerScript::getPossibleBreakpoints(
    const v8::DebugInterface::Location& start,
    const v8::DebugInterface::Location& end,
    std::vector<v8::DebugInterface::Location>* locations) {
  v8::HandleScope scope(m_isolate);
  v8::Local<v8::DebugInterface::Script> script = m_script.Get(m_isolate);
  return script->GetPossibleBreakpoints(start, end, locations);
}

}  // namespace v8_inspector