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

#include <akaxiso/classes/member.h>
#include <akaxiso/classes/node.h>

namespace aka2 {

  struct member_cache {
    member_cache() : getter_(0), cache_(0) {}
    member_cache(void *e, const element_op &op, const member_getter &getter) 
      : e_(e), op_(&op), getter_(&getter), cache_(0) {}

    ~member_cache() {
      clear();
    }

    void set(void *e, const element_op &op, const member_getter &getter) {
      assert(cache_ == 0);
      e_ = e;
      op_ = &op;
      getter_ = &getter;
    }

    void clear() {
      if (cache_ != 0) {
	op_->destruct(cache_);
	::operator delete(cache_);
	cache_ = 0;
      }
    }

    void prepare(bool do_copy) {
      if (getter_->get_type() != accessor_type)
	return;

      const accessor_getter *ag = static_cast<const accessor_getter*>(getter_);
      size_t size = op_->class_size();
      cache_ = ::operator new(size);
      if (do_copy)
	ag->get(e_, cache_);
      else
	op_->construct(cache_);
    }

    void flush() {
      if (cache_ == 0)
	return;

      const accessor_getter *ag = static_cast<const accessor_getter*>(getter_);
      ag->set(e_, cache_);
      op_->destruct(cache_);
      ::operator delete(cache_);
      cache_ = 0;
    }

    void *value() const {
      switch (getter_->get_type()) {
      case ptr_type: {
	return static_cast<const ptr_getter*>(getter_)->get_member(e_);
      }
      case accessor_type:
	return cache_;
      case null_type:
	return 0;
      default:
	assert(!"Must not reach here.");
      }
      return 0;
    }

    node get_node() const { 
      return node(value(), *op_);
    } 

  private:
    void operator=(const member_cache&);

    void *e_;
    const element_op *op_;
    const member_getter *getter_;
    void *cache_;
  };


  struct const_member_cache {
    const_member_cache(const void *e, const element_op &op, const member_getter &getter) 
      : e_(e), op_(&op), getter_(&getter), cache_(0) {}

    ~const_member_cache() {
      if (cache_ != 0) {
	op_->destruct(cache_);
	::operator delete(cache_);
	cache_ = 0;
      }
    }

    void set(const void *e, const element_op &op, const member_getter &getter) {
      assert(cache_ != 0);
      e_ = e;
      op_ = &op;
      getter_ = &getter;
    }

    void prepare() {
      if (getter_->get_type() != accessor_type)
	return;

      const accessor_getter *ag = static_cast<const accessor_getter*>(getter_);
      size_t size = op_->class_size();
      cache_ = ::operator new(size);
      ag->get(e_, cache_);
    }

    const void *value() const {
      switch (getter_->get_type()) {
      case ptr_type: {
	return static_cast<const ptr_getter*>(getter_)->get_member(e_);
      }
      case accessor_type:
	return cache_;
      case null_type:
	return 0;
      default:
	assert(!"Must not reach here.");
      }
      return 0;
    }
    const_node get_node() const { 
      return const_node(value(), *op_);
    } 

  private:
    const void *e_;
    const element_op *op_;
    const member_getter *getter_;
    void *cache_;
  };

}

#endif
