#include "platform.h"
#include "classgen.h"
#include <iostream>
#include "exception.h"
#include <assert.h>
#include "schema_xiso.h"
#include <algorithm>

#include <ctype.h>

using namespace osixaka;


void generator::generate_attributes(const AttributeList &attrs, attribute_contents *cls) {

  for (AttributeList::const_iterator ait = attrs.begin(); ait != attrs.end(); ++ait) {
    if (ait->name_.empty()) {
      std::ostringstream ostm;
      ostm << "xs:attribute@name is empty.";
      throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
    }
    cls->push_attribute(ait->name_, ait->type_);
  }
}

const contents* generator::find_contents(const aka::qname &type) const {
  for (contents_array::const_iterator it = contents_.begin();
       it != contents_.end(); ++it)
    if ((*it)->name_ == type)
      return *it;
  return 0;
}


int generator::generate_classes(xs::Schema &root) {

  ComplexTypeList cel;
  GlobalElementList gel;

  std::cout << "Resolving dependency..." << std::flush;

  TopLevelNodesLeaf::moc rootmoc(root.nodes_);
  rootmoc.find_elements(cel, "xs:complexType");
  rootmoc.find_elements(gel, "xs:element");
  
  declare_global_complexTypes(cel);
  declare_global_elements(gel);
  generate_global_complexTypes(cel);
  generate_global_elements(gel);

  contents_array resolved;
  qname_set resolved_types;

  while (!contents_.empty()) {
    const contents *no_deps = 0;
    
    for (contents_array::iterator 
	   it = contents_.begin(); it != contents_.end(); ++it) {
      
      if ((*it)->is_resolved(resolved_types)) {
	      no_deps = *it;
	      contents_.erase(it);
	      resolved.push_back(no_deps);
	      std::pair<qname_set::iterator, bool> res = resolved_types.insert(no_deps->name_);
	      if (!res.second)
	        throw fatal_error(__FILE__, __LINE__, "Dup name.");
	      std::cout << std::endl
		              << no_deps->name_ << ": dependency resolved." << std::flush;
      	break;
      }
    }    
    if (no_deps == 0) // Dependency cannot be deduced.
      throw fatal_error(__FILE__, __LINE__, 
			"Circular dependency detected.");
  }

  contents_ = resolved;
  std::sort(documents_.begin(), documents_.end(), document_less_by_type());

  std::cout << std::endl
	    << "done." << std::endl;

  return 0;
}

std::string generator::get_filesig(const std::string &filename) {
  std::string sig;
  for (std::string::const_iterator it = filename.begin();
       it != filename.end(); ++it)
    sig += (*it == '.') ? '_' : toupper(*it);
  return sig;
}


void generator::write_header(std::ostream &ostm, const std::string &filename) {
  std::string filesig = get_filesig(filename);

  ostm << "/* -*- c++ -*-  (Tell emacs for this file to be cpp-header.)*/" << std::endl
       << "#ifndef " << filesig << "__" << std::endl
       << "#define " << filesig << "__" << std::endl
       << std::endl;
  
  ostm << "/**" << std::endl
       << " * @file " << filename << std::endl
       << " * Document class declarations generated by osixaka." << std::endl
       << " */" << std::endl
       << std::endl;

}

void generator::write_footer(std::ostream &ostm, const std::string &filename) {
  std::string filesig = get_filesig(filename);
  ostm << std::endl
       << "#endif //" << filesig << "__" << std::endl;
}


