/* -*- c++ -*- */
#include "serializer.h"
#include "formatter.h"
#include "classes/member_cache.h"
#include "classes/any.h"
#include "classes/any_op.h"
#include "classes/namespace_statics.h"
#include "classes/traits.h"

using namespace aka2;

serializer::serializer(int tab, const std::string &encoding)
  : indent_str_(tab, ' '), encoding_(encoding), formatter_(0) {}

serializer::~serializer() {}

void serializer::serialize(const document &doc, std::ostream &ostm) {
  do_serialize(doc.get_root().ptr(), doc.get_props(), doc.get_name(), ostm);
}

void serializer::using_prefix(const std::string &prefix) {
  id_type id = get_prefix_id(prefix);
  nsids_.push_back(id);
}

void serializer::using_uri(const std::string &uri) {
  id_type id = get_uri_id(uri);
  nsids_.push_back(id);
}


void serializer::do_serialize(const void *e, const element_props &props, const qname &name, std::ostream &ostm) {
  create_formatter(ostm);
  indent_ = 0;
  is_root_ = true;
  serialize_internal(e, props, name);
  formatter_.reset();
}


void serializer::create_formatter(std::ostream& ostm) {
#ifdef USE_XERCESC
  if (formatter::use_xerces_transcoder_)
    formatter_.reset(new xerces_formatter(ostm));
  else
#endif
    formatter_.reset(new babel_formatter(ostm));

  try {
    formatter_->prepare(encoding_);
  }
  catch (...) {
    formatter_.reset();
    throw internal_error();
  }
}

void serializer::new_line() {
  std::string line = "\n";
  for (int i = indent_; i != 0; --i)
    line += indent_str_;
  formatter_->write(line);
}

bool serializer::is_sequence_empty(const void *e, const member_types &mtypes) {
  for (member_types::const_iterator it = mtypes.begin();
       it != mtypes.end(); ++it) {
    if (!is_member_empty(e, *it))
      return false;
  }
  return true;
}

bool serializer::is_all_empty(const void *e, const member_map &mmap) {
  for (member_map::const_iterator it = mmap.begin();
       it != mmap.end(); ++it) {
    if (!is_member_empty(e, it->second))
      return false;
  }
  return true;
}

bool serializer::is_member_empty(const void *e, const member_type &mtype) {

  const_member_cache cache(e, mtype.op(), mtype.getter());
  cache.prepare();
  
  switch (mtype.get_schematype()) {
  case simpletype_id:
  case simplecontent_id:
    return false;
  case array_id: {
    const array_op &aop = static_cast<const array_op&>(mtype.op());
    if (aop.size(cache.value()) != 0)
      return false;
    break;
  }
  case sequence_id: {
    if (mtype.is_element())
      return false;
    const sequence_op &sop = static_cast<const sequence_op&>(mtype.op());
    if (!is_sequence_empty(cache.value(), sop.get_member_types()))
      return false;
    break;
  }
  case choice_id: {
    if (mtype.is_element())
      return false;
    const choice_op &cop = static_cast<const choice_op&>(mtype.op());
    if (cop.size(cache.value()) != 0)
      return false;
    break;
  }
  case all_id: {
    if (mtype.is_element())
      return false;
    const all_op &aop = static_cast<const all_op&>(mtype.op());
    if (!is_all_empty(cache.value(), aop.get_member_map()))
      return false;
    break;
  }
  case ptrmember_id: {
    const ptrmember_op &pop = static_cast<const ptrmember_op&>(mtype.op());
    if (!pop.is_null(cache.value()))
      return false;
    break;
  }
  case any_array_id: {
    const any_array &ar = *static_cast<const any_array*>(cache.value());
    return ar.empty();
  }
  default:
    assert(!"Must not reach here.");
  }
  return true;
}


void serializer::inc_ns_depth() {
  if (ns_stack_.empty()) {
    ns_set new_set;
    new_set.insert(empty_token); // empty prefix and "xml" prefix is XML-instrinsic prefixes.
    new_set.insert(xml_ns_token);
    ns_stack_.push(new_set); // push empty one.
  }
  else {
    const ns_set &nsset = ns_stack_.top();
    ns_stack_.push(nsset);
  }
}

void serializer::dec_ns_depth() {
  ns_stack_.pop();
}

bool serializer::is_new_ns(id_type id) const {
  for (ids::const_iterator it = nsids_.begin(); it != nsids_.end();
       ++it) {
    if (*it == id)
      return false;
  }

  const ns_set &nsset = ns_stack_.top();
  return nsset.find(id) == nsset.end();
}

void serializer::use_temp_nsdecl(id_type id) {
  assert(is_new_ns(id));
  ns_set &nsset = ns_stack_.top();
  nsset.insert(id);
}
