/*
   Copyright (c) 2011, 2015 Oracle and/or its affiliates. All rights reserved.

   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 also distributed 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 included with MySQL.

   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
*/

#ifndef NDB_PACK_HPP
#define NDB_PACK_HPP

#include <ndb_global.h>
#include <my_sys.h>
#include <kernel/AttributeHeader.hpp>
#include <NdbSqlUtil.hpp>
#include <NdbEnv.h>
class NdbOut;

/*
 * Pack an array of NDB data values.  The types are specified by an
 * array of data types.  There is no associated table or attribute ids.
 * All or an initial sequence of the specified values are present.
 *
 * Currently used for ordered index keys and bounds in kernel (DBTUX)
 * and in index statistics (mysqld).  The comparison methods use the
 * primitive type comparisons from NdbSqlUtil.
 *
 * Keys and bounds use same spec.  However a value in an index bound can
 * be NULL even if the key attribute is not nullable.  Therefore bounds
 * set the "allNullable" property and have a longer null mask.
 *
 * There are two distinct use occasions: 1) construction of data or
 * bound 2) operating on previously constructed data or bound.  There
 * are classes Data/DataC and Bound/BoundC for these uses.  The latter
 * often can return a result without interpreting the full value.
 *
 * Methods return -1 on error and 0 on success.  Comparison methods
 * assume well-formed data and return negative, zero, positive for less,
 * equal, greater.
 */

class NdbPack {
public:
  class Endian;
  class Type;
  class Spec;
  class Iter;
  class DataC;
  class Data;
  class BoundC;
  class Bound;

  /*
   * Get SQL type.
   */
  static const NdbSqlUtil::Type& getSqlType(Uint32 typeId);

  /*
   * Error codes for core dumps.
   */
  class Error {
  public:
    enum {
      TypeNotSet = -101,          // type id was not set
      TypeOutOfRange = -102,      // type id is out of range
      TypeNotSupported = -103,    // blob (and for now bit) types
      TypeSizeZero = -104,        // max size was set to zero
      TypeFixSizeInvalid = -105,  // fixed size specified wrong
      TypeNullableNotBool = -106, // nullable must be 0 or 1
      CharsetNotSpecified = -107, // char type with no charset number
      CharsetNotFound = -108,     // cannot install in all_charsets[]
      CharsetNotAllowed = -109,   // non-char type with charset
      SpecBufOverflow = -201,     // more spec items than allocated
      DataCntOverflow = -301,     // more data items than in spec
      DataBufOverflow = -302,     // more data bytes than allocated
      DataValueOverflow = -303,   // var length exceeds max size
      DataNotNullable = -304,     // NULL value to not-nullable type
      InvalidAttrInfo = -305,     // invalid plain old attr info
      BoundEmptySide = -401,      // side not 0 for empty bound
      BoundNonemptySide = -402,   // side not -1,+1 for non-empty bound
      InternalError = -901,
      ValidationError = -902,
      NoError = 0
    };
    Error();
    ~Error() {}
    int get_error_code() const;
    int get_error_line() const;

  private:
    friend class Endian;
    friend class Type;
    friend class Spec;
    friend class Iter;
    friend class DataC;
    friend class Data;
    friend class BoundC;
    friend class Bound;
    void set_error(int code, int line) const;
    void set_error(const Error& e2) const;
    mutable int m_error_code;
    mutable int m_error_line;
  };

  /*
   * Endian definitions.
   */
  class Endian {
  public:
    enum Value {
      Native = 0, // replaced by actual value
      Little = 1,
      Big = 2
    };
    static Value get_endian();
    static void convert(void* ptr, Uint32 len);
  };

  /*
   * Data type.
   */
  class Type : public Error {
  public:
    Type();
    Type(int typeId, Uint32 byteSize, bool nullable, Uint32 csNumber);
    ~Type() {}
    /*
     * Define the type.  Size is fixed or max size.  Values of variable
     * length have length bytes.  The definition is verified when the
     * type is added to the specification.  This also installs missing
     * CHARSET_INFO* into all_charsets[].
     */
    void set(Uint32 typeId, Uint32 byteSize, bool nullable, Uint32 csNumber);
    // getters
    Uint32 get_type_id() const;
    Uint32 get_byte_size() const;
    bool get_nullable() const;
    Uint32 get_cs_number() const;
    Uint32 get_array_type() const;
    // print
    friend NdbOut& operator<<(NdbOut&, const Type&);
    void print(NdbOut& out) const;
    const char* print(char* buf, Uint32 bufsz) const;
    int validate() const;