void generator::write_element_header(const std::string &filename, std::ostream &ostm) {

  write_header(ostm, filename);
  ostm << "#include <akaxiso/akaxiso.h>" << std::endl
       << "#include <list>" << std::endl
       << "#include <string>" << std::endl
       << std::endl
       << "void instantiate_xiso();"
       << std::endl
       << std::endl;

  for (contents_array::const_iterator ait = contents_.begin();
       ait != contents_.end(); ++ait) {
    (*ait)->generate_element(ostm);
  }

  for (document_contents::iterator dit = documents_.begin();
       dit != documents_.end(); ) {

    document_contents::iterator pit = dit;
    dit = std::upper_bound(pit, documents_.end(), *pit, document_less_by_type());
    if (std::distance(pit, dit) == 1) {
      ostm << "/** serialize as <" << pit->document_name_ << "> */" << std::endl
	   << "void serialize(const " << get_cpptype(pit->element_type_) 
	   << "& root, std::ostream &ostm);" << std::endl;
      continue;
    }
    
    const aka::qname &root_type = pit->element_type_;
    ostm << "/** serialization of " << pit->element_type_ << " as :" << std::endl;
    for ( ; pit != dit; ++pit) {
      ostm << "     <" << pit->document_name_ << ">";
      if (std::distance(pit, dit) == 1)
	ostm << "." << std::endl;
      else
	ostm << "," << std::endl;
    }
    ostm << "*/" << std::endl
	 << "void serialize(const " << get_cpptype(root_type) << "& root, "
	 << "std::ostream &ostm, const std::string &docname);" << std::endl;
  }  

  ostm << "/** general parse function */" << std::endl
       << "aka::document parse(std::istream &istm);" << std::endl
       << std::endl;

  write_footer(ostm, filename);
}


void generator::write_xiso_header(const std::string &element_fn, 
				  const std::string &xiso_fn, 
				  std::ostream &ostm) {

  write_header(ostm, xiso_fn);
  ostm << "#include <akaxiso/content_model.h>" << std::endl
       << "#include \"" << element_fn << "\"" << std::endl
       << std::endl;

  for (contents_array::const_iterator it = contents_.begin();
       it != contents_.end(); ++it) {
    (*it)->generate_xiso(ostm);
  }

  ostm << std::endl;
  write_footer(ostm, xiso_fn);
}


void generator::write_xiso_implementation(const std::string &xiso_header, 
					  const std::string &xiso_impl,
					  std::ostream &ostm) {
  
  ostm << "#include \"" << xiso_header << "\"" << std::endl
       << std::endl;
  
  for (contents_array::const_iterator ait = contents_.begin();
       ait != contents_.end(); ++ait) {
    (*ait)->generate_xiso_impl(ostm);
  }

  ostm << "void instantiate_xiso() {" << std::endl;

  for (document_contents::iterator dit = documents_.begin();
       dit != documents_.end(); ++dit) {
    const contents *root_type = find_contents(dit->element_type_);
    if (root_type != 0 && xs::is_array(root_type->occ_)) { 
      ostm << "  aka::element_props &props = aka::doctype(\"" 
	   << dit->document_name_ << "\", "
	   << get_leaf_name(dit->element_type_) << "());" << std::endl
	   << "props.set_occurrence(";
      write_occurrence(root_type->occ_, ostm);
      ostm << ");" << std::endl;
    }
    else
      ostm << "  aka::doctype(\"" << dit->document_name_ << "\", "
	   << get_leaf_name(dit->element_type_) << "());" << std::endl;
  }
  ostm << "}" << std::endl
       << std::endl;
  
  for (document_contents::iterator it = documents_.begin();
       it != documents_.end(); ) {
    document_contents::iterator pit = it;
    it = std::upper_bound(pit, documents_.end(), *pit, document_less_by_type());
    
    if (std::distance(pit, it) == 1) {
      ostm << "void serialize(const " << get_cpptype(pit->element_type_) 
	   << " &root, std::ostream &ostm) {" << std::endl
	   << "  aka::xml_serializer ser;" << std::endl
	   << "  ser.serialize(root, \"" << pit->document_name_ << "\", ostm);" << std::endl
	   << "}" << std::endl
	   << std::endl;
      continue;
    }
    else {
      ostm << "void serialize(const " << get_cpptype(pit->element_type_)
	   << " &root, std::ostream &ostm, const std::string &docname) {" << std::endl
	   << "  aka::xml_serializer ser;" << std::endl
	   << "  ser.serialize(root, docname, ostm);" << std::endl
	   << "}" << std::endl
	   << std::endl;
    }
  }    


  ostm << "aka::document parse(std::istream &istm) {" << std::endl
       << "  aka::xml_parser parser;" << std::endl
       << "  aka::document doc = parser.parse(istm);" << std::endl
       << "  return doc;" << std::endl
       << "}" << std::endl
       << std::endl;

  ostm << std::endl;
}


