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

#include <sstream>

namespace aka2 {

xml_serializer::~xml_serializer() {}

void xml_serializer::serialize_internal(const void *root, const qname &name,
                                        const element_op &op) {
  
  
  indent_ = 0;

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

//   write_namespace_attributes(ostm); 
  write_element(name, root, 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 qname &tagname, 
                                   const void *e, const element_op &op, 
                                   bool is_root) {
  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: {
    const array_op &aop = static_cast<const array_op&>(op);
    write_array(tagname, e, aop);
    break;
  }  
  case simpletype_id:
    write_simpletype(tagname, e, static_cast<const simpletype_op&>(op), is_root);
    break;
  default:
    assert(!"Must not reach here.");
  }
}

void xml_serializer::write_element_entity(const void *e, 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 array_id: {
    const array_op &aop = static_cast<const array_op&>(op);
    write_group_array(e, aop);
    break;
  }  
  case all_id: 
    // Must be under the complexType.  
    // It should not be group or nested particle.
  case simplecontent_id:
  case simpletype_id:
  default:
    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 const_memberpair mpair = it->get_member(e);
    
    switch (mpair.op_.get_schematype())         {
    case enclose_id: {
      new_line();
      write_beginning_tag(it->get_name(), 0, enclose_op(), false);
      inc_indent_level();
      break;
    }
    case disclose_id: {
      dec_indent_level();
      new_line();
      write_ending_tag(it->get_name());
      break;
    }
    case simpletype_id:
    case simplecontent_id:        {
      if (!it->is_element())
        throw internal_error();
      write_element(it->get_name(), 
                    mpair.e_, mpair.op_,
                    false);
      break;
    }
    case ptrmember_id: {
      const ptrmember_op &pop = static_cast<const ptrmember_op&>(mpair.op_);
      if (!pop.is_null(mpair.e_))
        write_element(it->get_name(), pop.dereference(mpair.e_), pop.get_value_op(), 
                      false);
      break;
    }
    case fixed_id: {
      new_line();
      write_beginning_tag(it->get_name(), 0, mpair.op_, false); 
      std::ostringstream ostm;
      it->write_fixed_string(ostm);
      formatter_->write(ostm.rdbuf()->str());
      write_ending_tag(it->get_name());
      break;
    }
    case sequence_id:
    case choice_id:
    case all_id:
    case array_id: {
      if (it->is_element())
        write_element(it->get_name(), mpair.e_, mpair.op_, false);
      else 
        write_element_entity(mpair.e_, mpair.op_);
      break;
    }
    default:
      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_name(), i->element(), i->get_op(), false);
    else
      write_element_entity(i->element(), i->get_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;
    const const_memberpair mpair = mtype.get_member(e);

    if (!it->second.is_element())
      throw internal_error();
    
    switch (mpair.op_.get_schematype()) {
    case simpletype_id:
    case simplecontent_id: {
      write_element(mtype.get_name(), 
                    mpair.e_, static_cast<const simpletype_op&>(mpair.op_), false);
      break;
    }
    case sequence_id:
    case choice_id:
    case all_id:
    case array_id: {
      write_element(mtype.get_name(), mpair.e_, mpair.op_, false);
      break;
    }
    case ptrmember_id: {
      const ptrmember_op &pop = static_cast<const ptrmember_op&>(mpair.op_);
      if (!pop.is_null(mpair.e_))
        write_element(mtype.get_name(), pop.dereference(mpair.e_), pop.get_value_op(), false);
      break;
    }
    default:
      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_memberpair valpair = vt.get_member(e);
  assert(valpair.op_.get_schematype() == simpletype_id);
  const simpletype_op &simop = static_cast<const simpletype_op&>(valpair.op_);
    
  std::ostringstream ostring;
  simop.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_array(const qname &tagname, 
                                 const void *e, const array_op &aop) {
  const element_op &itemop = aop.get_item_op();
  array_iterator *ait = aop.get_iterator(e);
    
  while (ait->has_next()) {
    const void *item = ait->next();
    write_element(tagname, item, itemop, false);
  }
  delete ait;
}

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

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_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);
          formatter_->write_attribute_entity(ostring.rdbuf()->str());
          formatter_->write(std::string("\""));
        }
      }
      else if (attr.should_write(elm)) {
        std::string line(" ");
        line += it->first.local() + "=\"";
        formatter_->write(line);
        
        const_memberpair mpair = attr.get_member(elm);
        std::ostringstream ostring;
        static_cast<const simpletype_op&>(mpair.op_).write_text(mpair.e_, ostring, 
                                                                system_global_attributes());
        formatter_->write_attribute_entity(ostring.rdbuf()->str());
        formatter_->write("\"");
      }
    }
  }

  if (op.get_anyattr_type() != 0) {
    const any_member &atype = *op.get_anyattr_type();
    const_memberpair mpair = atype.get_member(elm);
    const wc_attributes &anyattrs = *static_cast<const wc_attributes*>(mpair.e_);
    
    for (wc_attributes::const_iterator anyit = anyattrs.begin();
         anyit != anyattrs.end(); ++anyit) {
      std::string line(" ");
      line += anyit->tagname_.qualified() + "=\"";
      formatter_->write(line);
      formatter_->write_attribute_entity(anyit->value_);
      formatter_->write("\"");
    }
  }
}

} // namespace aka2
