// file      : odb/mysql/container-statements.hxx
// license   : GNU GPL v2; see accompanying LICENSE file

#ifndef ODB_MYSQL_CONTAINER_STATEMENTS_HXX
#define ODB_MYSQL_CONTAINER_STATEMENTS_HXX

#include <odb/pre.hxx>

#include <memory>  // std::unique_ptr
#include <cstddef> // std::size_t

#include <odb/forward.hxx>
#include <odb/schema-version.hxx>
#include <odb/traits.hxx>

#include <odb/mysql/mysql.hxx>
#include <odb/mysql/version.hxx>
#include <odb/mysql/statement.hxx>

#include <odb/mysql/details/export.hxx>

namespace odb
{
  namespace mysql
  {
    class connection;

    // Template argument is the generated abstract container traits type.
    // That is, it doesn't need to provide column counts and statements.
    //
    template <typename T>
    class container_statements
    {
    public:
      typedef T traits;

      typedef typename traits::data_image_type data_image_type;
      typedef typename traits::functions_type functions_type;

      typedef mysql::insert_statement insert_statement_type;
      typedef mysql::select_statement select_statement_type;
      typedef mysql::delete_statement delete_statement_type;

      typedef mysql::connection connection_type;

      container_statements (connection_type&, binding& id_binding);

      container_statements (const container_statements&) = delete;
      container_statements& operator= (const container_statements&) = delete;

      connection_type&
      connection ()
      {
        return conn_;
      }

      // Functions.
      //
      functions_type&
      functions ()
      {
        return functions_;
      }

      // Schema version.
      //
      const schema_version_migration&
      version_migration () const {return *svm_;}

      void
      version_migration (const schema_version_migration& svm) {svm_ = &svm;}

      // Id image binding (external).
      //
      const binding&
      id_binding ()
      {
        return id_binding_;
      }

      // Data image. The image is split into the id (that comes as a binding)
      // and index/key plus value which are in data_image_type. The select
      // binding is a subset of the full binding (skipping id).
      //
      data_image_type&
      data_image ()
      {
        return data_image_;
      }

      MYSQL_BIND*
      data_bind ()
      {
        return insert_image_binding_.bind;
      }

      bool
      data_binding_test_version () const
      {
        return data_id_binding_version_ != id_binding_.version ||
          data_image_version_ != data_image_.version ||
          insert_image_binding_.version == 0;
      }

      void
      data_binding_update_version ()
      {
        data_id_binding_version_ = id_binding_.version;
        data_image_version_ = data_image_.version;
        insert_image_binding_.version++;
        select_image_binding_.version++;
      }

      MYSQL_BIND*
      select_bind ()
      {
        return data_bind ();
      }

      bool
      select_binding_test_version () const
      {
        return data_binding_test_version ();
      }

      void
      select_binding_update_version ()
      {
        data_binding_update_version ();
      }

      my_bool*
      select_image_truncated ()
      {
        return select_image_truncated_;
      }

      //
      // Statements.
      //

      insert_statement_type&
      insert_statement ()
      {
        if (insert_ == 0)
          insert_.reset (
            new insert_statement_type (
              conn_,
              insert_text_,
              versioned_,   // Process if versioned.
              insert_image_binding_,
              0,
              false));

        return *insert_;
      }

      select_statement_type&
      select_statement ()
      {
        if (select_ == 0)
          select_.reset (
            new select_statement_type (
              conn_,
              select_text_,
              versioned_,   // Process if versioned.
              false,        // Don't optimize.
              id_binding_,
              select_image_binding_,
              false));

        return *select_;
      }

      delete_statement_type&
      delete_statement ()
      {
        if (delete_ == 0)
          delete_.reset (
            new delete_statement_type (
              conn_, delete_text_, id_binding_, false));

        return *delete_;
      }

    protected:
      connection_type& conn_;
      binding& id_binding_;

      functions_type functions_;

      data_image_type data_image_;
      std::size_t data_image_version_;
      std::size_t data_id_binding_version_;

      binding insert_image_binding_;

      binding select_image_binding_;
      my_bool* select_image_truncated_;

      const char* insert_text_;
      const char* select_text_;
      const char* delete_text_;

      bool versioned_;
      const schema_version_migration* svm_;

      std::unique_ptr<insert_statement_type> insert_;
      std::unique_ptr<select_statement_type> select_;
      std::unique_ptr<delete_statement_type> delete_;
    };

