/* -*- c++ -*- */
#ifndef AKAXISO_ELEMENT_PTR_H__
#define AKAXISO_ELEMENT_PTR_H__

#include <cassert>

namespace aka2 {

  struct refstate {
    refstate(int count) : count_(count), owner_(true) {}
    int count_;
    bool owner_;
  };

  class element_holder {
    friend class element_ptr;
    friend class const_element_ptr;
    element_holder() : v_(0), op_(0), refstate_(0){}

    void attach(void *const rhs, const element_op &op) {
      assert(refstate_ == 0);
      assert((op_ == 0) || (op_ == &op));
      refstate_ = new refstate(1);
      v_ = rhs;
      op_ = &op;
    }

    void attach(const element_holder &rhs) {
      assert(refstate_ == 0);
      assert((op_ == 0) || (op_ == rhs.op_));

      if (!rhs.refstate_->owner_)
	return;
      v_ = rhs.v_;
      op_ = rhs.op_;
      refstate_ = rhs.refstate_;
      ++(refstate_->count_);
    }

    void detach() {
      if (refstate_ == 0)
	return;
      if (--(refstate_->count_) == 0) {
	if (refstate_->owner_)
	  op_->destroy(v_);
	delete refstate_;
      }
      refstate_ = 0;
      v_ = 0;
    }
    void *adopt() {
      assert(refstate_ != 0);
      refstate_->owner_ = false;
      void *ret = v_;
      v_ = 0;
      return ret;
    }
    void *get_v() const {
      if ((refstate_ == 0) || (!refstate_->owner_))
	return 0;
      return v_;
    }

    void* v_;
    const element_op *op_;
    refstate *refstate_;
  };


  class element_ptr {
    friend class const_element_ptr;
  public:
    element_ptr(){}
    element_ptr(void* v, const element_op& op) {
      holder_.attach(v, op);
    }
    element_ptr(const element_ptr &ptr) {
      holder_.attach(ptr.holder_);
    }
    ~element_ptr() {
      holder_.detach();
    }

    element_ptr& operator=(const element_ptr& rhs) {
      if ((this != &rhs) && (rhs.holder_.v_ != holder_.v_)) {
	holder_.detach();
	holder_.attach(rhs.holder_);
      }
      return *this;
    }

    void *adopt() { return holder_.adopt(); }
    const element_op& get_op() const { return *holder_.op_; }
    void *get_ptr() { return holder_.get_v(); }
    const void *get_ptr() const { return holder_.get_v(); }

  private:
    element_holder holder_;
  };

} // namespace aka2

#endif /* AKAXISO_ELEMENT_PTR_H__ */
