// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "content_stream.h"

#include "dap/io.h"

#include <string.h>   // strlen
#include <algorithm>  // std::min

namespace dap {

////////////////////////////////////////////////////////////////////////////////
// ContentReader
////////////////////////////////////////////////////////////////////////////////
ContentReader::ContentReader(
    const std::shared_ptr<Reader>& reader,
    OnInvalidData on_invalid_data /* = OnInvalidData::kIgnore */)
    : reader(reader), on_invalid_data(on_invalid_data) {}

ContentReader& ContentReader::operator=(ContentReader&& rhs) noexcept {
  buf = std::move(rhs.buf);
  reader = std::move(rhs.reader);
  on_invalid_data = std::move(rhs.on_invalid_data);
  return *this;
}

bool ContentReader::isOpen() {
  return reader ? reader->isOpen() : false;
}

void ContentReader::close() {
  if (reader) {
    reader->close();
  }
}

std::string ContentReader::read() {
  // Find Content-Length header prefix
  if (on_invalid_data == kClose) {
    if (!match("Content-Length:")) {
      return badHeader();
    }
  } else {
    if (!scan("Content-Length:")) {
      return "";
    }
  }
  // Skip whitespace and tabs
  while (matchAny(" \t")) {
  }
  // Parse length
  size_t len = 0;
  while (true) {
    auto c = matchAny("0123456789");
    if (c == 0) {
      break;
    }
    len *= 10;
    len += size_t(c) - size_t('0');
  }
  if (len == 0) {
    return "";
  }

  // Expect \r\n\r\n
  if (!match("\r\n\r\n")) {
    return badHeader();
  }

  // Read message
  if (!buffer(len)) {
    return "";
  }
  std::string out;
  out.reserve(len);
  for (size_t i = 0; i < len; i++) {
    out.push_back(static_cast<char>(buf.front()));
    buf.pop_front();
  }
  return out;
}

bool ContentReader::scan(const uint8_t* seq, size_t len) {
  while (buffer(len)) {
    if (match(seq, len)) {
      return true;
    }
    buf.pop_front();
  }
  return false;
}

bool ContentReader::scan(const char* str) {
  auto len = strlen(str);
  return scan(reinterpret_cast<const uint8_t*>(str), len);
}

bool ContentReader::match(const uint8_t* seq, size_t len) {
  if (!buffer(len)) {
    return false;
  }
  auto it = buf.begin();
  for (size_t i = 0; i < len; i++, it++) {
    if (*it != seq[i]) {
      return false;
    }
  }
  for (size_t i = 0; i < len; i++) {
    buf.pop_front();
  }
  return true;
}

bool ContentReader::match(const char* str) {
  auto len = strlen(str);
  return match(reinterpret_cast<const uint8_t*>(str), len);
}

char ContentReader::matchAny(const char* chars) {
  if (!buffer(1)) {
    return false;
  }
  int c = buf.front();
  if (auto p = strchr(chars, c)) {
    buf.pop_front();
    return *p;
  }
  return 0;
}

bool ContentReader::buffer(size_t bytes) {
  if (bytes < buf.size()) {
    return true;
  }
  bytes -= buf.size();
  while (bytes > 0) {
    uint8_t chunk[256];
    auto numWant = std::min(sizeof(chunk), bytes);
    auto numGot = reader->read(chunk, numWant);
    if (numGot == 0) {
      return false;
    }
    for (size_t i = 0; i < numGot; i++) {
      buf.push_back(chunk[i]);
    }
    bytes -= numGot;
  }
  return true;
}

std::string ContentReader::badHeader() {
  if (on_invalid_data == kClose) {
    close();
  }
  return "";
}

////////////////////////////////////////////////////////////////////////////////
// ContentWriter
////////////////////////////////////////////////////////////////////////////////
ContentWriter::ContentWriter(const std::shared_ptr<Writer>& rhs)
    : writer(rhs) {}

ContentWriter& ContentWriter::operator=(ContentWriter&& rhs) noexcept {
  writer = std::move(rhs.writer);
  return *this;
}

bool ContentWriter::isOpen() {
  return writer ? writer->isOpen() : false;
}

void ContentWriter::close() {
  if (writer) {
    writer->close();
  }
}

bool ContentWriter::write(const std::string& msg) const {
  auto header =
      std::string("Content-Length: ") + std::to_string(msg.size()) + "\r\n\r\n";
  return writer->write(header.data(), header.size()) &&
         writer->write(msg.data(), msg.size());
}

}  // namespace dap