  private:
    friend class Spec;
    friend class Iter;
    friend class DataC;
    friend class Data;
    // verify and complete when added to specification
    int complete();
    Uint16 m_typeId;
    Uint16 m_byteSize;    // fixed or max size in bytes
    Uint16 m_nullable;
    Uint16 m_csNumber;
    Uint16 m_arrayType;   // 0,1,2 length bytes
    Uint16 m_nullbitPos;  // computed as part of Spec
  };

  /*
   * Data specification i.e. array of types.  Usually constructed on the
   * heap, so keep fairly small.  Used for boths keys and bounds.
   */
  class Spec : public Error {
  public:
    Spec();
    ~Spec() {}
    // set initial buffer (calls reset)
    void set_buf(Type* buf, Uint32 bufMaxCnt);
    // use if buffer is relocated
    void set_buf(Type* buf);
    // reset but keep buffer
    void reset();
    // add type to specification once or number of times
    int add(Type type);
    int add(Type type, Uint32 cnt);
    // copy from
    void copy(const Spec& s2);
    // getters (bounds set allNullable)
    const Type& get_type(Uint32 i) const;
    Uint32 get_cnt() const;
    Uint32 get_nullable_cnt(bool allNullable) const;
    Uint32 get_nullmask_len(bool allNullable) const;
    // max data length including null mask
    Uint32 get_max_data_len(bool allNullable) const;
    // minimum var bytes (if used by Data instance)
    Uint32 get_min_var_bytes(bool allNullable) const;
    // print
    friend NdbOut& operator<<(NdbOut&, const Spec&);
    void print(NdbOut& out) const;
    const char* print(char* buf, Uint32 bufsz) const;
    int validate() const;

  private:
    friend class Iter;
    friend class DataC;
    friend class Data;
    friend class BoundC;
    // undefined
    Spec(const Spec&);
    Spec& operator=(const Spec&);
    Type* m_buf;
    Uint16 m_bufMaxCnt;
    Uint16 m_cnt;
    Uint16 m_nullableCnt;
    Uint16 m_varsizeCnt;
    Uint32 m_maxByteSize; // excludes null mask
  };

  /*
   * Iterator over data items.  DataC uses external Iter instances in
   * comparison methods etc.  Data contains an Iter instance which
   * iterates on items added.
   */
  class Iter : public Error {
  public:
    // the data instance is only used to set metadata
    Iter(const DataC& data);
    ~Iter() {}
    void reset();

  private:
    friend class DataC;
    friend class Data;
    friend class BoundC;
    // undefined
    Iter(const Iter&);
    Iter& operator=(const Iter&);
    // describe next non-null or null item and advance iterator
    int desc(const Uint8* item);
    int desc_null();
    // compare current items (DataC buffers are passed)
    int cmp(const Iter& r2, const Uint8* buf1, const Uint8* buf2) const;

    const Spec& m_spec;
    const bool m_allNullable;
    // iterator
    Uint32 m_itemPos;     // position of current item in DataC buffer
    Uint32 m_cnt;         // number of items described so far
    Uint32 m_nullCnt;
    // current item
    Uint32 m_lenBytes;    // 0-2
    Uint32 m_bareLen;     // excludes length bytes
    Uint32 m_itemLen;     // full length, value zero means null
  };

  /*
   * Read-only superclass of Data.  Initialized from a previously
   * constructed Data buffer (any var bytes skipped).  Methods interpret
   * one data item at a time.  Values are native endian.
   */
  class DataC : public Error {
  public:
    DataC(const Spec& spec, bool allNullable);
    // set buffer to previously constructed one with given item count
    void set_buf(const void* buf, Uint32 bufMaxLen, Uint32 cnt);
    // interpret next data item
    int desc(Iter& r) const;
    // compare cnt attrs and also return number of initial equal attrs
    int cmp(const DataC& d2, Uint32 cnt, Uint32& num_eq) const;
    // getters
    const Spec& get_spec() const;
    const void* get_data_buf() const;
    Uint32 get_cnt() const;
    bool is_empty() const;
    bool is_full() const;
    // print
    friend NdbOut& operator<<(NdbOut&, const DataC&);
    void print(NdbOut& out) const;
    const char* print(char* buf, Uint32 bufsz, bool convert_flag = false) const;
    int validate() const { return 0; }

