#include "choice_handler.h"

#include "sequence_handler.h"
#include "all_handler.h"
#include "simplecontent_handler.h"
#include "simpletype_handler.h"
#include "array_handler.h"
#include "any_handler.h"
#include "../classes/item.h"
#include "../classes/construct.h"

#include <cassert>

using namespace aka2;
  
choice_handler::choice_handler(const qname &tagname, void *cho, int depth,
			       const choice_op &cop, const occurrence &occ,
			       parser_context &context) 
  : handler(context, depth, tagname), 
    cho_(cho), e_(0), cop_(cop), occurrence_(occ), count_(0),
    itypes_(cop_.get_itemtypes()) {
  current_ = itypes_.end();
  searching_ = itypes_.end();
}

choice_handler::~choice_handler() {
  if (e_ != 0)
    abort();
}


parse_result choice_handler::query_element(const qname &tagname, const attribute_values &attrs) {

  assert(current_ == itypes_.end());

  if (occurrence_.maxOccurs_ == count_)
    return skip;

  itemtypes::const_iterator it = itypes_.find(tagname);
  if (it != itypes_.end()) {
    current_ = it;
    return parse_element(attrs);
  }

  parse_result res = find_particle(tagname, attrs, current_);
  if (res == ok)
    return ok;

  // <xs:any>
  if (!context_.ignore_any()) {

    it = itypes_.find(qname("&any"));
    if (it != itypes_.end()) {
      const itemtype &itype = it->second;
      schematype_id id = itype.get_schematype();

      if (id == any_array_id) {
	e_ = itype.op().create();
	handler *h = create_any_array_handler(tagname, *static_cast<any_array*>(e_),
					      depth_, itype);
	parse_result res = h->query_element(tagname, attrs);
	if (res == ok)
	  current_ = it;
	return res;
      }

      assert(id == any_id);
      bool next_found = seek_forward(tagname, attrs);
      if (!next_found) {
	current_ = it;
	e_ = itype.op().create();
	create_any_handler(tagname, *static_cast<any*>(e_), depth_ + 1);
	return ok;
      }
    }
  }

  if (occurrence_.in_range(count_))
    return skip;

  //context_.report_no_element(tagname, __FILE__, __LINE__);
  return error;
}


parse_result choice_handler::query_next(const qname &tagname, const attribute_values &attrs) {
  
  itemtypes::const_iterator it = itypes_.find(tagname);
  if (current_ == it)
    return skip;
  if (it != itypes_.end())
    return ok;

  //it = itypes_.end()
  parse_result res = find_particle(tagname, attrs, it);
  
  if (res == ok)
    return ok;

  return can_skip() ? skip : error;
}


parse_result choice_handler::parse_element(const attribute_values &attrs) {

  const element_op &iop = current_->second.op();
  const qname &tagname = current_->first;
  const itemtype &itype = current_->second;

  e_ = iop.create();

  switch (iop.get_schematype()) {
  case sequence_id:
  case choice_id:
  case all_id: {
    create_particle_handler(tagname, e_, iop, depth_ + 1, itype);
    break;
  }
  case simplecontent_id:
  case simpletype_id:
  case fixed_id: {
    create_simple_handler(tagname, e_, iop, depth_ + 1, itype);
    break;
  }
  case array_id: {
    handler *h = create_array_handler(tagname, e_, 
				      static_cast<const array_op&>(iop), 
				      depth_, itype);
    parse_result res = h->query_element(tagname, attrs);
    assert(res == ok);
    break;
  } 
  case any_id: {
    create_any_handler(tagname, *static_cast<any*>(e_), depth_ + 1);
    break;
  }
  case any_array_id: {
    handler *h = create_any_array_handler(tagname, *static_cast<any_array*>(e_),
					  depth_, itype);
    parse_result res = h->query_element(tagname, attrs);
    assert(res == ok);
    break;
  }
  case enclose_id: // enclose/disclose are for <xs:sequence>.
  case disclose_id:
  case ptrmember_id: // ptrmember should not be under choice.
  case any_attribute_id: // attribute is not handled here.
    assert(!"Must not reach here.");
  }
  return ok;
}


parse_result choice_handler::find_particle(const qname &tagname, 
					   const attribute_values &attrs,
					   itemtypes::const_iterator &found) {

  if (searching_ != itypes_.end()) {
    return error; //currently searching particle.  It could be called by find_next().
                  // To avoid searchnig xs:any's in items, return error to stop search.
  }

  handler *current = 0;

  for (itemtypes::const_iterator it = itypes_.begin(); it != itypes_.end(); ++it) {

    const itemtype &itype = it->second;
    if (itype.is_element())
      continue;

    searching_ = it;

    schematype_id id = itype.get_schematype();
    switch (id) {
    case sequence_id:
    case choice_id: 
    case all_id: {
      e_ = itype.op().create();
      current = create_particle_handler(tagname, e_, itype.op(), 
					depth_, itype);
      break;
    }
    case array_id: {
      e_ = itype.op().create();
      current = create_array_handler(tagname, e_, 
				     static_cast<const array_op&>(itype.op()), 
				     depth_, itype);
      break;
    }
    case any_id:
    case any_array_id: 
      continue;
    case ptrmember_id:
    case simpletype_id:    // simpletype is not a particle.
    case simplecontent_id: // simplecontent is not a particle.
    case enclose_id:       // enclose is not a particle.
    case disclose_id:      // disclose is not a particle.
    case fixed_id:         // fixed is always element.(XML-Schema spec. restriction of akaxiso.)
    case any_attribute_id: // attribute is not handled here.
      assert("!Must not reach here.");
    }

    parse_result res = current->query_element(tagname, attrs);

    if (res == ok) {
      found = it;
      searching_ = itypes_.end();
      return ok;  // Element given by tagname found.
    }

    // Rollup.
    while (context_.top() != current)
      context_.pop();

    // Remove current.
    assert(context_.top() == current);
    context_.pop();
    itype.op().destroy(e_);
    e_ = 0;

    assert((res == skip) || (res == error));
  }
  searching_ = itypes_.end();
  return skip;
}


parse_result choice_handler::end_element(const qname &tagname) {
  if (occurrence_.in_range(count_))
    return ok;
  context_.report_wrong_occurrence(occurrence_, count_, __FILE__, __LINE__);
  return error;
}


bool choice_handler::parse_entity(const std::string &entity) {
  return true;
}

node choice_handler::get_node() {
  return node(cho_, cop_);
}

void choice_handler::receive_child(const node &child) {
  assert(&child.op() == &current_->second.op());
  item i(child.ptr(), current_->second);
  cop_.push(cho_, i);
  ++count_; // occurrence increased.
  e_ = 0; // Item is passed to choice object.  Clear the pointer.
  current_ = itypes_.end();
}

bool choice_handler::can_skip() {
  return occurrence_.in_range(count_);
}

void choice_handler::abort() {
  if ((current_ != itypes_.end()) && (e_ != 0)) {
    const element_op &iop = current_->second.op();
    iop.destroy(e_);
  }
  e_ = 0;
}

