//===- MsgPackTypes.cpp - MsgPack Types -------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
/// \file
/// Implementation of types representing MessagePack "documents".
//
//===----------------------------------------------------------------------===//

#include "llvm/BinaryFormat/MsgPackTypes.h"
#include "llvm/Support/Error.h"

using namespace llvm;
using namespace msgpack;

namespace llvm {
namespace msgpack {
void ScalarNode::anchor() {}
void ArrayNode::anchor() {}
void MapNode::anchor() {}
}
}

Expected<OptNodePtr> Node::readArray(Reader &MPReader, size_t Length) {
  auto A = std::make_shared<ArrayNode>();
  for (size_t I = 0; I < Length; ++I) {
    auto OptNodeOrErr = Node::read(MPReader);
    if (auto Err = OptNodeOrErr.takeError())
      return std::move(Err);
    if (!*OptNodeOrErr)
      return make_error<StringError>(
          "Insufficient array elements",
          std::make_error_code(std::errc::invalid_argument));
    A->push_back(std::move(**OptNodeOrErr));
  }
  return OptNodePtr(std::move(A));
}

Expected<OptNodePtr> Node::readMap(Reader &MPReader, size_t Length) {
  auto M = std::make_shared<MapNode>();
  for (size_t I = 0; I < Length; ++I) {
    auto OptKeyOrErr = Node::read(MPReader);
    if (auto Err = OptKeyOrErr.takeError())
      return std::move(Err);
    if (!*OptKeyOrErr)
      return make_error<StringError>(
          "Insufficient map elements",
          std::make_error_code(std::errc::invalid_argument));
    auto OptValOrErr = Node::read(MPReader);
    if (auto Err = OptValOrErr.takeError())
      return std::move(Err);
    if (!*OptValOrErr)
      return make_error<StringError>(
          "Insufficient map elements",
          std::make_error_code(std::errc::invalid_argument));
    auto *Key = dyn_cast<ScalarNode>((*OptKeyOrErr)->get());
    if (!Key)
      return make_error<StringError>(
          "Only string map keys are supported",
          std::make_error_code(std::errc::invalid_argument));
    if (Key->getScalarKind() != ScalarNode::SK_String)
      return make_error<StringError>(
          "Only string map keys are supported",
          std::make_error_code(std::errc::invalid_argument));
    M->try_emplace(Key->getString(), std::move(**OptValOrErr));
  }
  return OptNodePtr(std::move(M));
}

Expected<OptNodePtr> Node::read(Reader &MPReader) {
  Object Obj;

  auto ContinueOrErr = MPReader.read(Obj);
  if (auto Err = ContinueOrErr.takeError())
    return std::move(Err);
  if (!*ContinueOrErr)
    return None;

  switch (Obj.Kind) {
  case Type::Int:
    return OptNodePtr(std::make_shared<ScalarNode>(Obj.Int));
  case Type::UInt:
    return OptNodePtr(std::make_shared<ScalarNode>(Obj.UInt));
  case Type::Nil:
    return OptNodePtr(std::make_shared<ScalarNode>());
  case Type::Boolean:
    return OptNodePtr(std::make_shared<ScalarNode>(Obj.Bool));
  case Type::Float:
    return OptNodePtr(std::make_shared<ScalarNode>(Obj.Float));
  case Type::String:
    return OptNodePtr(std::make_shared<ScalarNode>(Obj.Raw));
  case Type::Binary:
    return OptNodePtr(std::make_shared<ScalarNode>(Obj.Raw));
  case Type::Array:
    return Node::readArray(MPReader, Obj.Length);
  case Type::Map:
    return Node::readMap(MPReader, Obj.Length);
  case Type::Extension:
    return make_error<StringError>(
        "Extension types are not supported",
        std::make_error_code(std::errc::invalid_argument));
  }
  llvm_unreachable("msgpack::Type not handled");
}

void ScalarNode::destroy() {
  switch (SKind) {
  case SK_String:
  case SK_Binary:
    StringValue.~basic_string();
    break;
  default:
    // POD types do not require destruction
    break;
  }
}

ScalarNode::ScalarNode(int64_t IntValue)
    : Node(NK_Scalar), SKind(SK_Int), IntValue(IntValue) {}

ScalarNode::ScalarNode(int32_t IntValue)
    : ScalarNode(static_cast<int64_t>(IntValue)) {}

ScalarNode::ScalarNode(uint64_t UIntValue)
    : Node(NK_Scalar), SKind(SK_UInt), UIntValue(UIntValue) {}

ScalarNode::ScalarNode(uint32_t IntValue)
    : ScalarNode(static_cast<uint64_t>(IntValue)) {}

ScalarNode::ScalarNode() : Node(NK_Scalar), SKind(SK_Nil) {}

ScalarNode::ScalarNode(bool BoolValue)
    : Node(NK_Scalar), SKind(SK_Boolean), BoolValue(BoolValue) {}

ScalarNode::ScalarNode(double FloatValue)
    : Node(NK_Scalar), SKind(SK_Float), BoolValue(FloatValue) {}

ScalarNode::ScalarNode(StringRef StringValue)
    : Node(NK_Scalar), SKind(SK_String) {
  new (&this->StringValue) std::string(StringValue);
}