  private:
    friend class Iter;
    friend class Data;
    friend class BoundC;
    // undefined
    DataC(const Data&);
    DataC& operator=(const DataC&);
    const Spec& m_spec;
    const bool m_allNullable;
    const Uint8* m_buf;
    Uint32 m_bufMaxLen;
    // can be updated as part of Data instance
    Uint32 m_cnt;
  };

  /*
   * Instance of an array of data values.  The values are packed into
   * a byte buffer.  The buffer is also maintained as a single varbinary
   * value if non-zero var bytes (length bytes) is specified.
   *
   * Data instances can be received from another source (such as table
   * in database) and may not be native-endian.  Such instances must
   * first be completed with desc_all() and convert().
   */
  class Data : public DataC {
  public:
    Data(const Spec& spec, bool allNullable, Uint32 varBytes);
    // set buffer (calls reset)
    void set_buf(void* buf, Uint32 bufMaxLen);
    // reset but keep buffer (header is zeroed)
    void reset();
    // add non-null data items and return length in bytes
    int add(const void* data, Uint32* len_out);
    int add(const void* data, Uint32 cnt, Uint32* len_out);
    // add null data items and return length 0 bytes
    int add_null(Uint32* len_out);
    int add_null(Uint32 cnt, Uint32* len_out);
    // add from "plain old attr info"
    int add_poai(const Uint32* poai, Uint32* len_out);
    int add_poai(const Uint32* poai, Uint32 cnt, Uint32* len_out);
    // call this before first use
    int finalize();
    // copy from
    int copy(const DataC& d2);
    // convert endian
    int convert(Endian::Value to_endian);
    // create complete instance from buffer contents
    int desc_all(Uint32 cnt, Endian::Value from_endian);
    // getters
    Uint32 get_max_len() const;
    Uint32 get_max_len4() const;
    Uint32 get_var_bytes() const;
    const void* get_full_buf() const;
    Uint32 get_full_len() const;
    Uint32 get_data_len() const;
    Uint32 get_null_cnt() const;
    Endian::Value get_endian() const;
    // print
    friend NdbOut& operator<<(NdbOut&, const Data&);
    void print(NdbOut& out) const;
    const char* print(char* buf, Uint32 bufsz) const;
    int validate() const;

  private:
    friend class Iter;
    friend class Bound;
    // undefined
    Data(const Data&);
    Data& operator=(const Data&);
    int finalize_impl();
    int convert_impl(Endian::Value to_endian);
    const Uint32 m_varBytes;
    Uint8* m_buf;
    Uint32 m_bufMaxLen;
    Endian::Value m_endian;
    // iterator on items added
    Iter m_iter;
  };

  /*
   * Read-only superclass of BoundC, analogous to DataC.  Initialized
   * from a previously constructed Bound or DataC buffer.
   */
  class BoundC : public Error {
  public:
    BoundC(DataC& data);
    ~BoundC() {}
    // call this before first use
    int finalize(int side);
    // compare bound to key (may return 0 if bound is longer)
    int cmp(const DataC& d2, Uint32 cnt, Uint32& num_eq) const;
    // compare bounds (may return 0 if cnt is less than min length)
    int cmp(const BoundC& b2, Uint32 cnt, Uint32& num_eq) const;
    // getters
    DataC& get_data() const;
    int get_side() const;
    // print
    friend NdbOut& operator<<(NdbOut&, const BoundC&);
    void print(NdbOut& out) const;
    const char* print(char* buf, Uint32 bufsz) const;
    int validate() const;

  private:
    friend class Bound;
    // undefined
    BoundC(const BoundC&);
    BoundC& operator=(const BoundC&);
    DataC& m_data;
    int m_side;
  };

  /*
   * Ordered index range bound consists of a partial key and a "side".
   * The partial key is a Data instance where some initial number of
   * values are present.  It is defined separately by the caller and
   * passed to Bound ctor by reference.
   */
  class Bound : public BoundC {
  public:
    Bound(Data& data);
    ~Bound() {}
    void reset();
    // call this before first use
    int finalize(int side);
    // getters
    Data& get_data() const;
    // print
    friend NdbOut& operator<<(NdbOut&, const Bound&);
    void print(NdbOut& out) const;
    const char* print(char* buf, Uint32 bufsz) const;
    int validate() const;

  private:
    // undefined
    Bound(const Bound&);
    Bound& operator=(const Bound&);
    Data& m_data;
  };

  /*
   * Helper for print() methods.
   */
  struct Print {
  private:
    friend class Endian;
    friend class Type;
    friend class Spec;
    friend class Iter;
    friend class DataC;
    friend class Data;
    friend class BoundC;
    friend class Bound;
    Print(char* buf, Uint32 bufsz);
    void print(const char* frm, ...);
    char* m_buf;
    Uint32 m_bufsz;
    Uint32 m_sz;
  };
};

