#include "akaxisonizer.h"


void akaxisonizer::replace_any() {
  for (class_defs::iterator cit = registry_->classes_.begin();
       cit != registry_->classes_.end(); ++cit) {

    class_def *def = cit->second;

    for (element_types::iterator it = def->etypes_.begin();
	 it != def->etypes_.end(); ++it) {
      if (it->is_ptrmember()) {
	it->ptrmember_ = true;
	if (it->type_ == aka::qname("xs:anyType"))
	  it->type_ = aka::qname("aka:any");
	else if (it->type_ == aka::qname("xs:anySimpleType"))
	  it->type_ = aka::qname("xs:string");
      }
      else if (it->is_array()) {
	if (it->type_ == aka::qname("xs:anyType"))
	  it->type_ = aka::qname("aka:any_array");
	else if (it->type_ == aka::qname("xs:anySimpleType"))
	  it->type_ = aka::qname("aka:string_array");
      }
      else {
	if (it->type_ == aka::qname("xs:anyType"))
	  it->type_ = aka::qname("aka:any");
	else if (it->type_ == aka::qname("xs:anySimpleType"))
	  it->type_ = aka::qname("xs:string");
      }
    }

    for (attribute_types::iterator ait = def->adefs_.attributes_.begin();
	 ait != def->adefs_.attributes_.end(); ++ait) {
      if (ait->type_ == aka::qname("xs:anySimpleType"))
	ait->type_ = aka::qname("xs:string");
    }

  }



}


void akaxisonizer::akaxisonize() {
  if (verbose_)
    std::cout << "Optimization" << std::flush;

  bool optimizing = true;
  while (optimizing) {
    if (verbose_)
      std::cout << "." << std::flush;
    optimizing = false;
    for (class_defs::iterator it = registry_->classes_.begin();
	 it != registry_->classes_.end(); ++it) {
      class_def *def = it->second;
      optimizing |= simplify_class(def);
    }
  }

//   optimizing = true;
//   while (optimizing) {
//     if (verbose_)
//       std::cout << "." << std::flush;
//     optimizing = false;
//     for (class_defs::iterator it = registry_->classes_.begin();
// 	 it != registry_->classes_.end(); ++it) {
//       class_def *def = it->second;
//       optimizing |= optimize_children(def);
//     }
//   }
  if (verbose_)
    std::cout << "done." << std::endl;
}


void akaxisonizer::add_missings() {
  for (class_defs::iterator it = registry_->classes_.begin();
       it != registry_->classes_.end(); ++it) {
    class_def *def = it->second;
    fix_member_name(def);
    add_missings_for_class(def);
  }
}

bool akaxisonizer::add_member_number(aka::qname &name, int index) {

  std::string::size_type pos = name.local().find_first_of('*');
  if (pos == std::string::npos)
    return false;

  name.local().resize(name.local().size() - 1);

  std::ostringstream ostm;
  ostm << index;
  name.local() += ostm.rdbuf()->str();
  return true;
}

void akaxisonizer::fix_member_name(class_def *def) {
  int seqindex = 0;
  int choindex = 0;
  int allindex = 0;

  for (element_types::iterator it = def->etypes_.begin();
       it != def->etypes_.end(); ++it) {
    if (it->name_ == aka::qname("@")) {
      it->name_ = it->type_.local();
      continue;
    }

    if (registry_->is_predefined(it->type_))
      continue;

    class_def *child = registry_->classes_.get(it->type_);
    if (child->id_ == sequence_id) {
      if (add_member_number(it->name_, seqindex))
	++seqindex;
    }
    if (child->id_ == choice_id) {
      if (add_member_number(it->name_, choindex))
	++choindex;
    }
    if (child->id_ == all_id) {
      if (add_member_number(it->name_, allindex))
	++allindex;
    }
  }
}

bool akaxisonizer::simplify_class(class_def *def) {

  bool one_child_element = def->etypes_.size() == 1;

  if (one_child_element) {
    element_type &etype = def->etypes_.front();

    // Don't optimize toplevel types.
    if (registry_->is_toplevel(etype.type_))
      return false;

    // If element is not group, no optimization.
    if (!etype.is_group_)
      return false;

    // If predefined type, no optimization.
    // Because upper element_type reference required to be updated.
    if (!registry_->classes_.exists(etype.type_))
      return false;

    // If class has attributes, it's complexType.
    // Could not be optimized.
    class_def *child = registry_->classes_.get(etype.type_);

    // We need space to describe nested array/choice.  No optimization.
    bool both_are_array = child->is_array() && etype.is_array();
    if (both_are_array)
      return false;

    if (!def->adefs_.has_attributes()) {
      // parent does not have attributes.
      // replace parent with child.
      def->etypes_ = child->etypes_;
      def->adefs_ = child->adefs_;
      def->id_ = child->id_;
      return true;
    }
    
    if (child->adefs_.has_attributes())
      return false;

    if (child->id_ != choice_id) { // Child is particle (all or sequence or simpleType/simpleContent).
      // Parent has particle.
      // Therefore expand child elements to parent to erase anonymous types.
      def->etypes_ = child->etypes_;
      def->id_ = child->id_;
      return true;;
    }
  }
  return false;
}

