//===---------------------StructuredData.cpp ---------------------*- C++-*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/DataBuffer.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/JSON.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/Stream.h" // for Stream
#include "lldb/Utility/StreamString.h"
#include "llvm/ADT/STLExtras.h" // for make_unique
#include <cerrno>
#include <cstdlib>
#include <inttypes.h>
#include <limits> // for numeric_limits

using namespace lldb_private;

//----------------------------------------------------------------------
// Functions that use a JSONParser to parse JSON into StructuredData
//----------------------------------------------------------------------
static StructuredData::ObjectSP ParseJSONValue(JSONParser &json_parser);
static StructuredData::ObjectSP ParseJSONObject(JSONParser &json_parser);
static StructuredData::ObjectSP ParseJSONArray(JSONParser &json_parser);

StructuredData::ObjectSP
StructuredData::ParseJSONFromFile(const FileSpec &input_spec, Status &error) {
  StructuredData::ObjectSP return_sp;
  if (!input_spec.Exists()) {
    error.SetErrorStringWithFormatv("input file {0} does not exist.",
                                    input_spec);
    return return_sp;
  }

  auto buffer_or_error = llvm::MemoryBuffer::getFile(input_spec.GetPath());
  if (!buffer_or_error) {
    error.SetErrorStringWithFormatv("could not open input file: {0} - {1}.",
                                    input_spec.GetPath(),
                                    buffer_or_error.getError().message());
    return return_sp;
  }

  JSONParser json_parser(buffer_or_error.get()->getBuffer());
  return_sp = ParseJSONValue(json_parser);
  return return_sp;
}

static StructuredData::ObjectSP ParseJSONObject(JSONParser &json_parser) {
  // The "JSONParser::Token::ObjectStart" token should have already been
  // consumed by the time this function is called
  auto dict_up = llvm::make_unique<StructuredData::Dictionary>();

  std::string value;
  std::string key;
  while (1) {
    JSONParser::Token token = json_parser.GetToken(value);

    if (token == JSONParser::Token::String) {
      key.swap(value);
      token = json_parser.GetToken(value);
      if (token == JSONParser::Token::Colon) {
        StructuredData::ObjectSP value_sp = ParseJSONValue(json_parser);
        if (value_sp)
          dict_up->AddItem(key, value_sp);
        else
          break;
      }
    } else if (token == JSONParser::Token::ObjectEnd) {
      return StructuredData::ObjectSP(dict_up.release());
    } else if (token == JSONParser::Token::Comma) {
      continue;
    } else {
      break;
    }
  }
  return StructuredData::ObjectSP();
}

static StructuredData::ObjectSP ParseJSONArray(JSONParser &json_parser) {
  // The "JSONParser::Token::ObjectStart" token should have already been
  // consumed
  // by the time this function is called
  auto array_up = llvm::make_unique<StructuredData::Array>();

  std::string value;
  std::string key;
  while (1) {
    StructuredData::ObjectSP value_sp = ParseJSONValue(json_parser);
    if (value_sp)
      array_up->AddItem(value_sp);
    else
      break;

    JSONParser::Token token = json_parser.GetToken(value);
    if (token == JSONParser::Token::Comma) {
      continue;
    } else if (token == JSONParser::Token::ArrayEnd) {
      return StructuredData::ObjectSP(array_up.release());
    } else {
      break;
    }
  }
  return StructuredData::ObjectSP();
}

static StructuredData::ObjectSP ParseJSONValue(JSONParser &json_parser) {
  std::string value;
  const JSONParser::Token token = json_parser.GetToken(value);
  switch (token) {
  case JSONParser::Token::ObjectStart:
    return ParseJSONObject(json_parser);

  case JSONParser::Token::ArrayStart:
    return ParseJSONArray(json_parser);

  case JSONParser::Token::Integer: {
    uint64_t uval;
    if (llvm::to_integer(value, uval, 0))
      return std::make_shared<StructuredData::Integer>(uval);
  } break;

  case JSONParser::Token::Float: {
    double val;
    if (llvm::to_float(value, val))
      return std::make_shared<StructuredData::Float>(val);
  } break;

  case JSONParser::Token::String:
    return std::make_shared<StructuredData::String>(value);

  case JSONParser::Token::True:
  case JSONParser::Token::False:
    return std::make_shared<StructuredData::Boolean>(token ==
                                                     JSONParser::Token::True);

  case JSONParser::Token::Null:
    return std::make_shared<StructuredData::Null>();

  default:
    break;
  }
  return StructuredData::ObjectSP();
}