// NdbPack

inline const NdbSqlUtil::Type&
NdbPack::getSqlType(Uint32 typeId)
{
  return NdbSqlUtil::m_typeList[typeId];
}

// NdbPack::Error

inline
NdbPack::Error::Error()
{
  m_error_code = 0;
  m_error_line = 0;
}

// NdbPack::Endian

inline NdbPack::Endian::Value
NdbPack::Endian::get_endian()
{
#ifndef WORDS_BIGENDIAN
  return Little;
#else
  return Big;
#endif
}

// NdbPack::Type

inline
NdbPack::Type::Type()
{
  m_typeId = NDB_TYPE_UNDEFINED;
  m_byteSize = 0;
  m_nullable = true;
  m_csNumber = 0;
  m_arrayType = 0;
  m_nullbitPos = 0;
}

inline
NdbPack::Type::Type(int typeId, Uint32 byteSize, bool nullable, Uint32 csNumber)
{
  set(typeId, byteSize, nullable, csNumber);
}

inline void
NdbPack::Type::set(Uint32 typeId, Uint32 byteSize, bool nullable, Uint32 csNumber)
{
  m_typeId = typeId;
  m_byteSize = byteSize;
  m_nullable = nullable;
  m_csNumber = csNumber;
}

inline Uint32
NdbPack::Type::get_type_id() const
{
  return m_typeId;
}

inline Uint32
NdbPack::Type::get_byte_size() const
{
  return m_byteSize;
}

inline bool
NdbPack::Type::get_nullable() const
{
  return (bool)m_nullable;
}

inline Uint32
NdbPack::Type::get_cs_number() const
{
  return m_csNumber;
}

inline Uint32
NdbPack::Type::get_array_type() const
{
  return m_arrayType;
}

// NdbPack::Spec

inline
NdbPack::Spec::Spec()
{
  reset();
  m_buf = 0;
  m_bufMaxCnt = 0;
}

inline void
NdbPack::Spec::set_buf(Type* buf, Uint32 bufMaxCnt)
{
  reset();
  m_buf = buf;
  m_bufMaxCnt = bufMaxCnt;
}

inline void
NdbPack::Spec::set_buf(Type* buf)
{
  m_buf = buf;
}

inline void
NdbPack::Spec::reset()
{
  m_cnt = 0;
  m_nullableCnt = 0;
  m_varsizeCnt = 0;
  m_maxByteSize = 0;
}

inline const NdbPack::Type&
NdbPack::Spec::get_type(Uint32 i) const
{
  assert(i < m_cnt);
  return m_buf[i];
}

inline Uint32
NdbPack::Spec::get_cnt() const
{
  return m_cnt;
}

inline Uint32
NdbPack::Spec::get_nullable_cnt(bool allNullable) const
{
  if (!allNullable)
    return m_nullableCnt;
  else
    return m_cnt;
}

inline Uint32
NdbPack::Spec::get_nullmask_len(bool allNullable) const
{
  return (get_nullable_cnt(allNullable) + 7) / 8;
}

inline Uint32
NdbPack::Spec::get_max_data_len(bool allNullable) const
{
  return get_nullmask_len(allNullable) + m_maxByteSize;
}

inline Uint32
NdbPack::Spec::get_min_var_bytes(bool allNullable) const
{
  const Uint32 len = get_max_data_len(allNullable);
  return (len < 256 ? 1 : 2);
}

// NdbPack::Iter

inline
NdbPack::Iter::Iter(const DataC& data) :
  m_spec(data.m_spec),
  m_allNullable(data.m_allNullable)
{
  reset();
}

inline void
NdbPack::Iter::reset()
{
  m_itemPos = m_spec.get_nullmask_len(m_allNullable);
  m_cnt = 0;
  m_nullCnt = 0;
  m_lenBytes = 0;
  m_bareLen = 0;
  m_itemLen = 0;
}

// NdbPack::DataC

inline
NdbPack::DataC::DataC(const Spec& spec, bool allNullable) :
  m_spec(spec),
  m_allNullable(allNullable)
{
  m_buf = 0;
  m_bufMaxLen = 0;
  m_cnt = 0;
}

inline void
NdbPack::DataC::set_buf(const void* buf, Uint32 bufMaxLen, Uint32 cnt)
{
  m_buf = static_cast<const Uint8*>(buf);
  m_bufMaxLen = bufMaxLen;
  m_cnt = cnt;
}

