/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * The MySQL Connector/C++ is licensed under the terms of the GPLv2
 * <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
 * MySQL Connectors. There are special exceptions to the terms and
 * conditions of the GPLv2 as it is applied to this software, see the
 * FLOSS License Exception
 * <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; version 2 of the License.
 *
 * 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
 * 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
 */

#ifndef PROTOCOL_MYSQLX_BUILDERS_H
#define PROTOCOL_MYSQLX_BUILDERS_H

#include <mysql/cdk/protocol/mysqlx/expr.h>

PUSH_BOOST_WARNINGS
#include <boost/format.hpp>
POP_BOOST_WARNINGS

namespace cdk {
namespace protocol {
namespace mysqlx {


/*
  Message builders
  ================
  A message builder class acts as expression processor and builds protobuf
  message which represents given expression. Different types of protobuf
  messages are built from different types of expressions - we have a
  builder for Mysqlx::Datatypes::Any from Any value as well as a builder
  for Mysqlx::Expr::Expr from a full Expression. However, both builders
  are generated from the same templates with help of Msg_traits template
  which defines specifics of each protobuf message type.
*/


/*
  Protobuf message traits
  -----------------------
  For one of the supported protobuf message types MSG, there are
  XXX_msg_traits<MSG> structures which define how to store different
  kinds of values in a message of type MSG.

  Scalar_msg_traits<MSG> - for a message that can store plain scalars,
    defines a static function:

    Mysqlx::Datatypes::Scalar& get_scalar(MSG &msg, Scalar::Type type)

    which returns a reference to a sub-messgae of type
    Mysqlx::Datatypes::Scalar inside msg, where scalar value can be stored.
    If needed, msg is marked to indicate that it stores a scalar value.
    The `type` field within returned sub-message is initialized to given type.

  Arr_msg_traits<MSG> - for a message that can store sequence of values.
    This structure defines a sub-message type Msg and a static function:

    Msg& add_element(MSG &arr)

    which adds a new element to the array and returns reference to the new
    sub-message where the next value in the sequence can be stored.

  Obj_msg_tratis<MSG> - for a message that can store objects, that is
    key-value maps. This structure defines sub-message type Msg and
    a static function:

    Msg& add_key(MSG &obj, const string &key)

    which adds a new key-value pair to the object and returns reference to
    the sub-message where the key value can be stored.

  Any_msg_traits<MSG> - for messages that can store three kinds of values:
    scalars in Mysqx::Datatypes::Scalar sub-message, arrays or documents.
    The structure defines sub-message types Scalar, Array and Object for
    storing corresponding types of values and functions:

    Scalar& get_scalar(MSG &msg)  - return a sub-message where scalar value
                                    can be stored.

    Array& get_array(MSG &msg)    - return a sub-message where array of values
                                    can be stored.

    Object& get_object(MSG &msg)  - return a sub-message where a key-value
                                    map can be stored.

    These functions mark the message to indicate what kind of value it
    stores.

*/

template <class MSG> struct Scalar_msg_traits;
template <class MSG> struct Arr_msg_traits;
template <class MSG> struct Obj_msg_traits;
template <class MSG> struct Any_msg_traits;


template <class MSG, class ARR, class OBJ>
struct Msg_traits_common
{
  typedef Mysqlx::Datatypes::Scalar Scalar;
  typedef ARR  Array;
  typedef OBJ  Object;
  typedef MSG  Msg;

  static Msg& add_element(Array &arr)
  {
    return *arr.add_value();
  }

