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

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

namespace fsl {

  Deck::~Deck() throw(){
    delete this->deltaBase;
    if(this->ownsDeck){
      assert(this->d->allocStamp);
      fsl_deck_finalize(this->d);
    }
  }

  void Deck::setup(fsl_deck * mf, fsl_satype_e type){
    if(!mf){
      assert(!this->d);
      assert(this->ownsDeck);
      this->d = fsl_deck_malloc();
      if(!this->d) throw OOMException();
    }else{
      assert(this->d == mf);
      if(d && d->f && (d->f != this->cx.handle())){
        throw Exception(FSL_RC_MISUSE,
                        "Mis-matched fsl_cx contexts for deck.");
      }
    }
    if(ownsDeck){
      fsl_deck_init( this->cx, d, type );
    }
  }

  Deck::Deck(Context & cx, fsl_satype_e type)
    : cx(cx),
      d(NULL),
      deltaBase(NULL),
      ownsDeck(true){
    this->setup( NULL, type );
  }

  Deck::Deck(Context & cx, fsl_deck * d, bool ownsDeck)
    : cx(cx),
      d(d),
      deltaBase(NULL),
      ownsDeck(ownsDeck){
    if(!d){
      throw Exception(FSL_RC_MISUSE,
                      "A proxied Deck may not be NULL.");
    }
    this->setup( d, d->type );
  }

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

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

  Deck & Deck::cleanup() throw(){
    fsl_deck_clean(*this);
    return *this;
  }

  fsl_satype_e Deck::type() const throw(){
    return this->d->type;
  }

  fsl_id_t Deck::rid() const throw(){
    return this->d->rid;
  }

#if 0
  fsl_uuid_cstr Deck::uuid() const throw(){
    return this->d->uuid;
  }
#endif

  Deck::operator fsl_deck *() throw(){
    return this->d;
  }

  Deck::operator fsl_deck const *() const throw(){
    return this->d;
  }

  Context & Deck::context() throw() { return this->cx; }
  Context const & Deck::context() const throw() { return this->cx; }

  fsl_deck * Deck::handle() throw(){
    return this->d;
  }

  fsl_deck const * Deck::handle() const throw(){
    return this->d;
  }

  bool Deck::hasAllRequiredCards() const throw(){
    return fsl_deck_has_required_cards(*this);
  }

  Deck const & Deck::assertHasRequiredCards() const{
    if(!fsl_deck_has_required_cards(*this)){
      throw Exception(fsl_cx_err_get_e(this->cx));
    }
    else return *this;
  }

  bool Deck::cardIsLegal(char cardLetter) const throw(){
    return fsl_card_is_legal( this->d->type, cardLetter );
  }

