// file      : common/container/change-tracking/driver.cxx
// copyright : Copyright (c) 2009-2017 Code Synthesis Tools CC
// license   : GNU GPL v2; see accompanying LICENSE file

// Test change-tracking containers.
//

#include <common/config.hxx> // HAVE_CXX11

#include <memory>   // std::auto_ptr
#include <cassert>
#include <iostream>

#ifdef HAVE_CXX11
#  include <utility> // std::move
#endif

#include <odb/tracer.hxx>
#include <odb/session.hxx>
#include <odb/database.hxx>
#include <odb/transaction.hxx>

#include <common/common.hxx>

#include "test.hxx"
#include "test-odb.hxx"

using namespace std;
using namespace odb::core;

struct counting_tracer: odb::tracer
{
  void
  reset (transaction& tr) {u = i = d = s = t = 0; tr.tracer (*this);}

  virtual void
  execute (odb::connection&, const char* stmt)
  {
    string p (stmt, 6);
    if (p == "UPDATE")
      u++;
    else if (p == "INSERT")
      i++;
    else if (p == "DELETE")
      d++;
    else if (p == "SELECT")
      s++;
    t++;
  }

  size_t u, i, d, s, t;
};

static counting_tracer tr;

// Compilation test: instantiate all the functions. In C++11 mode only
// do this if we have a fairly conforming compiler that implements the
// complete std::vector interface.
//

#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
#if !defined (HAVE_CXX11) || \
  (defined (__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 7)
struct item {};
template class odb::vector<item>;
template class odb::vector_iterator<odb::vector<item>,
                                    std::vector<item>::iterator>;
template class odb::vector_iterator<odb::vector<item>,
                               std::vector<item>::reverse_iterator>;
#endif
#endif

void
f (const std::vector<int>&) {}

int
main (int argc, char* argv[])
{
  try
  {
    // Test extended interface.
    //
    {
      typedef odb::vector<int> vector;

      vector ov;
      std::vector<int> sv;
      f (ov);               // Implicit conversion to std::vector.
      vector ov1 (sv);      // Initialization from std::vector.
      ov = sv;              // Assignement from std::vector.

      // Container comparison.
      //
      if (ov != ov1 ||
          ov != sv  ||
          sv != ov1)
        ov.clear ();

      // Iterator comparison/conversion.
      //
      vector::const_iterator i (ov.begin ());
      if (i != ov.end ())
        i = ov.end ();

      // Things are just really borken in Sun CC, no matter which STL
      // you use.
      //
#ifndef __SUNPRO_CC
      vector::const_reverse_iterator j (ov.rbegin ());
      if (j != ov.rend ())
        j = ov.rend ();
#endif
    }

    auto_ptr<database> db (create_database (argc, argv));

    // Test traits logic.
    //
    {
      object o ("1");
      o.i = 123;
      o.s.push_back ("a");

      assert (!o.s._tracking ());

      // persist
      //
      {
        transaction t (db->begin ());
        db->persist (o);
        t.commit ();
      }

      assert (o.s._tracking ());

      // load
      //
      {
        transaction t (db->begin ());
#ifdef HAVE_CXX11
        unique_ptr<object> p (db->load<object> ("1"));
#else
        auto_ptr<object> p (db->load<object> ("1"));
#endif
        assert (p->s._tracking ());
        t.commit ();
      }

      // update
      //
      {
        transaction t (db->begin ());
        db->update (o);
        t.commit ();
      }

      assert (o.s._tracking ());

      // erase
      //
      {
        transaction t (db->begin ());
        db->erase (o);
        t.commit ();
      }

      assert (!o.s._tracking ());
    }

    // Test change tracking.
    //
    object o ("1");
    o.i = 123;
    o.s.push_back ("a");

    {
      transaction t (db->begin ());
      db->persist (o);
      t.commit ();
    }

    // push_back/pop_back
    //
    {
      o.s.push_back ("b"); // insert

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.i == 1 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.pop_back ();
      o.s.push_back ("c"); // update

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 2 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.pop_back ();
      for (int i (0); i != 1024; ++i)
        o.s.push_back ("x"); // realloc

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 2 && tr.i == 1023 && tr.t == 1025);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      for (int i (0); i != 1024; ++i)
        o.s.pop_back (); // delete

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.push_back ("b");
      o.s.pop_back (); // no-op

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.t == 1);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // insert
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.insert (o.s.begin (), "a1"); // insert front

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 3 && tr.i == 1 && tr.t == 4);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.insert (o.s.begin () + 1, "a2"); // insert middle

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 3 && tr.i == 1 && tr.t == 4);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.insert (o.s.end (), "b1"); // insert back

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.i == 1 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // erase
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");
      o.s.push_back ("c");
      o.s.push_back ("d");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.erase (o.s.begin ()); // erase front

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 4 && tr.d == 1 && tr.t == 5);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.erase (o.s.begin () + 1); // erase middle

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 2 && tr.d == 1 && tr.t == 3);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.erase (o.s.end () - 1); // erase back

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // modify
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");
      o.s.push_back ("c");
      o.s.push_back ("d");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.modify (1) += 'b';
      o.s.modify_at (2) += 'c';

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 3 && tr.t == 3);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.modify_front () += 'a';
      o.s.modify_back () += 'd';

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 3 && tr.t == 3);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.begin ().modify () += 'a';
#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
      o.s.rbegin ().modify () += 'c';