    // Similar to container_statements (indirect) but here we cannot share the
    // binding between insert and select (so select gets its own).
    //
    template <typename T>
    class direct_container_statements
    {
    public:
      typedef T traits;

      typedef typename traits::data_image_type data_image_type;
      typedef typename traits::functions_type functions_type;

      typedef mysql::insert_statement insert_statement_type;
      typedef mysql::select_statement select_statement_type;
      typedef mysql::delete_statement delete_statement_type;

      typedef mysql::connection connection_type;

      direct_container_statements (connection_type&, binding& id_binding);

      direct_container_statements (const direct_container_statements&) = delete;
      direct_container_statements& operator= (const direct_container_statements&) = delete;

      connection_type&
      connection ()
      {
        return conn_;
      }

      // Functions.
      //
      functions_type&
      functions ()
      {
        return functions_;
      }

      // Schema version.
      //
      const schema_version_migration&
      version_migration () const {return *svm_;}

      void
      version_migration (const schema_version_migration& svm) {svm_ = &svm;}

      // Id image binding (external).
      //
      const binding&
      id_binding ()
      {
        return id_binding_;
      }

      // Data image. The image is split into the id (that comes as a binding)
      // and index/key plus value which are in data_image_type. The select
      // binding is separate since it has to bind pointed objects's images
      // fully rather than just their ids.
      //
      data_image_type&
      data_image ()
      {
        return data_image_;
      }

      MYSQL_BIND*
      data_bind ()
      {
        return insert_image_binding_.bind;
      }

      bool
      data_binding_test_version () const
      {
        return data_id_binding_version_ != id_binding_.version ||
          data_image_version_ != data_image_.version ||
          insert_image_binding_.version == 0;
      }

      void
      data_binding_update_version ()
      {
        data_id_binding_version_ = id_binding_.version;
        data_image_version_ = data_image_.version;
        insert_image_binding_.version++;
      }

      MYSQL_BIND*
      select_bind ()
      {
        return select_image_binding_.bind;
      }

      bool
      select_binding_test_version () const
      {
        return select_image_version_ != data_image_.version ||
          select_image_binding_.version == 0;
      }

      void
      select_binding_update_version ()
      {
        select_image_version_ = data_image_.version;
        select_image_binding_.version++;
      }

      my_bool*
      select_image_truncated ()
      {
        return select_image_truncated_;
      }

      //
      // Statements.
      //

      insert_statement_type&
      insert_statement ()
      {
        if (insert_ == 0)
          insert_.reset (
            new insert_statement_type (
              conn_,
              insert_text_,
              versioned_,   // Process if versioned.
              insert_image_binding_,
              0,
              false));

        return *insert_;
      }

      select_statement_type&
      select_statement ()
      {
        if (select_ == 0)
          select_.reset (
            new select_statement_type (
              conn_,
              select_text_,
              versioned_,   // Process if versioned.
              false,        // Don't optimize.
              id_binding_,
              select_image_binding_,
              false));

        return *select_;
      }

      delete_statement_type&
      delete_statement ()
      {
        if (delete_ == 0)
          delete_.reset (
            new delete_statement_type (
              conn_, delete_text_, id_binding_, false));

        return *delete_;
      }

    protected:
      connection_type& conn_;
      binding& id_binding_;

      functions_type functions_;

      data_image_type data_image_;
      std::size_t data_image_version_;
      std::size_t data_id_binding_version_;

      binding insert_image_binding_;

      std::size_t select_image_version_;
      binding select_image_binding_;
      my_bool* select_image_truncated_;

      const char* insert_text_;
      const char* select_text_;
      const char* delete_text_;

      bool versioned_;
      const schema_version_migration* svm_;

      std::unique_ptr<insert_statement_type> insert_;
      std::unique_ptr<select_statement_type> select_;
      std::unique_ptr<delete_statement_type> delete_;
    };

    // B should be either container_statements or direct_container_statements
    // (what smart adds is the same for direct/indirect, which only affects
    // select).
    //
    template <typename T, template <typename> class B>
    class smart_container_statements: public B<T>
    {
    public:
      typedef T traits;
      typedef typename traits::cond_image_type cond_image_type;

      typedef mysql::update_statement update_statement_type;
      typedef mysql::delete_statement delete_statement_type;

      typedef mysql::connection connection_type;

      smart_container_statements (connection_type&, binding& id_binding);