bool akaxisonizer::optimize_children(class_def *def) {

  // optimizing member.
  bool optimized = false;
  
  // Here we have optimized classdefs for members.
  // Further optimization with element_type and class_def will be done!
  for (element_types::iterator it = def->etypes_.begin();
       it != def->etypes_.end(); ++it) {
    
    // Cannot do further optimization for predefined types.
    if (registry_->is_predefined(it->type_))
      continue;

    if (!registry_->classes_.exists(it->type_))
      throw fatal_error(it->type_.qualified() + " not defined.", __FILE__, __LINE__);

    class_def *child = registry_->classes_.get(it->type_); 
    
    if (child->name_ == aka::qname("xs:anyType")) {
      if (it->is_array())
	it->type_ = aka::qname("aka:any_array");
      else
	it->type_ = aka::qname("aka:any");
      continue;
    }
    
    // nested array could not be optimized.
    if (it->is_array() && child->is_array())
      continue;
    // If child has attribute, it's a complexType.  Could not be optimized.
    if (child->adefs_.has_attributes())
      continue;
    
    // Replace child with member type if possible.
    if (child->etypes_.size() == 1) {
      element_type &et = child->etypes_.front();
      if (registry_->is_predefined(et.type_)) {
	it->type_ = et.type_;
	if (et.is_array())
	  it->occ_ = et.occ_;
	optimized = true;
	continue;
      }
      else {
	// Shrink nested sequence if possible.
	// Here parent nor child is not sequence. 
	if ((child->id_ != sequence_id) || (def->id_ != sequence_id))
	  continue;
	
	// replace parent member with child members.  
	it = def->etypes_.erase(it);
	def->etypes_.insert(it, child->etypes_.begin(), child->etypes_.end());
	
	optimized = true;
	continue;
      }
    }
  }
  return optimized;
}

// !!!!!!!!!!!!!!!!!! Optimize double/nested array/choice.

void akaxisonizer::add_missings_for_class(class_def *def) {

  if ((def->id_ == array_id) || (def->id_ == choice_id))
    return;

  // Confirm occurrence, and add array decls if required.
  for (element_types::iterator it = def->etypes_.begin();
       it != def->etypes_.end(); ++it) {

    if (registry_->is_any(it->type_))
      continue;

    if (registry_->is_predefined(it->type_)) {
      if (it->is_ptrmember()) {
	it->ptrmember_ = true;
      }
      else if (it->is_array()) {
	it->type_ = registry_->get_predefined_array(it->type_);
	assert(!it->type_.empty());
      }
      continue;
    }

    class_def *childdef = registry_->classes_.get(it->type_);
    if ((childdef->id_ == array_id) || (childdef->id_ == choice_id))
      continue;
    
    if (it->is_array() && !childdef->is_array()) {
      if (it->is_ptrmember())
	it->ptrmember_ = true;
      else
	it->type_ = define_array(childdef->name_, it->occ_);
      assert(!it->type_.empty());
    }
    else if (!it->is_array() && childdef->is_array()) {
      if (childdef->is_ptrmember())
	it->ptrmember_ = true;
      else
	it->type_ = define_array(childdef->name_, childdef->occ_);
      it->occ_ = childdef->occ_;
      assert(!it->type_.empty());
    }
    else if (it->is_array() && childdef->is_array()) {
      aka::qname arrayname = define_array(childdef->name_, childdef->occ_);
      aka::qname holdername = childdef->name_;
      holdername.local() += "_holder";
      it->type_ = holdername;
      
      if (!registry_->classes_.exists(holdername)) {
	class_def *holder = new array_def(holdername);
	holder->value_type_ = childdef->name_;
	element_type el;
	el.name_ = childdef->name_;
	el.type_ = arrayname;
	el.occ_ = childdef->occ_;
	holder->etypes_.push_back(el);
	registry_->classes_.insert(holdername, holder);
	assert(!el.type_.empty());
      }
    }
  }
}

aka::qname akaxisonizer::define_array(const aka::qname &valuename, const aka::occurrence &occ) {
  
  aka::qname arrayname = valuename;
  arrayname.local() += "_array";
  
  if (registry_->classes_.exists(arrayname))
    return arrayname;
  
  class_def *ardef = new array_def(arrayname);
  ardef->value_type_ = valuename;
  registry_->classes_.insert(arrayname, ardef);
  
  assert(!arrayname.empty());
  return arrayname;
}