void generator::declare_global_elements(const GlobalElementList &elms) {

  GlobalElementList::const_iterator it;
  for (it = elms.begin(); it != elms.end(); ++it) {
    if (it->type_.empty() && it->complexTypes_.empty())
      throw fatal_error(__FILE__, __LINE__, "xs:element does not specify its content model.");
  }

  for (it= elms.begin(); it != elms.end(); ++it) {
    document_class doc;
    doc.document_name_ = it->name_;

    if (!it->complexTypes_.empty()) {
      // anonymous <xs:complexType> specifies the content model.
      complexTypes_.insert(it->name_, it->name_.qualified());
      doc.element_type_ = it->name_;
      elements_.insert(it->name_, it->name_);
    }
    else if (builtinTypes_.exists(it->type_)) { 
      // document root content is specified by xs:element@type, 
      // and it points to the <xs:simpleType>.
      doc.element_type_ = it->type_;
      elements_.insert(it->name_, it->type_);
    }
    else if (complexTypes_.exists(it->type_)) {
      // document root is specified by xs:element@type, 
      // and it points to the <xs:complexType>.
      doc.element_type_ = complexTypes_.resolve(it->type_);
      elements_.insert(it->name_, it->type_);
    }
    else {
      std::ostringstream ostm;
      ostm << "Type not found(" << it->type_.qualified() << ")";
      throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
    }

    documents_.push_back(doc);
  }
}


void generator::declare_global_complexTypes(const ComplexTypeList &cel) {
  for (ComplexTypeList::const_iterator it = cel.begin(); it != cel.end(); ++it) {
    if (it->name_.empty())
      throw fatal_error(__FILE__, __LINE__, "xs:complexType@name is empty.");
    complexTypes_.insert(it->name_, cppname(it->name_));
  }
}

void generator::generate_global_elements(const GlobalElementList &elms) {

  GlobalElementList::const_iterator it;
  for (it = elms.begin(); it != elms.end(); ++it) {
    if (!it->complexTypes_.empty()) {
      // <xs:complexType> specifies the content model.
      generate_complexType(*it->complexTypes_.begin(), it->name_);
    }
  }
}


void generator::generate_global_complexTypes(const ComplexTypeList &cel) {
  for (ComplexTypeList::const_iterator it = cel.begin(); it != cel.end(); ++it) 
    generate_complexType(*it, it->name_);
}


void generator::generate_complexType(const xs::ComplexType &ct, const aka::qname &qname) {

  assert(!qname.empty());
  
  typedef std::vector<xs::Sequence> seqlist_t;
  typedef std::vector<xs::Choice> cholist_t;

  seqlist_t seqlist;
  cholist_t cholist;
  AttributeList attrlist;

  ParticlesLeaf::const_moc ctmoc(ct.particles_);

  ctmoc.find_elements(seqlist, "xs:sequence");
  ctmoc.find_elements(cholist, "xs:choice");
  ctmoc.find_elements(attrlist, "xs:attribute");
  
  if (seqlist.size() == 1)
    generate_sequence(*seqlist.begin(), qname, attrlist);
  else if (cholist.size() == 1)
    generate_choice(*cholist.begin(), qname, attrlist);
  else
    throw fatal_error(__FILE__, __LINE__, "Number of particles must be 1 under xs:complexType.");
}

const aka::qname generator::generate_array(const aka::qname &itemtype) {
  aka::qname array_name = itemtype.qualified() + "[]";
  if (find_contents(array_name) != 0)
    return array_name;

  array_class *cls = new array_class(array_name);
  if (!builtinTypes_.exists(itemtype))
    arrays_.insert(array_name, cppname(itemtype.qualified() + "s_type"));

  cls->item_type_ = itemtype;
  contents_.push_back(cls);
  return array_name;
}