inline const NdbPack::Spec&
NdbPack::DataC::get_spec() const
{
  return m_spec;
}

inline const void*
NdbPack::DataC::get_data_buf() const
{
  return &m_buf[0];
}

inline Uint32
NdbPack::DataC::get_cnt() const
{
  return m_cnt;
}

inline bool
NdbPack::DataC::is_empty() const
{
  return m_cnt == 0;
}

inline bool
NdbPack::DataC::is_full() const
{
  return m_cnt == m_spec.m_cnt;
}

// NdbPack::Data

inline
NdbPack::Data::Data(const Spec& spec, bool allNullable, Uint32 varBytes) :
  DataC(spec, allNullable),
  m_varBytes(varBytes),
  m_iter(*this)
{
  m_buf = 0;
  m_bufMaxLen = 0;
  m_endian = Endian::get_endian();
}

inline void
NdbPack::Data::set_buf(void* buf, Uint32 bufMaxLen)
{
  m_buf = static_cast<Uint8*>(buf);
  m_bufMaxLen = bufMaxLen;
  reset();
  assert(bufMaxLen >= m_varBytes);
  DataC::set_buf(&m_buf[m_varBytes], m_bufMaxLen - m_varBytes, 0);
}

inline void
NdbPack::Data::reset()
{
  m_cnt = 0;  // in DataC
  const Uint32 bytes = m_varBytes + m_spec.get_nullmask_len(m_allNullable);
  memset(m_buf, 0, bytes);
  m_endian = Endian::get_endian();
  m_iter.reset();
}

inline int
NdbPack::Data::finalize()
{
  if (m_varBytes == 0 ||
      finalize_impl() == 0)
    return 0;
  return -1;
}

inline int
NdbPack::Data::convert(Endian::Value to_endian)
{
  if (to_endian == Endian::Native)
    to_endian = Endian::get_endian();
  if (m_endian == to_endian)
    return 0;
  if (convert_impl(to_endian) == 0)
  {
    m_endian = to_endian;
    return 0;
  }
  return -1;
}

inline Uint32
NdbPack::Data::get_max_len() const
{
  return m_varBytes + m_spec.get_max_data_len(m_allNullable);
}

inline Uint32
NdbPack::Data::get_max_len4() const
{
  Uint32 len4 = get_max_len();
  len4 += 3;
  len4 /= 4;
  len4 *= 4;
  return len4;
}

inline Uint32
NdbPack::Data::get_var_bytes() const
{
  return m_varBytes;
}

inline const void*
NdbPack::Data::get_full_buf() const
{
  return &m_buf[0];
}

inline Uint32
NdbPack::Data::get_full_len() const
{
  return m_varBytes + m_iter.m_itemPos + m_iter.m_itemLen;
}

inline Uint32
NdbPack::Data::get_data_len() const
{
  return m_iter.m_itemPos + m_iter.m_itemLen;
}

inline Uint32
NdbPack::Data::get_null_cnt() const
{
  return m_iter.m_nullCnt;
}

inline NdbPack::Endian::Value
NdbPack::Data::get_endian() const
{
  return m_endian;
}

// NdbPack::BoundC

inline
NdbPack::BoundC::BoundC(DataC& data) :
  m_data(data)
{
  m_side = 0;
}

inline int
NdbPack::BoundC::cmp(const DataC& d2, Uint32 cnt, Uint32& num_eq) const
{
  const BoundC& b1 = *this;
  const DataC& d1 = b1.m_data;
  int res = d1.cmp(d2, cnt, num_eq);
  if (res == 0 && d1.m_cnt <= d2.m_cnt)
    res = b1.m_side;
  return res;
}

inline NdbPack::DataC&
NdbPack::BoundC::get_data() const
{
  return m_data;
}

inline int
NdbPack::BoundC::get_side() const
{
  return m_side;
}

// NdbPack::Bound

inline
NdbPack::Bound::Bound(Data& data) :
  BoundC(data),
  m_data(data)
{
}

inline void
NdbPack::Bound::reset()
{
  m_data.reset();
  m_side = 0;
}

inline int
NdbPack::Bound::finalize(int side)
{
  if (m_data.finalize() == -1)
  {
    set_error(m_data);
    return -1;
  }
  if (BoundC::finalize(side) == -1)
    return -1;
  return 0;
}

inline NdbPack::Data&
NdbPack::Bound::get_data() const
{
  return m_data;
}

#endif // NDB_PACK_HPP
