/*
 * Copyright (C) 2010 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */

/*****
 * This file is part of the Wt::Dbo tutorial:
 * http://www.webtoolkit.eu/wt/doc/tutorial/dbo/tutorial.html
 *****/

/*****
 * Dbo tutorial section 7.4
 *  Specifying a composite natural primary key (with foreign keys)
 *****/

#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/backend/Sqlite3>

namespace dbo = Wt::Dbo;

class Membership;
class Organisation;
class Person;

struct MembershipId {
  dbo::ptr<Person>       person;
  dbo::ptr<Organisation> organisation;

  MembershipId() { }

  MembershipId(dbo::ptr<Person> p, dbo::ptr<Organisation> o)
    : person(p),
      organisation(o) 
  { }

  bool operator== (const MembershipId& other) const {
    return person == other.person && organisation == other.organisation;
  }

  bool operator< (const MembershipId& other) const {
    if (person < other.person)
      return true;
    else if (person == other.person)
      return organisation < other.organisation;
    else
      return false;
  }
};

std::ostream& operator<< (std::ostream& o, const MembershipId& mid)
{
  return o << "(" << mid.person << ", " << mid.organisation << ")";
}

namespace Wt {
  namespace Dbo {

    template <class Action>
    void field(Action& action, MembershipId& mid, const std::string& name,
	       int size = -1)
    {
      /*
       * Note: here we ignore name because MembershipId is used only
       * as primary key of membership, and the name needs to match the
       * names of the reciproce collection in Person and Organisation.
       */
      belongsTo(action, mid.person, "person");
      belongsTo(action, mid.organisation, "organisation");
    }
  }
}

namespace Wt {
  namespace Dbo {

    template<>
    struct dbo_traits<Membership> : public dbo_default_traits
    {
      typedef MembershipId IdType;
      static IdType invalidId() { return MembershipId(); }
      static const char *surrogateIdField() { return 0; }
    };
  }
}

class Membership {
public:
  MembershipId id;
  int karma;

  template<class Action>
  void persist(Action& a)
  {
    dbo::id(a, id, "id");
    dbo::field(a, karma, "karma");
  }
};

class Person {
public:
  std::string name;

  dbo::collection< dbo::ptr<Membership> > memberships;

  template<class Action>
  void persist(Action& a)
  {
    dbo::field(a, name, "name");
    dbo::hasMany(a, memberships, dbo::ManyToOne, "person");
  }
};

class Organisation {
public:
  std::string name;

  dbo::collection< dbo::ptr<Membership> > memberships;

  template<class Action>
  void persist(Action& a)
  {
    dbo::field(a, name, "name");
    dbo::hasMany(a, memberships, dbo::ManyToOne, "organisation");
  }
};

void run()
{
  /*
   * Setup a session, would typically be done once at application startup.
   */
  dbo::backend::Sqlite3 sqlite3(":memory:");
  sqlite3.setProperty("show-queries", "true");
  dbo::Session session;
  session.setConnection(sqlite3);

  session.mapClass<Membership>("membership");
  session.mapClass<Person>("person");
  session.mapClass<Organisation>("organisation");

  /*
   * Try to create the schema (will fail if already exists).
   */
  session.createTables();

  {
    dbo::Transaction transaction(session);

    Person *p = new Person();
    p->name = "Joe";
    dbo::ptr<Person> joe = session.add(p);

    Organisation *o = new Organisation();
    o->name = "Police";
    dbo::ptr<Organisation> police = session.add(o);

    Membership *ms = new Membership();
    ms->id.person = joe;
    ms->id.organisation = police;
    ms->karma = 42;

    session.add(ms);

    std::cerr << "Joe is member of " << joe->memberships.size()
	      << " organisation(s):" << std::endl;
    for (dbo::collection< dbo::ptr<Membership> >::const_iterator i = 
	   joe->memberships.begin(); i != joe->memberships.end(); ++i) {
      const Membership& ms = **i;
      std::cerr << " " << ms.id.organisation->name
		<< " (karma: " << ms.karma << ")" << std::endl;
    }
  }
}

int main(int argc, char **argv)
{
  run();
}