ScalarNode::ScalarNode(const char *StringValue)
    : ScalarNode(StringRef(StringValue)) {}

ScalarNode::ScalarNode(std::string &&StringValue)
    : Node(NK_Scalar), SKind(SK_String) {
  new (&this->StringValue) std::string(StringValue);
}

ScalarNode::ScalarNode(MemoryBufferRef BinaryValue)
    : Node(NK_Scalar), SKind(SK_Binary) {
  new (&StringValue) std::string(BinaryValue.getBuffer());
}

ScalarNode::~ScalarNode() { destroy(); }

ScalarNode &ScalarNode::operator=(ScalarNode &&RHS) {
  destroy();
  switch (SKind = RHS.SKind) {
  case SK_Int:
    IntValue = RHS.IntValue;
    break;
  case SK_UInt:
    UIntValue = RHS.UIntValue;
    break;
  case SK_Boolean:
    BoolValue = RHS.BoolValue;
    break;
  case SK_Float:
    FloatValue = RHS.FloatValue;
    break;
  case SK_String:
  case SK_Binary:
    new (&StringValue) std::string(std::move(RHS.StringValue));
    break;
  case SK_Nil:
    // pass
    break;
  }
  return *this;
}

StringRef ScalarNode::inputYAML(StringRef ScalarStr) {
  switch (SKind) {
  case SK_Int:
    return yaml::ScalarTraits<int64_t>::input(ScalarStr, nullptr, IntValue);
  case SK_UInt:
    return yaml::ScalarTraits<uint64_t>::input(ScalarStr, nullptr, UIntValue);
  case SK_Nil:
    return StringRef();
  case SK_Boolean:
    return yaml::ScalarTraits<bool>::input(ScalarStr, nullptr, BoolValue);
  case SK_Float:
    return yaml::ScalarTraits<double>::input(ScalarStr, nullptr, FloatValue);
  case SK_Binary:
  case SK_String:
    return yaml::ScalarTraits<std::string>::input(ScalarStr, nullptr,
                                                  StringValue);
  }
  llvm_unreachable("unrecognized ScalarKind");
}

void ScalarNode::outputYAML(raw_ostream &OS) const {
  switch (SKind) {
  case SK_Int:
    yaml::ScalarTraits<int64_t>::output(IntValue, nullptr, OS);
    break;
  case SK_UInt:
    yaml::ScalarTraits<uint64_t>::output(UIntValue, nullptr, OS);
    break;
  case SK_Nil:
    yaml::ScalarTraits<StringRef>::output("", nullptr, OS);
    break;
  case SK_Boolean:
    yaml::ScalarTraits<bool>::output(BoolValue, nullptr, OS);
    break;
  case SK_Float:
    yaml::ScalarTraits<double>::output(FloatValue, nullptr, OS);
    break;
  case SK_Binary:
  case SK_String:
    yaml::ScalarTraits<std::string>::output(StringValue, nullptr, OS);
    break;
  }
}

yaml::QuotingType ScalarNode::mustQuoteYAML(StringRef ScalarStr) const {
  switch (SKind) {
  case SK_Int:
    return yaml::ScalarTraits<int64_t>::mustQuote(ScalarStr);
  case SK_UInt:
    return yaml::ScalarTraits<uint64_t>::mustQuote(ScalarStr);
  case SK_Nil:
    return yaml::ScalarTraits<StringRef>::mustQuote(ScalarStr);
  case SK_Boolean:
    return yaml::ScalarTraits<bool>::mustQuote(ScalarStr);
  case SK_Float:
    return yaml::ScalarTraits<double>::mustQuote(ScalarStr);
  case SK_Binary:
  case SK_String:
    return yaml::ScalarTraits<std::string>::mustQuote(ScalarStr);
  }
  llvm_unreachable("unrecognized ScalarKind");
}

const char *ScalarNode::IntTag = "!int";
const char *ScalarNode::NilTag = "!nil";
const char *ScalarNode::BooleanTag = "!bool";
const char *ScalarNode::FloatTag = "!float";
const char *ScalarNode::StringTag = "!str";
const char *ScalarNode::BinaryTag = "!bin";

StringRef ScalarNode::getYAMLTag() const {
  switch (SKind) {
  case SK_Int:
    return IntTag;
  case SK_UInt:
    return IntTag;
  case SK_Nil:
    return NilTag;
  case SK_Boolean:
    return BooleanTag;
  case SK_Float:
    return FloatTag;
  case SK_String:
    return StringTag;
  case SK_Binary:
    return BinaryTag;
  }
  llvm_unreachable("unrecognized ScalarKind");
}

void ScalarNode::write(Writer &MPWriter) {
  switch (SKind) {
  case SK_Int:
    MPWriter.write(IntValue);
    break;
  case SK_UInt:
    MPWriter.write(UIntValue);
    break;
  case SK_Nil:
    MPWriter.writeNil();
    break;
  case SK_Boolean:
    MPWriter.write(BoolValue);
    break;
  case SK_Float:
    MPWriter.write(FloatValue);
    break;
  case SK_String:
    MPWriter.write(StringValue);
    break;
  case SK_Binary:
    MPWriter.write(MemoryBufferRef(StringValue, ""));
    break;
  }
}
