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

/** @file akaxiso/classes/choice.h */

#include <typeinfo> /* {g++2.96} */

#include <akaxiso/classes/qname.h>
#include <akaxiso/classes/item.h>

#include <vector>

namespace aka2 {


  template<class C>
  class typed_item_iterator : public item_iterator {
  public:
    typed_item_iterator(const C& container) :
      container_(container), current_(container.begin()) {}
    virtual ~typed_item_iterator(){}

    const C &container_;
    TYPENAME C::const_iterator current_;

    virtual bool has_next() const { return current_ != container_.end(); }
    virtual const item *next() {
      const item &v = *current_;
      ++current_;
      return &v;
    }
  };


  template<class L>
  class choice_op_dispatcher : public choice_op {
  public:
    virtual schematype_id get_schematype() const { return choice_id; }
    virtual const qname& get_typename() const { return L::typename_; }
    virtual const attribute_types* get_attribute_types() const { return 0; }
    virtual const any_member *get_anyattr_type() const { return 0; }
    virtual void construct(void *e) const {  L::construct(e); }
    virtual void copy_construct(void *e, const void *src) const { L::copy_construct(e, src); }
    virtual void destruct(void *e) const { L::destruct(e); }
    virtual size_t class_size() const { return L::class_size(); }

    virtual bool equals(const void *lhs, const void *rhs) const {
      return L::equals(lhs, rhs);
    }

    virtual size_t size(const void *container) const { return L::size(container); }

    /** attribute_info getter. */
    virtual const itemtypes &get_itemtypes() const { return L::itemtypes_; }

    virtual void push(void *container, item &i) const {
      L::push_function(container, i);
    }
    virtual item_iterator* get_iterator(const void *e) const {
      return L::get_iterator(e);
    }
  };


  template<class L, class T>
  struct choice_statics {
    static itemtypes itemtypes_;
    static qname typename_;
    static choice_op_dispatcher<L> dispatcher_;
  };

  template<class L, class T>
  itemtypes choice_statics<L, T>::itemtypes_;

  template<class L, class T>
  qname choice_statics<L, T>::typename_;

  template<class L, class T>
  choice_op_dispatcher<L> choice_statics<L, T>::dispatcher_;


  template<class T, class L>
  class choice : public choice_statics<L, T> {
  public:
    typedef T value_type;
    virtual ~choice(){}

    static void initialize() {
      if (!system_type_registry().add(L()))
	return;
      itemtypes_.clear();
      L l; l.model();
    }
    static void uninitialize() {
      itemtypes_.clear();
    }

    static typed_item_iterator<T>* get_iterator(const void *container) {
      return new typed_item_iterator<T>(*static_cast<const T*>(container));
    }


    static void construct(void *e) {
      new (e) T();
    }
    static void copy_construct(void *e, const void *src) {
      new (e) T(*static_cast<const T*>(src));
    }
    static size_t class_size() { return sizeof(T); }
    static void destruct(void *elm) { static_cast<T*>(elm)->~T(); }

    static bool equals(const void *lhs, const void *rhs) {
      return choice_equals(lhs, rhs, dispatcher_);
    }

    static size_t size(const void *container) { return static_cast<const T*>(container)->size(); }

    static void push_function(void *container, aka2::item &i) {
      L().methods.push(*static_cast<T*>(container), i);
    }

    template<class IL>
    void item(const std::string &tagname, const IL&, int minOccurs = 1, int maxOccurs = 1) {
      IL::initialize();
      element_op &op = IL::dispatcher_;
      itemtype itype(op);
      default_op *defop = IL::create_default_op();
      if (defop != 0)
	itype.set_default_op(defop);
      itype.set_name(tagname);

      schematype_id id = itype.get_schematype();
      if ((id == array_id) || (id == choice_id))
	itype.set_occurrence(aka2::occurrence(minOccurs, maxOccurs));
      else {
	if ((minOccurs != 1) && (maxOccurs != 1))
	  throw internal_error();
      }
      std::pair<itemtypes::iterator, bool> res = 
	L::itemtypes_.insert(itemtypes::value_type(qname(tagname), itype));
      if (!res.second)
	throw internal_error();
    }

    template<class IL>
    void fixed_item(const std::string &tagname, const std::string &fixed_value, const IL&) {
      if (IL::dispatcher_.get_schematype() != simpletype_id)
	throw internal_error();
      IL::initialize();
      element_op &op = aka2::fixed<IL>::dispatcher_;
      itemtype itype(op); 
      itype.set_default_op(IL::create_default_op());
      itype.setup_default_value(fixed_value);
      itype.set_name(qname(tagname));
      std::pair<itemtypes::iterator, bool> res = 
	L::itemtypes_.insert(itemtypes::value_type(qname(tagname), itype));
      if (!res.second)
	throw internal_error();
    }

    template<class IL>
    void fixed_array(const std::string &tagname, const std::string &fixed_value, const IL&, 
		     const occurrence &occ) {
      if (IL::dispatcher_.get_schematype() != array_id)
	throw internal_error();
      
      IL::initialize();      
      element_op &op = IL::dispatcher_;
      itemtype itype(op);

      typedef TYPENAME IL::value_leaf_type value_leaf_type;
      default_op *defop = value_leaf_type::create_default_op();
      assert(defop != 0);
      itype.set_default_op(defop);
      itype.setup_default_value(fixed_value);
      itype.set_name(tagname);
      itype.set_occurrence(occ);

      std::pair<itemtypes::iterator, bool> res = 
	L::itemtypes_.insert(itemtypes::value_type(qname(tagname), itype));
      if (!res.second)
	throw internal_error();
    }

