/* -*- c++ -*- */
#include "xml_serializer.h"
#include "formatter.h"
#include "classes/closure.h"
#include "classes/any_op.h"
#include "classes/member_cache.h"
#include "classes/namespaces.h"

#include <sstream>

using namespace aka2;

xml_serializer::~xml_serializer() {}

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

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

  if (props.get_schematype() == array_id)
    write_root_array(name, e, props);
  else
    write_element(e, props, props.op());

  formatter_->write(std::string("\n"));
}

void xml_serializer::write_ns(id_type nsid) {
  std::string line = " xmlns:";
  line += get_prefix(nsid) + "=\"" + get_namespace_uri(nsid) + "\"";
  formatter_->write(line);
}

void xml_serializer::write_namespace_attributes() {
  for (ids::const_iterator it = nsids_.begin(); it != nsids_.end(); ++it)
    write_ns(*it);
  is_root_ = false;
}

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

  const qname &tagname = props.get_name();

  switch (op.get_schematype()) {
  case sequence_id: {
    write_sequence(tagname, e, static_cast<const sequence_op&>(op));
    break;
  }
  case choice_id: {
    write_choice(tagname, e, static_cast<const choice_op&>(op));
    break;
  }
  case all_id: {
    const all_op &aop = static_cast<const all_op&>(op);
    write_all(tagname, e, aop);
    break;
  }
  case simplecontent_id: {
    write_simplecontent(tagname, e, static_cast<const simplecontent_op&>(op));
    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));
    break;
  }
  case fixed_id: {
    write_fixed_simpletype(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 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);
    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) {
  if (is_sequence_empty(e, sop.get_member_types())) {
    new_line();
    write_nill_element(tagname, e, sop);
  }
  else { 
    new_line();
    write_beginning_tag(tagname, e, sop);
    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());
      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());
      break;
    }
    case fixed_id: {
      write_fixed_simpletype(mtype);
      break;
    }
    case sequence_id:
    case choice_id:
    case all_id: {
      if (it->is_element())
        write_element(cache.value(), mtype, mtype.op());
      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: {
      if (it->is_element())
	write_ptrmember(cache.value(), mtype);
      else
	write_ptrmember_entity(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) {
  if (cop.empty(e)) {
    new_line();
    write_nill_element(tagname, e, cop);
  }
  else {
    new_line();
    write_beginning_tag(tagname, e, cop);
    
    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) {
  scoped_ptr<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());
    }
    else
      write_element_entity(i->get_node().ptr(), i->get_props(),
			   i->get_props().op());
  }
  ait.reset();
}

void xml_serializer::write_all(const qname &tagname, 
                              const void *e, const all_op &aop) {
  if (is_all_empty(e, aop.get_member_map())) {
    new_line();
    write_nill_element(tagname, e, aop);
  }
  else {
    new_line();
    write_beginning_tag(tagname, e, aop);
    
    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());
      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) {
  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_preconditions());

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

void xml_serializer::write_root_array(const qname &tagname, const void *e, 
				      const element_props &props) {
  const array_op &aop = static_cast<const array_op&>(props.op());
  if (aop.size(e) == 0) {
    new_line();
    write_nill_element(tagname, e, props.op());
  }
  else { 
    new_line();
    write_beginning_tag(tagname, e, aop);
    inc_indent_level();
    
    write_array(e, props);
    
    dec_indent_level();    
    new_line();
    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());
  scoped_ptr<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);
  }
  ait.reset();
}


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


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);
  }
}

void xml_serializer::write_ptrmember_entity(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_entity(deref_e, props, vop);
  }
}


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


void xml_serializer::write_fixed_simpletype(const element_props &props) {
  std::ostringstream ostring;
  props.write_fixed_string(ostring, system_preconditions());
  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);
  }
  else {
    new_line();
    write_beginning_tag(props.get_name(), 0, vop);
    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) {
  inc_ns_depth();

  formatter_->write("<" + tagname.qualified());

  if (is_root_)
    write_namespace_attributes();

  if (ns11_enabled_)
    write_ns11_declaration(tagname, op, e);

  write_attributes(e, op);

  formatter_->write(std::string(">"));
}

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

void xml_serializer::write_nill_element(const qname &tagname, 
                                       const void *e, const element_op &op) {
  inc_ns_depth();

  formatter_->write("<" + tagname.qualified());

  if (is_root_)
    write_namespace_attributes();

  if (ns11_enabled_)
    write_ns11_declaration(tagname, op, e);

  write_attributes(e, op);

  formatter_->write("/>");
  dec_ns_depth();
}

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;
      
      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->get_name().qualified() + "=\"";
          formatter_->write(line);
          std::ostringstream ostring;
          it->write_fixed_string(ostring, system_preconditions());
          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_preconditions());
	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->get_name().qualified() + "=\"";
	  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 any_attributes &anyattrs = *static_cast<const any_attributes*>(cache.value());
    
    for (any_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_ns_decl_if_new(id_type nsid) { 
  if (is_new_ns(nsid)) {
    write_ns(nsid);
    use_temp_nsdecl(nsid);
  }
}


/* any */
void xml_serializer::write_ns11_declaration(const qname &tagname, const element_op &op, 
					    const void *e) {
  id_type tagname_id = tagname.get_uri_id();
  write_ns_decl_if_new(tagname_id);

  if (op.get_schematype() == any_id) {
    const any &an = *static_cast<const any*>(e);
    for (any_attributes::const_iterator it = an.attributes_.begin();
	 it != an.attributes_.end(); ++it)
      write_ns_decl_if_new(it->name_.get_uri_id());
  }
  else {
    const attribute_types *atypes = op.get_attribute_types();
    if (atypes == 0)
      return;
    for (attribute_types::const_iterator it = atypes->begin();
	 it != atypes->end(); ++it)
      write_ns_decl_if_new(it->get_name().get_uri_id());
  }
}

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_);
  else {
    write_beginning_tag(an.name_, &an, any_op::dispatcher_);

    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);
  }      
}