      // Condition image. The image is split into the id (that comes as
      // a binding) and index/key/value which is in cond_image_type.
      //
      cond_image_type&
      cond_image ()
      {
        return cond_image_;
      }

      MYSQL_BIND*
      cond_bind ()
      {
        return cond_image_binding_.bind;
      }

      bool
      cond_binding_test_version () const
      {
        return cond_id_binding_version_ != this->id_binding_.version ||
          cond_image_version_ != cond_image_.version ||
          cond_image_binding_.version == 0;
      }

      void
      cond_binding_update_version ()
      {
        cond_id_binding_version_ = this->id_binding_.version;
        cond_image_version_ = cond_image_.version;
        cond_image_binding_.version++;
      }

      // Update image. The image is split as follows: value comes
      // from the data image, id comes as binding, and index/key
      // comes from the condition image.
      //
      MYSQL_BIND*
      update_bind ()
      {
        return update_image_binding_.bind;
      }

      bool
      update_binding_test_version () const
      {
        return update_id_binding_version_ != this->id_binding_.version ||
          update_cond_image_version_ != cond_image_.version ||
          update_data_image_version_ != this->data_image_.version ||
          update_image_binding_.version == 0;
      }

      void
      update_binding_update_version ()
      {
        update_id_binding_version_ = this->id_binding_.version;
        update_cond_image_version_ = cond_image_.version;
        update_data_image_version_ = this->data_image_.version;
        update_image_binding_.version++;
      }

      //
      // Statements.
      //

      delete_statement_type&
      delete_statement ()
      {
        if (this->delete_ == 0)
          this->delete_.reset (
            new delete_statement_type (
              this->conn_,
              this->delete_text_,
              this->cond_image_binding_,
              false));

        return *this->delete_;
      }

      update_statement_type&
      update_statement ()
      {
        if (update_ == 0)
          update_.reset (
            new update_statement_type (
              this->conn_,
              update_text_,
              this->versioned_, // Process if versioned.
              update_image_binding_,
              false));

        return *update_;
      }

    protected:
      cond_image_type cond_image_;
      std::size_t cond_image_version_;
      std::size_t cond_id_binding_version_;
      binding cond_image_binding_;

      std::size_t update_id_binding_version_;
      std::size_t update_cond_image_version_;
      std::size_t update_data_image_version_;
      binding update_image_binding_;

      const char* update_text_;

      std::unique_ptr<update_statement_type> update_;
    };

    // Template argument is the generated concrete container traits type
    // (direct/indirect should be consistent with T::statements_type).
    //
    template <typename T>
    class container_statements_impl: public T::statements_type
    {
    public:
      typedef T traits;
      typedef typename T::statements_type base;
      typedef mysql::connection connection_type;

      container_statements_impl (connection_type&, binding&);

      container_statements_impl (const container_statements_impl&) = delete;
      container_statements_impl& operator= (const container_statements_impl&) = delete;

    private:
      MYSQL_BIND data_image_bind_[traits::data_column_count];
      my_bool select_image_truncated_array_[traits::data_column_count -
                                            traits::id_column_count];
    };

    template <typename T>
    class direct_container_statements_impl: public T::statements_type
    {
    public:
      typedef T traits;
      typedef typename T::statements_type base;
      typedef mysql::connection connection_type;

      direct_container_statements_impl (connection_type&, binding&);

      direct_container_statements_impl (const direct_container_statements_impl&) = delete;
      direct_container_statements_impl& operator= (const direct_container_statements_impl&) = delete;

    private:
      MYSQL_BIND data_image_bind_[traits::data_column_count];

      MYSQL_BIND select_image_bind_[traits::select_column_count];
      my_bool select_image_truncated_array_[traits::select_column_count];
    };

    // B should be either container_statements_impl or
    // direct_container_statements_impl.
    //
    template <typename T, template <typename> class B>
    class smart_container_statements_impl: public B<T>
    {
    public:
      typedef T traits;
      typedef mysql::connection connection_type;

      smart_container_statements_impl (connection_type&, binding&);

    private:
      MYSQL_BIND cond_image_bind_[traits::cond_column_count];
      MYSQL_BIND update_image_bind_[traits::value_column_count +
                                    traits::cond_column_count];
    };
  }
}

#include <odb/mysql/container-statements.txx>

#include <odb/post.hxx>

#endif // ODB_MYSQL_CONTAINER_STATEMENTS_HXX