    void any() {
      aka2::itemtype itype(aka2::any_op::dispatcher_);
      itype.set_name("&any");// Must not be named element.
      std::pair<itemtypes::iterator, bool> res = 
	L::itemtypes_.insert(itemtypes::value_type(qname("&any"), itype));
      if (!res.second)
	throw internal_error();
    }

    void any(int minOccurs, int maxOccurs) {
      aka2::itemtype itype(aka2::any_array_op::dispatcher_);
      itype.set_name(qname("&any"));
      itype.set_occurrence(minOccurs, maxOccurs);
      std::pair<itemtypes::iterator, bool> res = 
	L::itemtypes_.insert(itemtypes::value_type(qname("&any"), itype));
      if (!res.second)
	throw internal_error();
    }

    static default_op* create_default_op() { return 0; }

    static const itemtypes::value_type* find_itemtype(const qname &name) {
      itemtypes::const_iterator it = itemtypes_.find(name);
      if (it == itemtypes_.end())
      	return 0;
      return &*it;
    }

    static const itemtypes::value_type* find_itemtype(const element_op &eo) {
      for (itemtypes::const_iterator it = itemtypes_.begin(); it != itemtypes_.end(); ++it)
    	if (it->second == &eo)
	      return &*it;
      return 0;
    }
  };


  /** !!!!!!! Reconsider MOC. */

  template<class C, class L>
  struct sequential_choice_moc {
    typedef TYPENAME C::value_type value_type;
    sequential_choice_moc(C &container) : c_(container) {}
    TYPENAME C::iterator begin() { return c_.begin(); }
    TYPENAME C::iterator end() { return c_.end(); }
    TYPENAME C::const_iterator begin() const { return c_.begin(); }
    TYPENAME C::const_iterator end() const { return c_.end(); }
    C& c_;

    template<class I>
    void push_back(const I &v, const std::string &tagname) {
      const itemtypes::value_type *vt = L::find_itemtype(tagname);
      if (vt == 0)
	throw internal_error();

      const itemtype &itype = vt->second;
      if (itype.get_schematype() == fixed_id)
	throw internal_error();

      c_.push_back(item(itype.op().replicate(&v), itype));
    }

    void push_back_fixed_item(const std::string &tagname) {
      const itemtypes::value_type *vt = L::find_itemtype(tagname);
      if (vt == 0)
	throw internal_error();
      const itemtype &itype = vt->second;
      if (itype.get_schematype() != fixed_id)
	throw internal_error();
      void *i = itype.op().create();
      c_.push_back(item(i, itype));
    }


    template<class R>
    int find_elements(R &r, const std::string &tagname) const {
      int count = 0;

      const itemtypes::value_type *itype = L::find_itemtype(tagname);
      if (itype == 0)
        throw internal_error();

      for (TYPENAME C::const_iterator it = c_.begin(); it != c_.end(); ++it) {
	if (it->get_name() == qname(tagname)) {
          r.push_back(item_cast<TYPENAME R::value_type>(*it));
	  ++count;
	}
      }
      return count;
    }

  };


  template<class T, class L=xiso::leaf<T> >
  class sequential_choice : public choice<T, L> {
  public:
    virtual ~sequential_choice(){}
    typedef  sequential_choice_moc<T, L> moc;
    typedef  sequential_choice_moc<const T, L> const_moc;
    sequential<T> methods;
  };


  template<class C, class L>
  struct associative_choice_moc {
    typedef TYPENAME C::value_type value_type;
    associative_choice_moc(C &container) : c_(container) {}
    TYPENAME C::iterator begin() { return c_.begin(); }
    TYPENAME C::iterator end() { return c_.end(); }
    TYPENAME C::const_iterator begin() const { return c_.begin(); }
    TYPENAME C::const_iterator end() const { return c_.end(); }
    C& c_;

    template<class I, class IL>
    void insert(const I &v) { 
      const itemtypes::value_type *vt = L::find_itemtype(*op);
      if (vt == 0)
	throw internal_error();
      c_.insert(item(vt->first, vt->second.op().replicate(&v), vt->second.op())); 
    }

    template<class R>
    int find_elements(R &r, const std::string &tagname) const {

      const itemtypes::value_type *itype = L::find_itemtype(tagname);
      if (itype == 0)
        throw internal_error();

      int count = 0;
      for (TYPENAME C::const_iterator it = c_.begin(); it != c_.end(); ++it) {
      	if (it->get_name() == qname(tagname)) {
          r.push_back(item_cast<TYPENAME R::value_type>(*it));
	        ++count;
    	  }
      }
      return count;
    }

  };


  template<class T, class L=xiso::leaf<T> >
  class associative_choice : public choice<T, L> {
  public:
    virtual ~associative_choice(){}

    typedef  associative_choice_moc<T, L> moc;
    typedef  associative_choice_moc<const T, L> const_moc;
    associative<T> methods;
  };

} // namespace aka2

#endif /* AKAXISO_CHOICE_H__ */