  Deck & Deck::unshuffle(bool calcRCard){
    int const rc = fsl_deck_unshuffle(*this, calcRCard);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }else return *this;
  }

  Deck const & Deck::output( fsl_output_f f, void * outState ) const{
    int const rc = fsl_deck_output( this->d, f, outState );
    if(rc) throw Exception(this->d->f->error);
    else return *this;
  }

  Deck const & Deck::output( std::ostream & os ) const{
    return this->output( fsl_output_f_std_ostream, &os );
  }

  Deck & Deck::save(bool isPrivate){
    int const rc = fsl_deck_save( *this, isPrivate );
    if(rc){
      this->propagateError();
      throw Exception(rc,"fsl_deck_save() failed: %s",
                      fsl_rc_cstr(rc));
    }
    else return *this;
  }

  Deck & Deck::load( fsl_id_t rid, fsl_satype_e type ){
    this->cleanup();
    int const rc = fsl_deck_load_rid( this->cx, *this,
                                      rid, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_rid() failed "
                      "with %s for symbol: %" FSL_ID_T_PFMT,
                      fsl_rc_cstr(rc), (fsl_id_t)rid);
    }else return *this;
  }

  Deck & Deck::load( char const * symbolicName, fsl_satype_e type){
    this->cleanup();
    int const rc = fsl_deck_load_sym( this->cx, *this,
                                      symbolicName, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_sym() failed "
                      "with %s for symbol: %s",
                      fsl_rc_cstr(rc), symbolicName);
    }else return *this;
  }

  Deck & Deck::load( std::string const & symbolicName, fsl_satype_e type){
    return this->load( symbolicName.c_str(), type );
  }

  Deck & Deck::setCardA( char const * name,
                         char const * tgt,
                         fsl_uuid_cstr uuid ){
    int const rc = fsl_deck_A_set(*this, name, tgt, uuid);
    this->assertRC("fsl_deck_A_set()", rc);
    return *this;
  }

  Deck & Deck::setCardB(fsl_uuid_cstr uuid){
    if(this->deltaBase){
      delete this->deltaBase;
      this->deltaBase = NULL;
    }
    int const rc = fsl_deck_B_set(*this, uuid);
    this->assertRC("fsl_deck_B_add()", rc);
    return *this;
  }


  Deck & Deck::setCardC( char const * comment ){
    int const rc = fsl_deck_C_set(*this, comment, -1);
    this->assertRC("fsl_deck_C_set()", rc);
    return *this;
  }

  Deck & Deck::setCardD(double julianDay){
    int const rc = fsl_deck_D_set(*this,
                                  julianDay<0
                                  ? fsl_julian_now()
                                  : julianDay);
    this->assertRC("fsl_deck_D_set()", rc);
    return *this;
  }

  Deck & Deck::setCardE( fsl_uuid_cstr uuid, double julian ){
    int const rc = fsl_deck_E_set(*this,
                                  julian<0
                                  ? fsl_julian_now()
                                  : julian,
                                  uuid );
    this->assertRC("fsl_deck_E_set()", rc);
    return *this;
  }


  Deck & Deck::addCardF(char const * name,
                        fsl_uuid_cstr uuid,
                        fsl_fileperm_e perm, 
                        char const * oldName ){
    int const rc = fsl_deck_F_add(*this, name, uuid, perm, oldName);
    this->assertRC("fsl_deck_F_add()", rc);
    return *this;
  }

  Deck & Deck::addCardJ( char isAppend, char const * key, char const * value ){
    int const rc = fsl_deck_J_add(*this, isAppend, key, value);
    this->assertRC("fsl_deck_J_add()", rc);
    return *this;
  }


  Deck & Deck::setCardK(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_K_set(*this, uuid);
    this->assertRC("fsl_deck_K_set()", rc);
    return *this;
  }

  Deck & Deck::setCardL( char const * title ){
    int const rc = fsl_deck_L_set(*this, title, -1);
    this->assertRC("fsl_deck_L_set()", rc);
    return *this;
  }

  Deck & Deck::addCardM(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_M_add(*this, uuid);
    this->assertRC("fsl_deck_M_add()", rc);
    return *this;
  }


  Deck & Deck::setCardN(char const * name){
    int const rc = fsl_deck_N_set(*this, name, -1);
    this->assertRC("fsl_deck_N_set()", rc);
    return *this;
  }

  Deck & Deck::addCardP(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_P_add(*this, uuid);
    this->assertRC("fsl_deck_P_add()", rc);
    return *this;
  }

  Deck & Deck::addCardQ(char type, fsl_uuid_cstr target,
                        fsl_uuid_cstr baseline){
    int const rc = fsl_deck_Q_add(*this, type, target, baseline);
    this->assertRC("fsl_deck_Q_add()", rc);
    return *this;
  }

  Deck & Deck::addCardT(fsl_tagtype_e tagType,
                        char const * name,
                        fsl_uuid_cstr uuid,
                        char const * value){
    int const rc = fsl_deck_T_add( *this, tagType, uuid, name, value );
    this->assertRC("fsl_deck_T_add()", rc);
    return *this;
  }


  Deck & Deck::setCardU(char const * user){
    if(!user || !*user){
      user = fsl_cx_user_get(this->cx);
    }
    if(!user || !*user){
      throw Exception(FSL_RC_MISUSE,
                      "setCardU(): NULL/empty user name is not legal.");
    }
    int const rc = fsl_deck_U_set(*this, user);
    this->assertRC("fsl_deck_U_set()", rc);
    return *this;
  }

  Deck & Deck::setCardW(char const * content, fsl_int_t len){
    int const rc = fsl_deck_W_set(*this, content, len);
    this->assertRC("fsl_deck_W_set()", rc);
    return *this;
  }

  Deck::FCardIterator::~FCardIterator() throw()
  {}

  Deck::FCardIterator::FCardIterator(Deck & d, bool skipDeletedFiles)
    : d(&d),
      fc(NULL),
      skipDeleted(skipDeletedFiles)
  {
    int const rc = fsl_deck_F_rewind(d);
    if(rc) throw Exception(rc, "fsl_deck_F_rewind() failed: %s",
                           fsl_rc_cstr(rc));
    fsl_deck_F_next(d, &this->fc);
  }

  Deck::FCardIterator::FCardIterator() throw()
    : d(NULL),
      fc(NULL),
      skipDeleted(false)
  {}

  void Deck::FCardIterator::assertHasDeck(){
    if(!this->d) throw Exception(FSL_RC_MISUSE,
                                 "Iterator requires a deck object.");
  }

  Deck::FCardIterator & Deck::FCardIterator::operator++(){
    if(this->fc){
      do{
        int const rc = fsl_deck_F_next(*this->d, &this->fc);
        if(rc) throw Exception(rc);
      }while(skipDeleted && (this->fc && !this->fc->uuid));
    }
    return *this;
  }

  fsl_card_F const * Deck::FCardIterator::operator*(){
    return this->fc;
  }

  fsl_card_F const * Deck::FCardIterator::operator->(){
    if(!this->fc) throw Exception(FSL_RC_MISUSE,
                                  "Throwing to avoid "
                                  "dereferencing a NULL fsl_card_F.");
    return this->fc;
  }


  bool Deck::FCardIterator::operator==(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return true;
    else if(!this->fc || !rhs.fc) return false;
    return 0==fsl_strcmp(this->fc->name, rhs.fc->name);
  }
    
  bool Deck::FCardIterator::operator!=(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return false;
    else if(!this->fc || !rhs.fc) return true;
    return 0!=fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  bool Deck::FCardIterator::operator<(FCardIterator const &rhs) const throw(){
    if(!this->fc) return rhs.fc ? true : false;
    else if(!rhs.fc) return false;
    return 0 > fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  Deck * Deck::baseline(){
    if(FSL_SATYPE_CHECKIN==this->d->type){
      if(this->deltaBase) return this->deltaBase;
      else if(!this->d->B.uuid) return NULL;
      else if(!this->d->B.baseline){
        int const rc = fsl_deck_F_rewind(*this);
        this->assertRC("fsl_deck_rewind()", rc);
        assert(this->d->B.baseline);
      }
      return this->deltaBase = new Deck(this->cx,
                                        this->d->B.baseline,
                                        false);
    }else{
      return NULL;
    }
  }

  Deck::TCardIterator::TCardIterator(Deck & d)
  : ParentType(d.handle()->T)
  {}

  Deck::TCardIterator::TCardIterator()
  : ParentType()
  {}

  Deck::TCardIterator::~TCardIterator() throw()
  {}

  fsl_card_T const * Deck::TCardIterator::operator->() const{
    fsl_card_T const * rv = this->currentValue();
    if( !rv ) throw Exception(FSL_RC_MISUSE,
                              "Throwing to avoid dereferencing a NULL "
                              "fsl_card_T.");
    else return rv;
  }
  std::ostream & operator<<( std::ostream & os, Deck const & d ){
    d.output(os);
    return os;
  }

} // namespace fsl

#undef CERR
