/* -*- c++ -*- */
#include "xml_serializer.h"
#include "formatter.h"
#include "classes/closure.h"
#include "classes/any_attributes.h"
#include "classes/any.h"
#include "classes/member_cache.h"

#include <sstream>

namespace aka2 {

xml_serializer::~xml_serializer() {}

void xml_serializer::serialize_internal(const void *e, const element_props &props, 
					const qname &name) {
  
  
  indent_ = 0;

  formatter_->write("<?xml version=\"1.0\" encoding=\"" + encoding_ 
		    + "\" standalone=\"yes\"?>\n");

//   write_namespace_attributes(ostm); 
  write_element(e, props, props.op(), true);
  formatter_->write(std::string("\n"));
}

void xml_serializer::write_namespace_attributes() {
  const prefix_map &prefixes = system_global_attributes().get_prefix_map();

  for (prefix_map::const_iterator it = prefixes.begin();
       it != prefixes.end(); ++it) {
    const std::string &prefix = it->second;
    id_type namespace_id = it->first;
    new_line();
    std::string line = " xmlns:";
    line += prefix + "=\"" + namespaces().get_namespace_uri(namespace_id) + "\"";
    formatter_->write(line);
  }
}

void xml_serializer::write_element(const void *e, const element_props &props,
				   const element_op &op, bool is_root) {

  const qname &tagname = props.get_name();

  switch (op.get_schematype()) {
  case sequence_id: {
    write_sequence(tagname, e, static_cast<const sequence_op&>(op), is_root);
    break;
  }
  case choice_id: {
    write_choice(tagname, e, static_cast<const choice_op&>(op), is_root);
    break;
  }
  case all_id: {
    const all_op &aop = static_cast<const all_op&>(op);
    write_all(tagname, e, aop, is_root);
    break;
  }
  case simplecontent_id: {
    write_simplecontent(tagname, e, static_cast<const simplecontent_op&>(op), is_root);
    break;
  }
  case array_id: {
    write_array(e, props);
    break;
  }  
  case ptrmember_id: {
    write_ptrmember(e, props);
    break;
  }
  case simpletype_id: {
    write_simpletype(tagname, e, static_cast<const simpletype_op&>(op), is_root);
    break;
  }
  case fixed_id: {
    write_fixed_simpletype(props, is_root);
    break;
  }
  case any_id: {
    write_any(*static_cast<const any*>(e));
    break;
  }
  case any_array_id: {
    write_any_array(*static_cast<const any_array*>(e));
    break;
  }
  case enclose_id:
  case disclose_id:
  case any_attribute_id:
    assert(!"Must not reach here.");
  }
}

void xml_serializer::write_element_entity(const void *e, const element_props &props,
					  const element_op &op) {

  switch (op.get_schematype()) {
  case sequence_id: {
    write_sequence_entity(e, static_cast<const sequence_op&>(op));
    break;
  }
  case choice_id: {
    write_choice_entity(e, static_cast<const choice_op&>(op));
    break;
  }
  case all_id: {
    write_all_entity(e, static_cast<const all_op&>(op));
    break;
  }
  case ptrmember_id: {
    write_ptrmember(e, props);
    break;
  }
  case any_id: {
    write_any(*static_cast<const any*>(e));
    break;
  }
  case any_array_id: {
    write_any_array(*static_cast<const any_array*>(e));
    break;
  }
  case fixed_id: {
    write_fixed_simpletype(props, false);
    break;
  }
  case simplecontent_id:
  case simpletype_id:
  case array_id: // Handled in sequence.
  case enclose_id:
  case disclose_id:
  case any_attribute_id:
    assert(!"Must not reach here.");
  }
}

void xml_serializer::write_sequence(const qname &tagname, 
                                   const void *e, const sequence_op &sop, 
                                   bool is_root) {
  if (is_sequence_empty(e, sop.get_member_types())) {
    new_line();
    write_nill_element(tagname, e, sop, is_root);
  }
  else { 
    new_line();
    write_beginning_tag(tagname, e, sop, is_root);
    inc_indent_level();
    
    write_sequence_entity(e, sop);
    
    dec_indent_level();    new_line();
    write_ending_tag(tagname);
  }
}

void xml_serializer::write_sequence_entity(const void *e, const sequence_op &sop) {
  const member_types &members = sop.get_member_types();
  for (member_types::const_iterator it = members.begin(); it != members.end(); ++it) {

    const member_type &mtype = *it;
    const_member_cache cache(e, mtype.op(), mtype.getter());
    cache.prepare();

    switch (mtype.get_schematype())         {
    case enclose_id: {
      new_line();
      write_beginning_tag(mtype.get_name(), 0, enclose_op(), false);
      inc_indent_level();
      break;
    }
    case disclose_id: {
      dec_indent_level();
      new_line();
      write_ending_tag(mtype.get_name());
      break;
    }
    case simpletype_id:
    case simplecontent_id:        {
      if (!mtype.is_element())
        throw internal_error();
      write_element(cache.value(), mtype, mtype.op(), false);
      break;
    }
    case fixed_id: {
      write_fixed_simpletype(mtype, false);
      break;
    }
    case sequence_id:
    case choice_id:
    case all_id: {
      if (it->is_element())
        write_element(cache.value(), mtype, mtype.op(), false);
      else 
        write_element_entity(cache.value(), mtype, mtype.op());
      break;
    }
    case array_id: {
      if (it->is_element())
	write_array(cache.value(), mtype);
      else
	write_group_array(cache.value(), mtype);
      break;
    }
    case ptrmember_id: {
      write_ptrmember(cache.value(), mtype);
      break;
    }
    case any_id:
      write_any(*static_cast<const any*>(cache.value()));
      break;
    case any_array_id:
      write_any_array(*static_cast<const any_array*>(cache.value()));
      break;
    case any_attribute_id:
      assert(!"Must not reach here.");
    }
  }
}

void xml_serializer::write_choice(const qname &tagname, 
                                 const void *e, const choice_op &cop, 
                                 bool is_root) {
  if (cop.empty(e)) {
    new_line();
    write_nill_element(tagname, e, cop, is_root);
  }
  else {
    new_line();
    write_beginning_tag(tagname, e, cop, is_root);
    
    inc_indent_level();

    write_choice_entity(e, cop);

    dec_indent_level();

    new_line();
    write_ending_tag(tagname);
  }
}

void xml_serializer::write_choice_entity(const void *e, const choice_op &cop) {
  item_iterator *ait = cop.get_iterator(e);
  while (ait->has_next()) {
    const item *i = ait->next();
    if (i->is_element())
      write_element(i->get_node().ptr(), i->get_props(), 
		    i->get_props().op(), false);
    else
      write_element_entity(i->get_node().ptr(), i->get_props(),
			   i->get_props().op());
  }
  delete ait;
}

void xml_serializer::write_all(const qname &tagname, 
                              const void *e, const all_op &aop, 
                              bool is_root) {
  if (is_all_empty(e, aop.get_member_map())) {
    new_line();
    write_nill_element(tagname, e, aop, is_root);
  }
  else {
    new_line();
    write_beginning_tag(tagname, e, aop, is_root);
    
    inc_indent_level();
    
    write_all_entity(e, aop);
    
    dec_indent_level();
    
    new_line();
    write_ending_tag(tagname);
  }
}

void xml_serializer::write_all_entity(const void *e, const all_op &aop) {
  const member_map &mmap = aop.get_member_map();
  for (member_map::const_iterator it = mmap.begin();
       it != mmap.end(); ++it) {
    const member_type &mtype = it->second;

    if (!it->second.is_element())
      throw internal_error();

    const_member_cache cache(e, mtype.op(), mtype.getter());
    cache.prepare();
    
    switch (mtype.get_schematype()) {
    case simpletype_id:
    case simplecontent_id:
    case sequence_id:
    case choice_id:
    case all_id:
    case array_id:
    case ptrmember_id: 
    case fixed_id: {
      write_element(cache.value(), mtype, mtype.op(), false);
      break;
    }
    case enclose_id:
    case disclose_id:
    case any_id:
    case any_array_id:
    case any_attribute_id:
      assert(!"Must not reach here.");
    }
  }
}

void xml_serializer::write_simplecontent(const qname &tagname,
                                         const void *e, const simplecontent_op &sop,
                                         bool is_root) {
  const member_type &vt = sop.get_valuetype();
  const_member_cache cache(e, vt.op(), vt.getter());
  cache.prepare();

  assert(vt.get_schematype() == simpletype_id);
  const simpletype_op &simop = static_cast<const simpletype_op&>(vt.op());
    
  std::ostringstream ostring;
  simop.write_text(cache.value(), ostring, system_global_attributes());

  if (ostring.rdbuf()->str().empty()) {
    new_line();
    write_nill_element(tagname, e, sop, is_root);
  }
  else {
    new_line();
    write_beginning_tag(tagname, e, sop, is_root);
    formatter_->write_text_entity(ostring.rdbuf()->str());
    write_ending_tag(tagname);
  }
}

void xml_serializer::write_array(const void *e, const element_props &props) {
  const array_op &aop = static_cast<const array_op&>(props.op());
  array_iterator *ait = aop.get_iterator(e);
  const element_op &iop = aop.get_item_op();
  while (ait->has_next()) {
    const void *item = ait->next();
    write_element(item, props, iop, false);
  }
  delete ait;
}


void xml_serializer::write_group_array(const void *e, const element_props &props) {
  const array_op &aop = static_cast<const array_op&>(props.op());
  array_iterator *ait = aop.get_iterator(e);
  const element_op &iop = aop.get_item_op();
  while (ait->has_next()) {
    const void *item = ait->next();
    write_element_entity(item, props, iop);
  }
  delete ait;
}


void xml_serializer::write_ptrmember(const void *e, const element_props &props) {
  const ptrmember_op &pop = static_cast<const ptrmember_op&>(props.op());
  if (!pop.is_null(e)) {
    const void *deref_e = pop.dereference(e);
    const element_op &vop = pop.get_value_op();
    write_element(deref_e, props, vop, false);
  }
}



void xml_serializer::write_simpletype(const qname &tagname,
                                     const void *e, const simpletype_op &sop, 
                                     bool is_root) {
  std::ostringstream ostring;
  sop.write_text(e, ostring, system_global_attributes());
  if (ostring.rdbuf()->str().empty()) {
    new_line();
    write_nill_element(tagname, e, sop, is_root);
  }
  else {
    new_line();
    write_beginning_tag(tagname, e, sop, is_root);
    formatter_->write_text_entity(ostring.rdbuf()->str());
    write_ending_tag(tagname);
  }
}



void xml_serializer::write_fixed_simpletype(const element_props &props,
					    bool is_root) {

  std::ostringstream ostring;
  props.write_fixed_string(ostring, system_global_attributes());
  const element_op &vop = static_cast<const element_op&>(props.op());

  if (ostring.rdbuf()->str().empty()) {
    new_line();
    write_nill_element(props.get_name(), 0, vop, is_root);
  }
  else {
    new_line();
    write_beginning_tag(props.get_name(), 0, vop, is_root);
    formatter_->write_text_entity(ostring.rdbuf()->str());
    write_ending_tag(props.get_name());
  }
}


void xml_serializer::write_beginning_tag(const qname &tagname, 
                                        const void *e, const element_op &op,
                                        bool is_root) {
  std::string tag = "<";
  tag += tagname.qualified();
  formatter_->write(tag);

  if (is_root)
    write_namespace_attributes();

  write_attributes(e, op);
  formatter_->write(std::string(">"));
}

void xml_serializer::write_ending_tag(const qname &tagname) {
  std::string tag = "</";
  tag += tagname.qualified() + ">";
  formatter_->write(tag);
}

void xml_serializer::write_nill_element(const qname &tagname, 
                                       const void *e, const element_op &op,
                                       bool is_root) {
  std::string line="<" + tagname.qualified();
  formatter_->write(line);

  if (is_root)
    write_namespace_attributes();

  write_attributes(e, op);

  line = "/>";
  formatter_->write(line);
}

void xml_serializer::write_attributes(const void *elm, const element_op &op) {
  if (op.get_attribute_types() != 0) {
    const attribute_types &attrs = *op.get_attribute_types();
    for (attribute_types::const_iterator it = attrs.begin(); it != attrs.end(); ++it) {
      const attribute_type &attr = it->second;
      
      assert((attr.get_schematype() == fixed_id) || 
             (attr.get_schematype() == simpletype_id));
      
      if (attr.get_schematype() == fixed_id) {
        if (attr.is_required()) {
          std::string line(" ");
          line += it->first.local() + "=\"";
          formatter_->write(line);
          std::ostringstream ostring;
          it->second.write_fixed_string(ostring, system_global_attributes());
          formatter_->write_attribute_entity(ostring.rdbuf()->str());
          formatter_->write(std::string("\""));
        }
      }
      else {
        std::ostringstream ostring;
	const_member_cache cache(elm, attr.op(), attr.getter());
	cache.prepare();
        static_cast<const simpletype_op&>(attr.op()).write_text(cache.value(), ostring, 
                                                                system_global_attributes());
	bool nothing_to_write = 
	  ostring.rdbuf()->str().empty() || attr.is_attribute_default(elm);
	  
	if ((!nothing_to_write) || attr.is_required()) {
          std::string line(" ");
	  line += it->first.local() + "=\"";
	  formatter_->write(line);
	  formatter_->write_attribute_entity(ostring.rdbuf()->str());
	  formatter_->write("\"");
	}

      }
    }
  }

  if (op.get_anyattr_type() != 0) {
    const any_member &atype = *op.get_anyattr_type();
    const_member_cache cache(elm, atype.op(), atype.getter());
    cache.prepare();
    const wc_attributes &anyattrs = *static_cast<const wc_attributes*>(cache.value());
    
    for (wc_attributes::const_iterator anyit = anyattrs.begin();
         anyit != anyattrs.end(); ++anyit) {
      std::string line(" ");
      line += anyit->name_.qualified() + "=\"";
      formatter_->write(line);
      formatter_->write_attribute_entity(anyit->value_);
      formatter_->write("\"");
    }
  }
}


void xml_serializer::write_any(const any &an) {

  new_line();
  if ((an.children_.size() == 0) && an.value_.empty())
    write_nill_element(an.name_, &an, any_op::dispatcher_, false);
  else {
    write_beginning_tag(an.name_, &an, any_op::dispatcher_, false);

    if (an.children_.size() == 0) 
      formatter_->write(an.value_);
    else {
      inc_indent_level();
      for (any_array::const_iterator it = an.children_.begin();
	   it != an.children_.end(); ++it) {
	write_any(*it);
      }
      dec_indent_level();
      new_line();
    }
    write_ending_tag(an.name_);
  }
}


void xml_serializer::write_any_array(const any_array &ar) {

  if (ar.empty())
    return;
  
  for (any_array::const_iterator it = ar.begin();
       it != ar.end(); ++it) {
    write_any(*it);
  }      
}



} // namespace aka2
