/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Context::~Context(){
    if(this->ownsCx){
      fsl_cx_finalize(this->f);
    }
  }

  void Context::setup(fsl_cx_init_opt const * opt){
    //this->f = fsl_cx_malloc();
    //if(!this->f) throw OOMException();
    assert(!this->f);
    int const rc = fsl_cx_init(&this->f, opt);
    if(rc){
      fsl_error err = fsl_error_empty;
      if(this->f){
        fsl_error_move( &this->f->error, &err );
        fsl_cx_finalize(this->f);
        this->f = NULL;
      }else{
        fsl_error_set( &err, rc,
                       "fsl_cx_init() failed with code %s",
                       fsl_rc_cstr(rc) );
      }
      throw Exception(err);
    }
  }

  Context::Context()
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(NULL);
  }
  
  Context::Context(fsl_cx_init_opt const & opt)
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(&opt);
  }

  Context::Context(fsl_cx * f, bool ownsHandle)
    : f(f),
      ownsCx(ownsHandle),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    
  }

  Context::operator fsl_cx * () throw(){
    return this->f;
  }

  Context::operator fsl_cx const * () const throw() {
    return this->f;
  }

  void Context::propagateError() const{
    if(this->f){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      if(err->code) throw Exception(err);
    }
  }

  void Context::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Context::assertHasRepo(){
    assert(this->f);
    if(!fsl_needs_repo(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  void Context::assertHasCheckout(){
    assert(this->f);
    if(!fsl_needs_checkout(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  fsl_cx * Context::handle() throw(){
    return this->f;
  }

  fsl_cx const * Context::handle() const throw(){
    return this->f;
  }

  Db & Context::db() throw() {
    if(!this->dbMain.handle()){
      fsl_db * db = fsl_cx_db(*this);
      if(db) this->dbMain.handle(db, false);
    }
    return this->dbMain;
  }


  Db & Context::dbRepo() throw() {
    if(!this->dbRe.handle()){
      fsl_db * db = fsl_cx_db_repo(*this);
      if(db) this->dbRe.handle(db, false);
    }
    return this->dbRe;
  }

  Db & Context::dbCheckout() throw() {
    if(!this->dbCkout.handle()){
      fsl_db * db = fsl_cx_db_checkout(*this);
      if(db) this->dbCkout.handle(db, false);
    }
    return this->dbCkout;
  }

  Context & Context::openCheckout( char const * dirName ){
    this->assertRC( "openCheckout()",
                    fsl_checkout_open_dir(this->f, dirName, -1, 1) );
    return *this;
  }

  Context & Context::openRepo( char const * dbName ){
    this->assertRC( "openRepo()",
                    fsl_repo_open(this->f, dbName) );
    return *this;
  }

  Context & Context::closeDbs() throw(){
    fsl_cx_close_dbs(this->f)
      /*
        Reminder to self: the 3 fsl_cx db handles are stored as
        complete fsl_db instances (not pointers) in fsl_cx, with the
        exception of the "main" db, which is just a pointer to one of
        the other 3. What does that mean? It means that when we use
        fsl_cx_close_dbs(), this->dbRe and friends will (if
        initialized) still be pointing to those pointers...  which are
        (due to internal details) actually still valid, they just
        refer to closed fsl_db handles.

        That's actually good for us here, except that certain
        combinations of C-level ops "might" get our checkout/repo db
        pointers cross a bit.
      */
      ;
    assert(!this->dbRe.ownsHandle());
    assert(!this->dbMain.ownsHandle());
    assert(!this->dbCkout.ownsHandle());
    this->dbRe.close();
    this->dbCkout.close();
    this->dbMain.close();
    if(this->dbRe.handle()){
      assert(!this->dbRe.handle()->dbh);
    }
    return *this;
  }

  bool Context::ownsHandle() const throw(){
    return this->ownsCx;
  }

  std::string Context::ridToArtifactUuid(fsl_id_t rid,
                                         fsl_catype_t type){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_artifact_uuid(*this, rid, type);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND,
                      "Could not resolve RID %"FSL_ID_T_PFMT
                      " as artifact type %s.",
                      (fsl_id_t)rid, fsl_catype_cstr(type));
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::ridToUuid(fsl_id_t rid){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_uuid(*this, rid);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND, "Could not resolve RID %"FSL_ID_T_PFMT".",
                      (fsl_id_t)rid);
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::symToUuid(char const * symbolicName,
                                 fsl_id_t * rid,
                                 fsl_catype_t type){
    this->assertHasRepo();
    fsl_uuid_str uuid = NULL;
    int const rc = fsl_sym_to_uuid(*this, symbolicName, type, &uuid, rid);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    std::string const & rv = uuid;
    fsl_free(uuid);
    return rv;
  }


  fsl_id_t Context::symToRid(char const * symbolicName, fsl_catype_t type){
    this->assertHasRepo();
    fsl_id_t rv = 0;
    int const rc = fsl_sym_to_rid(*this, symbolicName, type, &rv);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    assert(rv>0);
    return rv;
  }

  fsl_id_t Context::symToRid(std::string const & symbolicName,
                             fsl_catype_t type){
    return this->symToRid( symbolicName.c_str(), type );
  }

  Context::Transaction::Transaction(Context &cx)
    : tr( cx.db() ),
      level(tr.level()){
  }

  Context::Transaction::~Transaction() throw(){
    if(this->level) this->tr.rollback();
  }

  void Context::Transaction::commit(){
    if(this->level){
      this->level = 0;
      this->tr.commit();
    }else{
      throw Exception(FSL_RC_MISUSE,
                      "commit() called multiple times.");
    }
  }

  Context & Context::getContent( fsl_id_t rid, Buffer & dest ){
    int const rc = fsl_content_get( *this, rid, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( char const * sym, Buffer & dest ){
    int const rc = fsl_content_get_sym( *this, sym, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( std::string const & sym, Buffer & dest ){
    return this->getContent( sym.c_str(), dest );
  }

} // namespace fsl

#undef CERR