void generator::generate_sequence(const xs::Sequence &seq, const aka::qname &name, 
				  const AttributeList &attrs) {
  
  // Create class_contents. 
  sequence_class *cls = new sequence_class(name);
  contents_.push_back(cls);

  for (xs::Elements::const_iterator it = seq.elements_.begin(); it != seq.elements_.end(); ++it) {
    
    /** name and type specifies the content model  */
    /* <xs:element name="hoge" type="hogetype"/> */
    if (!it->name_.empty() && !it->type_.empty()) {
      if (!it->complexTypes_.empty() || !it->ref_.empty()) // complxType and ref should be empty.
	throw fatal_error(__FILE__, __LINE__, "");

      aka::occurrence occ(it->minOccurs_, it->maxOccurs_);
      aka::qname name = it->name_;
      aka::qname type = it->type_;
      if (xs::is_array(occ)) {
	type = generate_array(type);
	name = name.qualified();
      }
      if (!cls->push_member(name, type, occ)) {
	std::ostringstream ostm;
	ostm << "name of type (" << type << ") not found.";
	throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
      }
    }
    else if (!it->ref_.empty()) { // <xs:element ref="toplevel_element">
      if (!it->complexTypes_.empty()) // complexType should not appear.
	throw fatal_error(__FILE__, __LINE__, 
			  "Both of xs:complexType and xs:element@ref attributes "
			  "are specified.  Specify one of them."); 
      
      if (!elements_.exists(it->ref_)) {
	std::ostringstream ostm;
	ostm << "Type of sequence member(" << it->ref_ << ") is not found.";
	throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
      }

      aka::occurrence occ(it->minOccurs_, it->maxOccurs_);
      aka::qname name = it->ref_;
      aka::qname type = elements_.resolve(it->ref_);
      if (xs::is_array(occ)) {
	type = generate_array(type);
	name = name.qualified();
      }
      if (!cls->push_member(name, type, occ)) {
	std::ostringstream ostm;
	ostm << "Type of sequence member(" << it->ref_ << ") is not found.";
	throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
      }
    }
    else {
      if (it->complexTypes_.empty())       // Anonymous complexType must specify the content model.
	throw fatal_error(__FILE__, __LINE__, "ComplexType declaration is invalid."); 
      generate_complexType(*it->complexTypes_.begin(), it->name_);
    }
  }
  generate_attributes(attrs, cls);
}

void generator::generate_choice(const xs::Choice &cho, const aka::qname &name, 
				const AttributeList &attrs) {

  // Create class_contents. 
  choice_class *cls = new choice_class(name, aka::occurrence(cho.minOccurs_, cho.maxOccurs_));
  contents_.push_back(cls);
  
  for (xs::Elements::const_iterator it = cho.elements_.begin(); 
       it != cho.elements_.end(); ++it) {

    /** name and type specifies the content model  */
    /* <xs:element name="hoge" type="hogetype"/> */
    if (!it->name_.empty() && !it->type_.empty()) {
      if (!it->complexTypes_.empty() || !it->ref_.empty()) 
	// complexType or ref specifies the model.
        // Both of them should not be empty at the same time. 
	throw fatal_error(__FILE__, __LINE__, "Neither complexType nor ref not specified");

      // complexType and simpleType is acceptable.

      aka::occurrence occ(it->minOccurs_, it->maxOccurs_);
      aka::qname name = it->name_;
      aka::qname type = it->type_;
      if (xs::is_array(occ)) {
	type = generate_array(type);
	name = name.qualified();
      }
      if (!cls->push_item(name, type, occ)) {
	std::ostringstream ostm;
	ostm << "name of type (" << it->type_ << ") not found.";
	throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
      }
    }
    else if (!it->ref_.empty()) { // <xs:element ref="toplevel_element">
      if (!it->complexTypes_.empty()) // complexType should not appear.
	throw fatal_error(__FILE__, __LINE__, 
			  "Both xs:complexType and xs:element@ref attributes are specified."); 

      aka::occurrence occ(it->minOccurs_, it->maxOccurs_);
      aka::qname name = it->name_;
      aka::qname type = elements_.resolve(it->ref_);
      if (xs::is_array(occ)) {
	type = generate_array(type);
	name = name.qualified();
      }
      if (!cls->push_item(name, type, occ)) {
	std::ostringstream ostm;
	ostm << "Type of sequence member(" << it->ref_ << ") is not found.";
	throw fatal_error(__FILE__, __LINE__, ostm.rdbuf()->str().c_str());
      }
    }
    else {
      if (it->complexTypes_.empty())       // Anonymous complexType must specify the content model.
	throw fatal_error(__FILE__, __LINE__, "ComplexType declaration is invalid."); 
      generate_complexType(*it->complexTypes_.begin(), it->name_);
    }
  }
}

