/*
 * Copyright (c) 2015, 2024, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0, as
 * published by the Free Software Foundation.
 *
 * This program is designed to work with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation. The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have either included with
 * the program or referenced in the documentation.
 *
 * Without limiting anything contained in the foregoing, this file,
 * which is part of Connector/C++, is also subject to the
 * Universal FOSS Exception, version 1.0, a copy of which can be found at
 * https://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <mysql/cdk.h>
#include <mysqlx/xdevapi.h>
#include <json_parser.h>

/**
  @file
  Implementation of DbDoc and related classes.
*/

#include "impl.h"

#include <vector>
#include <sstream>
#include <iomanip>
#include <memory>

using namespace ::mysqlx;
using std::endl;

// Value::get specialization to allow convertion to common::Value type
// --------------------


template <>
common::Value Value::get<common::Value>() const {
  if (getType() == DOCUMENT) {
    return common::Value::Access::mk_json(m_doc.get_json());
  }
  return *this;
}

// DbDoc implementation
// --------------------


DbDoc::DbDoc(const std::string &json)
{
  try {
    m_impl = std::make_shared<Impl::JSONDoc>(json);
  }
  CATCH_AND_WRAP
}

DbDoc::DbDoc(std::string &&json)
{
  try {
    m_impl = std::make_shared<Impl::JSONDoc>(std::move(json));
  }
  CATCH_AND_WRAP
}


DbDoc::DbDoc(const std::shared_ptr<Impl> &impl)
  : m_impl(impl)
{}


const char* DbDoc::get_json() const
{
  return m_impl ? m_impl->get_json() : "";
}


bool DbDoc::hasField(const Field &fld) const
{
  try {
    return m_impl && m_impl->has_field(fld);
  }
  CATCH_AND_WRAP
}


const Value& DbDoc::operator[](const Field &fld) const
{
  if (!m_impl)
    throw std::out_of_range("empty document");

  try {
    return m_impl->get(fld);
  }
  catch (const std::out_of_range&)
  {
    throw;
  }
  catch (...)
  {
    try { throw; }
    CATCH_AND_WRAP
  }
}


void DbDoc::print(std::ostream &out) const
{
  try {
    if (m_impl)
      m_impl->print(out);
    else
      out << "{}";
  }
  CATCH_AND_WRAP
}


// JSON document
// -------------


/*
  JSON processor which builds document implementation adding
  key-value pairs to document's map.
*/

struct DbDoc::Impl::Builder
  : public cdk::JSON::Processor
  , public cdk::JSON::Processor::Any_prc
  , public cdk::JSON::Processor::Any_prc::Scalar_prc
{
  Map  &m_map;
  mysqlx::string m_key;

public:

  Builder(DbDoc::Impl &doc)
    : m_map(doc.m_map)
  {}

  // JSON processor (to build the docuemnt)

  void doc_begin()
  {
    m_map.clear();
  }

  void doc_end()
  {}

  cdk::JSON::Processor::Any_prc*
  key_val(const cdk::string &key)
  {
    m_key = key;
    // Return itself to process key value
    return this;
  }


  /*
    Builder for array values.
  */

  struct Arr_builder
    : cdk::JSON::Processor::Any_prc
    , cdk::JSON::Processor::Any_prc::List_prc
    , cdk::JSON::Processor::Any_prc::Scalar_prc
  {
    Value::Array *m_arr;


    // List processor (to build the list)

    void list_begin()
    {
      m_arr->clear();
    }

    void list_end() {}

    Element_prc* list_el()
    {
      // Return itself to process list element.
      return this;
    }


    // Any processor (to process elements of the list)

    // Handle sub-arrray element.

    std::unique_ptr<Arr_builder> m_arr_builder;

    List_prc* arr()
    {
      // Create array value.

      Value sub;
      sub.m_type = Value::ARR;
      sub.m_arr = std::make_shared<Value::Array>();

      // Create builder for the sub-array.

      m_arr_builder.reset(new Arr_builder());
      m_arr_builder->m_arr = sub.m_arr.get();

      // Append the sub-array to the main array.

      m_arr->emplace_back(sub);

      return m_arr_builder.get();
    }


    // Handle a document element

    std::unique_ptr<Builder> m_doc_builder;

    Doc_prc*  doc()
    {
      // Create document value and append it to the array.

      Value sub;
      sub.m_type = Value::DOC;
      sub.m_doc.m_impl = std::make_shared<DbDoc::Impl>();
      m_arr->emplace_back(sub);

      // Create builder for the document and return it as the processor.

      m_doc_builder.reset(new Builder(*sub.m_doc.m_impl));
      return m_doc_builder.get();
    }


    // Handle scalar values using itself as a processor.

    Scalar_prc* scalar()
    { return this; }


    // Sclar processor (to store scalar values in the list)

    void null() { m_arr->emplace_back(Value()); }
    void str(const cdk::string &val)
    {
      m_arr->emplace_back(mysqlx::string(val));
    }
    void num(uint64_t val) { m_arr->emplace_back(val); }
    void num(int64_t val) { m_arr->emplace_back(val); }
    void num(float val) { m_arr->emplace_back(val); }
    void num(double val) { m_arr->emplace_back(val); }
    void yesno(bool val) { m_arr->emplace_back(val); }

  }
  m_arr_builder;


  cdk::JSON::Processor::Any_prc::List_prc*
  arr()
  {
    using mysqlx::Value;
    Value &arr = m_map[m_key];

    // Turn the value to one storing an array.

    arr.m_type = Value::ARR;
    arr.m_arr  = std::make_shared<Value::Array>();

    // Set up array builder for the new value.

    m_arr_builder.m_arr = arr.m_arr.get();
    return &m_arr_builder;
  }


  std::unique_ptr<Builder> m_doc_builder;

  cdk::JSON::Processor::Any_prc::Doc_prc*
  doc()
  {
    using mysqlx::Value;
    Value &sub = m_map[m_key];

    // Turn the value to one storing a document.

    sub.m_type = Value::DOC;
    sub.m_doc.m_impl = std::make_shared<DbDoc::Impl>();

    // Use another builder to build the sub-document.

    m_doc_builder.reset(new Builder(*sub.m_doc.m_impl));
    return m_doc_builder.get();
  }

  cdk::JSON::Processor::Any_prc::Scalar_prc*
  scalar()
  {
    return this;
  }

  /*
    Callbacks for scalar values store the value under
    key given by m_key.
  */

  void null() { m_map.emplace(m_key, Value()); }
  void str(const cdk::string &val)
  {
    m_map.emplace(m_key, mysqlx::string(val));
  }
  void num(uint64_t val)  { m_map.emplace(m_key, val); }
  void num(int64_t val)   { m_map.emplace(m_key, val); }
  void num(float val)     { m_map.emplace(m_key, val); }
  void num(double val)    { m_map.emplace(m_key, val); }
  void yesno(bool val)    { m_map.emplace(m_key, val); }

};