  static Msg& add_key(Object &obj, const std::string &key)
  {
    typename Object::ObjectField *fld = obj.add_fld();
    fld->set_key(key);
    return *fld->mutable_value();
  }
};


// Common message traits for messages in Mysqlx::Datatypes namespace.

struct Data_msg_traits
  : public Msg_traits_common<
      Mysqlx::Datatypes::Any,
      Mysqlx::Datatypes::Array,
      Mysqlx::Datatypes::Object
    >
{
  static Scalar& get_scalar(Msg &msg)
  {
    msg.set_type(Msg::SCALAR);
    Scalar *s = msg.mutable_scalar();
    return *s;
  }

  static Scalar& get_scalar(Scalar &msg, Scalar::Type type)
  {
    msg.set_type(type);
    return msg;
  }

  static Array& get_array(Msg &msg)
  {
    msg.set_type(Msg::ARRAY);
    return *msg.mutable_array();
  }

  static Object& get_object(Msg &msg)
  {
    msg.set_type(Msg::OBJECT);
    return *msg.mutable_obj();
  }

};

template<>
struct Scalar_msg_traits<Mysqlx::Datatypes::Scalar> : public Data_msg_traits
{};

template<>
struct Arr_msg_traits<Mysqlx::Datatypes::Array> : public Data_msg_traits
{};

template<>
struct Obj_msg_traits<Mysqlx::Datatypes::Object> : public Data_msg_traits
{};

template<>
struct Any_msg_traits<Mysqlx::Datatypes::Any> : public Data_msg_traits
{};


// Common message traits for messages in Mysqlx::Expr namespace.

struct Expr_msg_traits
  : public Msg_traits_common<
      Mysqlx::Expr::Expr,
      Mysqlx::Expr::Array,
      Mysqlx::Expr::Object
    >
{
  static Msg& get_scalar(Msg &msg)
  {
    msg.set_type(Msg::LITERAL);
    return msg;
  }

  static Scalar& get_scalar(Msg &msg, Scalar::Type type)
  {
    get_scalar(msg);
    Scalar *s = msg.mutable_literal();
    s->set_type(type);
    return *s;
  }

  static Array& get_array(Msg &msg)
  {
    msg.set_type(Msg::ARRAY);
    return *msg.mutable_array();
  }

  static Object& get_object(Msg &msg)
  {
    msg.set_type(Msg::OBJECT);
    return *msg.mutable_object();
  }
};


template<>
struct Scalar_msg_traits<Mysqlx::Expr::Expr> : public Expr_msg_traits
{};

template<>
struct Arr_msg_traits<Mysqlx::Expr::Array> : public Expr_msg_traits
{};

template<>
struct Obj_msg_traits<Mysqlx::Expr::Object> : public Expr_msg_traits
{};

template<>
struct Any_msg_traits<Mysqlx::Expr::Expr> : public Expr_msg_traits
{};


// -----------------------------------------------------------------------

/*
  Common base for message builders which defines m_msg member that stores
  pointer to the message which is being constructed. Message builder object
  is an expression processor and should be used as follows:

    bld.reset(msg,conv);
    expr.process(bld);

  where bld is a message builder instance, msg is a protobuf message and
  expr is an expression.

  conv is a converter implementing Args_conv interface that is used to map named
  placeholders to positional ones. Before used in a builder, such converter must
  be initialized first, as is done in set_args() method.

*/

class Args_conv
{
public:
  virtual unsigned conv_placeholder(const string &) = 0;
};

template <class MSG, class PRC>
struct Builder_base
  : public PRC
  , cdk::foundation::nocopy
{
  typedef MSG Message;
  typedef PRC Processor;

  Message *m_msg;
  Args_conv *m_args_conv;

  Builder_base() : m_msg(NULL), m_args_conv(NULL)
  {}

  void reset(Message &msg, Args_conv *conv = NULL)
  {
    m_msg = &msg;
    m_args_conv = conv;
  }

  virtual ~Builder_base() {}
};


// -----------------------------------------------------------------------

/*
  Builder templates
  =================

  Templates defined below construct array or object builders from base
  builders for plain values.
*/

/*
  Array_builder<BLD, MSG> template defines a message builder which builds
  an array message of type MSG from a list of expression.
  Base builder of type BLD is used to build each expression in
  the list. Structure Arr_msg_traits<MSG> must be defined for message type MSG
  and the base builder must build sub-messages of the type defined by these
  traits. Alternative traits can be specified if needed.
*/

template <class BLD, class MSG, class Traits = Arr_msg_traits<MSG> >
class Array_builder
  : public Builder_base<
             MSG,
             cdk::api::List_processor<typename BLD::Processor>
           >
{
  typedef Builder_base<
             MSG,
             cdk::api::List_processor<typename BLD::Processor>
           >  Base;

  using Base::m_msg;
  using Base::m_args_conv;

public:

  typedef typename Base::Message   Message;
  typedef typename Base::Processor Processor;
  typedef typename Processor::Element_prc  Element_prc;

protected:

  Element_prc* list_el()
  {
    BLD *bld = get_el_builder();
    bld->reset(Traits::add_element(*m_msg),
               this->m_args_conv);
    return bld;
  }

private:

  scoped_ptr<BLD>   m_el_builder;

public:

  BLD* get_el_builder()
  {
    if (!m_el_builder)
      m_el_builder.reset(new BLD());
    return m_el_builder.get();
  }

};


/*
  Any_builder_base<BLD, MSG> template defines a message builder which builds
  a message that can store plain expression or array of expressions or a
  document, from an Any value that can be one of these things.

  Base builder of type BLD is used to build sub-messages from base (scalar)
  values. Structure Any_msg_traits<MSG> must be defined for message type MSG
  and the base builder must build sub-messages of the Scalar type defined by
  these traits. Builders for arrays and documents are generated from the
  base builder.
*/

template <class BLD, class MSG, class Traits = Obj_msg_traits<MSG> >
class Doc_builder_base;

template <class BLD, class MSG, class Traits = Any_msg_traits<MSG> >
class Any_builder_base
  : public Builder_base<
             MSG,
             cdk::api::Any_processor<typename BLD::Processor>
           >
{
  typedef Builder_base<
            MSG,
            cdk::api::Any_processor<typename BLD::Processor>
          >  Base;

  using Base::m_msg;
  using Base::m_args_conv;

public:

  typedef typename Base::Message   Message;
  typedef typename Base::Processor Processor;

protected:

  typedef typename Traits::Object  Object;
  typedef typename Traits::Array   Array;
  typedef Doc_builder_base<BLD, Object>           Obj_builder;
  typedef Array_builder<Any_builder_base, Array>  Arr_builder;

  typedef typename Processor::Scalar_prc  Scalar_prc;
  typedef typename Processor::Doc_prc     Doc_prc;
  typedef typename Processor::List_prc    List_prc;

  Scalar_prc* scalar()
  {
    BLD *sb = get_scalar_builder();
    sb->reset(Traits::get_scalar(*m_msg),
              this->m_args_conv);
    return sb;
  }

  Doc_prc* doc()
  {
    Obj_builder *ob = get_obj_builder();
    ob->reset(Traits::get_object(*m_msg),
              this->m_args_conv);
    return ob;
  }

  List_prc* arr()
  {
    Arr_builder *ab = get_arr_builder();
    ab->reset(Traits::get_array(*m_msg),
              this->m_args_conv);
    return ab;
  }

private:

  BLD m_scalar_builder;
  Arr_builder m_arr_builder;
  scoped_ptr<Obj_builder> m_obj_builder;

public:

  BLD* get_scalar_builder()
  { return &m_scalar_builder; }

  Arr_builder* get_arr_builder()
  { return &m_arr_builder; }

  Obj_builder* get_obj_builder()
  {
    if (!m_obj_builder)
      m_obj_builder.reset(new Obj_builder());
    return m_obj_builder.get();
  }

};


/*
  Doc_builder_base<BLD, MSG> template defines a message builder which builds
  an object message from a document expression.

  Base builder of type BLD is used to build sub-messages from base (scalar)
  values. Structure Obj_msg_traits<MSG> must be defined for message type MSG
  and the base builder must build sub-messages of the Scalar type defined by
  these traits.

  In the object keys are mapped to any values which can be either base values,
  arrays or sub-documents. The base builder is used only for base values -
  builders for arrays and sub-documents are generated from it.
*/

template <class BLD, class MSG, class Traits> // = Obj_msg_traits<typename BLD::Message> >
class Doc_builder_base
  : public Builder_base<
             MSG,
             cdk::api::Doc_processor< typename BLD::Processor >
           >
{
  typedef Builder_base<
            MSG,
            cdk::api::Doc_processor< typename BLD::Processor >
          >  Base;

  using Base::m_msg;
  using Base::m_args_conv;

public:

  typedef typename Base::Message   Message;
  typedef typename Base::Processor Processor;
  typedef typename Traits::Array   Array;
  typedef typename Traits::Msg     Any;

protected:

  typedef Any_builder_base<BLD, Any>    Any_builder;
  using typename Processor::Any_prc;


  Any_prc* key_val(const string &key)
  {
    Any &val = Traits::add_key(*m_msg,key);
    Any_builder *ab = get_any_builder();
    ab->reset(val, this->m_args_conv);
    return ab;
  }

private:

  Any_builder m_any_builder;

public:

  Any_builder* get_any_builder()
  { return &m_any_builder; }

};


// ----------------------------------------------------------------------


/*
  Scalar and expression builders
  ==============================

  The following builders are defined below using the generic templates:

  Scalar_builder - build Mysqlx::Datatypes::Scalar message from Any::Scalar
                   expression.

  Any_builder    - build Mysqlx::Datatypes::Any message from Any expression.

  Expr_builder   - build Mysqlx::Expr::Expr message from full expression of
                   type Expression.

  Build scalar value given by Any::Processor callbacks and store it in
  protobuf message of type MSG. Msg_traits<MSG>.get_scalar() defines
  where the scalar is stored within the message.
*/


/*
  This scalar builder is used to build either Mysqlx::Datatypes::Scalar
  or Mysqlx::Expr::Expr messages. Both types of messages can store plain
  scalar values.
*/

template <class MSG>
class Scalar_builder_base
  : public Builder_base<MSG, api::Scalar_processor>
{
  typedef Builder_base<MSG, api::Scalar_processor>  Base;

  using Base::m_msg;

  typedef api::Scalar_processor::Octets_content_type Octets_content_type;

protected:

  typedef Mysqlx::Datatypes::Scalar Scalar;
  typedef Scalar::String            String;

  Scalar& get_scalar(Scalar::Type type)
  {
    return Scalar_msg_traits<MSG>::get_scalar(*m_msg, type);
  }

  String& get_string()
  {
    Scalar &sc= get_scalar(Scalar::V_STRING);
    return *sc.mutable_v_string();
  }

protected:

  void null();
  void str(bytes val);
  void str(collation_id_t cs, bytes val);
  void num(int64_t val);
  void num(uint64_t val);
  void num(float val);
  void num(double val);
  void yesno(bool val);
  void octets(bytes val, Octets_content_type type);
};


typedef Scalar_builder_base<Mysqlx::Datatypes::Scalar>  Scalar_builder;

class Any_builder :
  public Any_builder_base<Scalar_builder, Mysqlx::Datatypes::Any>
{
public:

  Any_builder()
  {}

  Any_builder(Mysqlx::Datatypes::Any &msg, Args_conv *conv)
  {
    reset(msg, conv);
  }
};


/*
  Builder for base expressions. Below it is extended to full expressions
  using Any_builder_base<> template.
*/

class Expr_builder_base
  : public Builder_base<Mysqlx::Expr::Expr, api::Expr_processor>
{
public:

  typedef Mysqlx::Expr::Expr Expr;
  typedef Any_builder_base<Expr_builder_base, Expr> Expr_builder;

  using Builder_base<Mysqlx::Expr::Expr, api::Expr_processor>::m_args_conv;

protected:

  Scalar_builder_base<Expr>   m_scalar_builder;
  scoped_ptr<Args_prc> m_args_builder;

  template <class MSG>
  Args_prc* get_args_builder(MSG&);

  Value_prc* val()
  {
    m_scalar_builder.reset(Expr_msg_traits::get_scalar(*m_msg), this->m_args_conv);
    return &m_scalar_builder;
  }

  Args_prc* op(const char *name);
  Args_prc* call(const api::Db_obj& db_obj);

  void var(const string &name);
  void id(const string &name, const api::Db_obj *coll);
  void id(const string &name, const api::Db_obj *coll,
                  const api::Doc_path &path);
  void id(const api::Doc_path &path);

  void placeholder();
  void placeholder(const string &name);
  void placeholder(unsigned pos);

};


class Expr_builder
  : public Any_builder_base<Expr_builder_base, Mysqlx::Expr::Expr>
{

public:

  Expr_builder()
  {}

  Expr_builder(Mysqlx::Expr::Expr &msg, Args_conv *conv = NULL)
  {
    reset(msg, conv);
  }

protected:

};


/*
  Builder used to store operator or function call arguments inside
  a sub-message of Expr message.
*/

template <class MSG>
struct Args_builder
  : public Builder_base<MSG, api::Expr_list::Processor>
{
  typedef Builder_base<MSG, api::Expr_list::Processor>  Base;

  using Base::m_msg;
  using Base::m_args_conv;
  using Base::reset;

  Expr_builder m_arg_builder;

  Args_builder(MSG &msg, Args_conv *conv = NULL)
  {
    reset(msg, conv);
  }

  using typename Builder_base<MSG, api::Expr_list::Processor>::Element_prc;

  Element_prc* list_el()
  {
    m_arg_builder.reset(*m_msg->add_param(), this->m_args_conv);
    return &m_arg_builder;
  }
};


template <class MSG>
inline
Expr_builder_base::Args_prc*
Expr_builder_base::get_args_builder(MSG &msg)
{
  m_args_builder.reset(new Args_builder<MSG>(msg, this->m_args_conv));
  return m_args_builder.get();
}


// ---------------------------------------------------------------------

/*
  Scalar builder implementation
  =============================
*/

template <class MSG>
inline
void Scalar_builder_base<MSG>::null()
{
  get_scalar(Scalar::V_NULL);
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::str(bytes val)
{
  //TODO: Default charset handling - must be clarified in protocol specs.
  get_string().set_value(val.begin(), val.size());
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::str(collation_id_t cs, bytes val)
{
  String &str= get_string();
  str.set_collation(cs);
  str.set_value(val.begin(), val.size());
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::num(int64_t val)
{
  get_scalar(Scalar::V_SINT).set_v_signed_int(val);
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::num(uint64_t val)
{
  get_scalar(Scalar::V_UINT).set_v_unsigned_int(val);
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::num(float val)
{
  get_scalar(Scalar::V_FLOAT).set_v_float(val);
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::num(double val)
{
  get_scalar(Scalar::V_DOUBLE).set_v_double(val);
}

template <class MSG>
inline
void Scalar_builder_base<MSG>::yesno(bool val)
{
  get_scalar(Scalar::V_BOOL).set_v_bool(val);
}



template <class MSG>
inline
void Scalar_builder_base<MSG>::octets(bytes val, Octets_content_type type)
{
  ::Mysqlx::Datatypes::Scalar_Octets *octets =
      get_scalar(Scalar::V_OCTETS).mutable_v_octets();
  octets->set_value(val.begin(), val.size());
  octets->set_content_type(type);
}


// ---------------------------------------------------------------------

/*
  Plain expression builder implementation
  =======================================
*/

inline
Expr_builder_base::Args_prc*
Expr_builder_base::op(const char *name)
{
  m_msg->set_type(Expr::OPERATOR);
  Mysqlx::Expr::Operator *op = m_msg->mutable_operator_();
  op->set_name(name);
  return get_args_builder(*op);
}


/*
  Callback for FUNC_CALL expression type
*/
inline
Expr_builder_base::Args_prc*
Expr_builder_base::call(const api::Db_obj& db_obj)
{
  m_msg->set_type(Expr::FUNC_CALL);
  Mysqlx::Expr::FunctionCall *fc = m_msg->mutable_function_call();
  Mysqlx::Expr::Identifier *id = fc->mutable_name();

  id->set_name(db_obj.get_name());
  const string *schema = db_obj.get_schema();
  if (schema)
    id->set_schema_name(*schema);

  return get_args_builder(*fc);
}


inline
void Expr_builder_base::var(const string &name)
{
  m_msg->set_type(Mysqlx::Expr::Expr_Type_VARIABLE);
  m_msg->set_variable(name);
}


/*
  Callback for IDENT expression type
*/
inline
void Expr_builder_base::id(const string &name, const api::Db_obj *db_obj)
{
  m_msg->set_type(Expr::IDENT);
  Mysqlx::Expr::ColumnIdentifier *p_col_id = m_msg->mutable_identifier();
  p_col_id->set_name(name);

  if (!db_obj)
    return;

  p_col_id->set_table_name(db_obj->get_name());

  const string *schema= db_obj->get_schema();

  if (!schema)
    return;

  p_col_id->set_schema_name(*schema);
}


/*
  Callback for IDENT expression type with only Doc_path parameter
*/
inline
void Expr_builder_base::id(const api::Doc_path &doc)
{
  m_msg->set_type(Expr::IDENT);
  Mysqlx::Expr::ColumnIdentifier *p_col_id = m_msg->mutable_identifier();

  for (unsigned pos = 0; pos < doc.length(); ++pos)
  {
    Mysqlx::Expr::DocumentPathItem *dpi = p_col_id->add_document_path();
    dpi->set_type(static_cast<Mysqlx::Expr::DocumentPathItem_Type>(doc.get_type(pos)));

    switch (doc.get_type(pos))
    {
    case api::Doc_path::MEMBER:
      if (doc.get_name(pos))
        dpi->set_value(*doc.get_name(pos));
      break;

    case api::Doc_path::ARRAY_INDEX:
      if (doc.get_index(pos))
        dpi->set_index(*doc.get_index(pos));
      break;

    case api::Doc_path::DOUBLE_ASTERISK:
    case api::Doc_path::ARRAY_INDEX_ASTERISK:
    case api::Doc_path::MEMBER_ASTERISK:
      break;
    }
  }
}


/*
  Callback for IDENT expression type with name and Doc_path parameter
*/
inline
void Expr_builder_base::id(const string &name, const api::Db_obj *db_obj, const api::Doc_path &doc)
{
  id(name, db_obj);
  id(doc);
}


/*
  Callback for PLACEHOLDER expression type
*/
inline
void Expr_builder_base::placeholder()
{
  m_msg->set_type(Expr::PLACEHOLDER);
  // TODO: Does protocol support anonymous placeholders?
}

/*
  Callback for a named PLACEHOLDER expression type with name
*/

inline
void Expr_builder_base::placeholder(const string &name)
{
  if (!m_args_conv)
    throw_error(
          (boost::format("Calling placeholder(%s) without an Args_conv!")
           % name
           ).str());
  placeholder(m_args_conv->conv_placeholder(name));
}

inline
void Expr_builder_base::placeholder(unsigned pos)
{
  placeholder();
  m_msg->set_position(pos);
}


}}} // cdk::protocol::mysqlx

#endif