#endif

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
      assert (tr.u == 3 && tr.t == 3);
#else
      assert (tr.u == 2 && tr.t == 2);
#endif
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

#ifndef _RWSTD_NO_CLASS_PARTIAL_SPEC
    {
      (o.s.rbegin () + 1).modify (1) += 'a';
      (o.s.rbegin () + 1).modify (-1) += 'c';

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 3 && tr.t == 3);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }
#endif

    {
      o.s.mbegin ();

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 5 && tr.t == 5);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // clear
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");
      o.s.push_back ("c");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.clear ();

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // assign
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");
      o.s.push_back ("c");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.assign (4, "x");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 4 && tr.i == 1 && tr.t == 5);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // resize
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");
      o.s.push_back ("c");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.pop_back ();
      o.s.resize (4, "x"); // expand

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 2 && tr.i == 1 && tr.t == 3);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      o.s.push_back ("y");
      o.s.resize (3);      // shrink

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (tr.u == 1 && tr.d == 1 && tr.t == 2);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    // Transaction rollback.
    //
    {
      o.s.clear ();
      o.s.push_back ("a");
      o.s.push_back ("b");
      o.s.push_back ("c");

      transaction t (db->begin ());
      tr.reset (t);
      db->update (o);
      assert (*db->load<object> ("1") == o);
      t.commit ();
    }

    {
      {
        o.s.push_back ("d");

        transaction t (db->begin ());
        db->update (o);
        t.rollback ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (o);
        assert (tr.u == 1 && tr.i == 4 && tr.d == 1 && tr.t == 6);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (o);
        assert (tr.u == 1 && tr.t == 1);
        t.commit ();
      }
    }

    // Armed copy.
    //
    {
#ifdef HAVE_CXX11
      unique_ptr<object> c;
#else
      auto_ptr<object> c;
#endif

      {
        o.s.pop_back ();

        transaction t (db->begin ());
        db->update (o);
        c.reset (new object (o));
        t.rollback ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (c);
        assert (tr.u == 1 && tr.i == 3 && tr.d == 1 && tr.t == 5);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (c);
        assert (tr.u == 1 && tr.t == 1);
        t.commit ();
      }
    }

    // Armed swap.
    //
    {
      object c (o);

      {
        o.s.push_back ("d");

        transaction t (db->begin ());
        db->update (o);
        assert (o.s._tracking () && !c.s._tracking ());
        c.s.swap (o.s);
        assert (!o.s._tracking () && c.s._tracking ());
        t.rollback ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (c);
        assert (tr.u == 1 && tr.i == 4 && tr.d == 1 && tr.t == 6);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (c);
        assert (tr.u == 1 && tr.t == 1);
        t.commit ();
      }
    }

    // Armed move.
    //
#ifdef HAVE_CXX11
    {
      unique_ptr<object> c;

      {
        o.s.pop_back ();

        transaction t (db->begin ());
        db->update (o);
        assert (o.s._tracking ());
        c.reset (new object (std::move (o)));
        assert (!o.s._tracking () && c->s._tracking ());
        t.rollback ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (c);
        assert (tr.u == 1 && tr.i == 2 && tr.d == 1 && tr.t == 4);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        tr.reset (t);
        db->update (c);
        assert (tr.u == 1 && tr.t == 1);
        t.commit ();
      }
    }
#endif

    // Test mixing "smart" and "dumb" container (specifically, erase(obj)).
    //
    {
      mix_object o (1);
      o.ov.assign (3, 123);
      o.sv.assign (3, 123);

      {
        transaction t (db->begin ());
        db->persist (o);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        db->erase (o);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        db->persist (o);
        t.commit ();
      }
    }

    // Test using change tracking container as inverse member.
    //
    {
      inv_object1 o1;
      inv_object2 o2;
      o1.o2 = &o2;

      {
        transaction t (db->begin ());
        db->persist (o2);
        db->persist (o1);
        t.commit ();
      }

      assert (!o2.o1._tracking ());

      {
        session s;
        transaction t (db->begin ());
#ifdef HAVE_CXX11
        unique_ptr<inv_object1> p1 (db->load<inv_object1> (o1.id_));
        unique_ptr<inv_object2> p2 (db->load<inv_object2> (o2.id_));
#else
        auto_ptr<inv_object1> p1 (db->load<inv_object1> (o1.id_));
        auto_ptr<inv_object2> p2 (db->load<inv_object2> (o2.id_));
#endif
        assert (p2->o1[0] == p1.get ());
        assert (!p2->o1._tracking ());
        t.commit ();
      }
    }

    // Test read-only values.
    {
      ro_object o (1);
      o.v.push_back (ro_value (1, 1));
      o.v.push_back (ro_value (2, 2));
      o.v.push_back (ro_value (3, 3));

      {
        transaction t (db->begin ());
        db->persist (o);
        t.commit ();
      }

      o.v.erase (o.v.begin ());

      {
        transaction t (db->begin ());
        db->update (o);
        t.commit ();
      }

      {
        transaction t (db->begin ());
        assert (db->load<ro_object> (1)->v == o.v);
        t.commit ();
      }
    }
  }
  catch (const odb::exception& e)
  {
    cerr << e.what () << endl;
    return 1;
  }
}