StructuredData::ObjectSP StructuredData::ParseJSON(std::string json_text) {
  JSONParser json_parser(json_text.c_str());
  StructuredData::ObjectSP object_sp = ParseJSONValue(json_parser);
  return object_sp;
}

StructuredData::ObjectSP
StructuredData::Object::GetObjectForDotSeparatedPath(llvm::StringRef path) {
  if (this->GetType() == lldb::eStructuredDataTypeDictionary) {
    std::pair<llvm::StringRef, llvm::StringRef> match = path.split('.');
    std::string key = match.first.str();
    ObjectSP value = this->GetAsDictionary()->GetValueForKey(key);
    if (value.get()) {
      // Do we have additional words to descend?  If not, return the
      // value we're at right now.
      if (match.second.empty()) {
        return value;
      } else {
        return value->GetObjectForDotSeparatedPath(match.second);
      }
    }
    return ObjectSP();
  }

  if (this->GetType() == lldb::eStructuredDataTypeArray) {
    std::pair<llvm::StringRef, llvm::StringRef> match = path.split('[');
    if (match.second.size() == 0) {
      return this->shared_from_this();
    }
    errno = 0;
    uint64_t val = strtoul(match.second.str().c_str(), NULL, 10);
    if (errno == 0) {
      return this->GetAsArray()->GetItemAtIndex(val);
    }
    return ObjectSP();
  }

  return this->shared_from_this();
}

void StructuredData::Object::DumpToStdout(bool pretty_print) const {
  StreamString stream;
  Dump(stream, pretty_print);
  llvm::outs() << stream.GetString();
}

void StructuredData::Array::Dump(Stream &s, bool pretty_print) const {
  bool first = true;
  s << "[";
  if (pretty_print) {
    s << "\n";
    s.IndentMore();
  }
  for (const auto &item_sp : m_items) {
    if (first) {
      first = false;
    } else {
      s << ",";
      if (pretty_print)
        s << "\n";
    }

    if (pretty_print)
      s.Indent();
    item_sp->Dump(s, pretty_print);
  }
  if (pretty_print) {
    s.IndentLess();
    s.EOL();
    s.Indent();
  }
  s << "]";
}

void StructuredData::Integer::Dump(Stream &s, bool pretty_print) const {
  s.Printf("%" PRIu64, m_value);
}

void StructuredData::Float::Dump(Stream &s, bool pretty_print) const {
  s.Printf("%lg", m_value);
}

void StructuredData::Boolean::Dump(Stream &s, bool pretty_print) const {
  if (m_value == true)
    s.PutCString("true");
  else
    s.PutCString("false");
}

void StructuredData::String::Dump(Stream &s, bool pretty_print) const {
  std::string quoted;
  const size_t strsize = m_value.size();
  for (size_t i = 0; i < strsize; ++i) {
    char ch = m_value[i];
    if (ch == '"' || ch == '\\')
      quoted.push_back('\\');
    quoted.push_back(ch);
  }
  s.Printf("\"%s\"", quoted.c_str());
}

void StructuredData::Dictionary::Dump(Stream &s, bool pretty_print) const {
  bool first = true;
  s << "{";
  if (pretty_print) {
    s << "\n";
    s.IndentMore();
  }
  for (const auto &pair : m_dict) {
    if (first)
      first = false;
    else {
      s << ",";
      if (pretty_print)
        s << "\n";
    }
    if (pretty_print)
      s.Indent();
    s << "\"" << pair.first.AsCString() << "\" : ";
    pair.second->Dump(s, pretty_print);
  }
  if (pretty_print) {
    s.IndentLess();
    s.EOL();
    s.Indent();
  }
  s << "}";
}

void StructuredData::Null::Dump(Stream &s, bool pretty_print) const {
  s << "null";
}

void StructuredData::Generic::Dump(Stream &s, bool pretty_print) const {
  s << "0x" << m_object;
}