void DbDoc::Impl::JSONDoc::prepare()
{
  if (m_parsed)
    return;

  cdk::Codec<cdk::TYPE_DOCUMENT> codec;
  Builder bld(*this);
  codec.from_bytes(cdk::bytes(m_json), bld);
  m_parsed = true;
}


/*
  Parse JSON string and build a corresponding Value.
*/

Value Value::Access::mk_from_json(const std::string &json)
{
  /*
    Define builder which acts as JSON value processor and
    builds the corresponding Value object.
  */

  struct Builder
    : public cdk::JSON::Processor
    , cdk::JSON_processor
    , cdk::JSON::Processor::Any_prc
  {
    Value *m_val = NULL;

    // JSON::Processor

    Any_prc* key_val(const string&) override
    {
      return this;
    }

    // Any_prc

    Scalar_prc *scalar() override
    { return this; }

    std::unique_ptr<DbDoc::Impl::Builder> m_doc_builder;

    Doc_prc *doc() override
    {
      m_val->m_type = DOC;
      m_doc_builder.reset(new DbDoc::Impl::Builder(*m_val->m_doc.m_impl));
      return m_doc_builder.get();
    }

    DbDoc::Impl::Builder::Arr_builder m_arr_builder;

    List_prc *arr() override
    {
      m_val->m_type = ARR;
      m_val->m_arr = std::make_shared<Array>();
      m_arr_builder.m_arr = m_val->m_arr.get();
      return &m_arr_builder;
    }

    // JSON_processor

    void null() override
    {}

    void str(const cdk::string &val) override
    {
      *m_val = (mysqlx::string)val;
    }

    void num(uint64_t val) override
    { *m_val = val; }

    void num(int64_t val) override
    { *m_val = val; }

    void num(float val) override
    { *m_val = val; }

    void num(double val) override
   { *m_val = val; }

    void yesno(bool val) override
    { *m_val = val; }

  }
  builder;

  // Invoke parser to build the value.

  Value val;
  builder.m_val = &val;

  /*
    Note: json can be not only an object, but also scalar or array. Since
    JSON_parser can parse only documents, we do a trick of parsing document
    of the form { "doc": json } and builder ignores the top-level "doc"
    field.
  */

  parser::JSON_parser parser(std::string("{ \"doc\":") + json + "}");

  parser.process(builder);

  return val;
}


/*
  Iterating over document fields
  ------------------------------
  Iterator functionality is implemented by document implementation
  object in forms of these methods:

  - reset()  - restart iteration form the beginning,
  - next()   - move to next document field,
  - at_end() - true if all fields have been enumerated,
  - get_current_fld() - return current field in the sequence.

  Note: Since document implementation acts as an iterator, only one
  iterator can be used at a time. Creating new iterator will invalidate
  other iterators.

  Note: Iterator takes shared ownership of document implementation
  so it can be used even if original document was destroyed.

  TODO: Use common::Iterator<> template instead?
*/


DbDoc::Iterator DbDoc::begin()
{
  try {
    Iterator it;
    m_impl->reset();
    it.m_impl = m_impl;
    it.m_end = false;
    return it;
  }
  CATCH_AND_WRAP
}

DbDoc::Iterator DbDoc::end()
{
  try {
    /*
      Iterator that points one-past-the-end-of-sequence has no
      real representation - we simply set m_end flag in it.
    */
    Iterator it;
    it.m_end = true;
    return it;
  }
  CATCH_AND_WRAP
}


const Field& DbDoc::Iterator::operator*()
{
  if (m_end)
    THROW("dereferencing past-the-end iterator");

  try {
    return m_impl->get_current_fld();
  }
  CATCH_AND_WRAP
}

DbDoc::Iterator& DbDoc::Iterator::operator++()
{
  try {
    // only non-end iterator can be incremented.
    if (!m_end)
      m_impl->next();
    return *this;
  }
  CATCH_AND_WRAP
}

bool DbDoc::Iterator::operator==(const Iterator &other) const
{
  try {

    /*
      if this is end iterator, other is equal if it is also end
      iterator or it is at the end of sequence. And vice-versa.
    */

    if (m_end)
      return other.m_end || other.m_impl->at_end();

    if (other.m_end)
      return m_end || m_impl->at_end();

    /*
      Otherwise two iterators are equal if they use the same
      document implementation (but such two iterators should not
      be used at the same time).
    */

    return m_impl == other.m_impl;
  }
  CATCH_AND_WRAP
}
