/* -*- C++ -*- */

#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <lemon/error.h>
#include <lemon/xy.h>

namespace lemon {

  class XmlWriter 
  {
    std::ostream& os;
    int level;

  protected:
    void indent(int level) {
      os << std::endl;
      for(int i=0;i<level;i++) os << ' ';
    }
    void tag(const std::string &_tag) {
      os << '<' << _tag << '>';
    }
    void etag(const std::string &_tag) {
      os << "</" << _tag << '>';
    }
    void itag(const std::string &_tag) { indent();tag(_tag); }
    void ietag(const std::string &_tag) { indent();etag(_tag); }

    void beginTag(const std::string &_tag) {
      itag(_tag);
      level++;
    }
    void endTag(const std::string &_tag) {
      level--;
      ietag(_tag);
    }

  public:

    void indent() 
    {
      if(level>=0) indent(level);
      else level=0;
    }
  
    ///\e
  
    ///\e
    ///
    class ContTag
    {
      XmlWriter &ix;
      const std::string _tag;
    public:
      ///\e
  
      ///\e
      ///
      ContTag(XmlWriter &_ix,const std::string &_t) :
	ix(_ix), _tag(_t)
      {
	ix.tag(_tag);
      }
      ~ContTag() { ix.etag(_tag);}
    };

    class LineTag
    {
      XmlWriter &ix;
      const std::string _tag;
    public:
      ///\e
    
      ///\e
      ///
      LineTag(XmlWriter &_ix,const std::string &_t) :
	ix(_ix), _tag(_t)
      {
	ix.itag(_tag);
      }
      ~LineTag() { ix.etag(_tag);}
    };

    ///\e
  
    ///\e
    ///
    class Tag
    {
      XmlWriter &ix;
      const std::string _tag;
    public:
      ///\e
  
      ///\e
      ///
      Tag(XmlWriter &_ix,const std::string &_t) :
	ix(_ix), _tag(_t)
      {
	ix.beginTag(_tag);
      }
      ~Tag() { 
	ix.endTag(_tag);
      }
    };
      
    ///\e
  
    ///\e
    ///
    XmlWriter(std::ostream& _os) : os(_os),level(-1) {}
    ~XmlWriter() { os<< std::endl; }
  
    XmlWriter &operator()(int v) 
    { 
      os << v;
      return *this;
    }
    XmlWriter &operator()(const std::string &_tag,int v) 
    { 
      LineTag t(*this,_tag);
      (*this)(v);
      return *this;
    }
    XmlWriter &operator()(double v) 
    {
      os << v;
      return *this;
    }
    XmlWriter &operator()(const std::string &_tag,double v) 
    {
      LineTag t(*this,_tag);
      (*this)(v);
      return *this;
    }
    XmlWriter &operator()(const std::string &v)
    {
      for(std::string::const_iterator i=v.begin();i!=v.end();++i)
	switch(*i) {
	case '\\':
	  os << "\\\\";
	  break;
	case '<':
	  os << "\\<";
	  break;
	case '&':
	  os << "\\&";
	  break;
	case '\n':
	  os << "\\n";
	  break;
	default:
	  os<<*i;
	  break;
	}
      return *this;
    }
    XmlWriter &operator()(const std::string &_tag,const std::string &v)
    {
      LineTag t(*this,_tag);
      (*this)(v);
      return *this;
    }
    ///\e
  
    ///\e
    ///
    template<class V>
    XmlWriter &operator()(const std::string &_tag,const V &v)
    {
      Tag t(*this,_tag);
      out(*this,v);
      return *this;
    }
    ///\e
  
    ///\e
    ///
    template<class V>
    XmlWriter &operator()(const V &v)
    {
      out(*this,v);
      return *this;
    }
  };

  //////////////////////////////////////////////////////////////////////

  class XmlReader 
  {
    std::istream& is;
    std::string next_tag;
    int line_number;

    void skipWhiteSpaces()
    {
      char c;
      while (is.get(c) && std::isspace(c,is.getloc())) if(c=='\n') line_number++;
      is.unget();
    }
  protected:
    ///\e
  
    ///\e
    ///
    void useTag() {next_tag.clear();}
  
    void useTag(const std::string &_tag) {
      if(nextTag()==_tag) useTag();
      else throw DataFormatError("",line_number,"Unexpected token name");
    }
  public:
    ///\e
  
    ///\e
    ///
    const std::string &nextTag() 
    {
      if(next_tag.empty()) {
	char c;
	skipWhiteSpaces();
	if(!is.get(c) || c!='<')
	  throw DataFormatError("",line_number,"Bad token");
	next_tag.clear();
	while (is.get(c) && c!='>') next_tag.push_back(c);
	if(c!='>')
	  throw DataFormatError("",line_number,"Bad token");
      }
      return next_tag;
    }
  
    ///\e
  
    ///\e
    ///
    class Tag
    {
      XmlReader &ix;
      const std::string tag;
    public:
      ///\e
    
      ///\e
      ///
      Tag(XmlReader &_ix,const std::string &_tag) :
	ix(_ix), tag(_tag)
      {
	ix.useTag(_tag);
      }
      ~Tag() {
	if(!std::uncaught_exception()) 
	  ix.useTag('/'+tag);
      }
    };

    ///\e
  
    ///\e
    ///
    XmlReader(std::istream& _is) : is(_is), line_number(1) {}
  
    int operator()(const std::string &tag,int &v)
    { 
      Tag t(*this,tag);
      skipWhiteSpaces();
      if(!(is >> v)) throw DataFormatError("",line_number,"Not an 'int'");
      return v;
    }
    double operator()(const std::string &tag,double &v) 
    {
      Tag t(*this,tag);
      skipWhiteSpaces();
      if(!(is >> v))
	throw DataFormatError("",line_number,"Not a 'double'");
      return v;
    }
    std::string &operator()(const std::string &tag,std::string &v)
    {
      Tag t(*this,tag);
      v.clear();
      char c;
      while (is.get(c) && c!='<')
	if(c=='\\')
	  if(!is.get(c))
	    throw DataFormatError("",line_number,"Bad string");
	  else switch(c) {
	  case 'n':
	    v.push_back('\n');
	    break;
	  default:
	    v.push_back(c);
	    break;
	  }
	else {
	  if(c=='\n') line_number++;
	  v.push_back(c);
	}
      if(c!='<')
	throw DataFormatError("",line_number,"Unexpected eof");
      is.unget();
      return v;
    }
    ///\e
  
    ///\e
    ///
    template<class V>
    V &operator()(const std::string &tag,V &v)
    {
      Tag t(*this,tag);
      in(*this,v);
      return v;
    }
    ///\e
  
    ///\e
    ///
    template<class V>
    V &operator()(V &v)
    {
      in(*this,v);
      return v;
    }
    ///\e
  
    ///\e
    ///
    template<class V>
    V load(const std::string &tag)
    {
      Tag t(*this,tag);
      V v;
      (*this)(tag,v);
      return v;
    }
  };

  //////////////////////////////////////////////////////////////////////

  template<class A>
  void out(XmlWriter &x,const std::auto_ptr<A> &v)
  {
    x(*v);
  }

  template<class A>
  void in(XmlReader &x,std::auto_ptr<A> &v)
  {
    v=new A;
    x(*v);
  }

  //////////////////////////////

  template<class A,class B>
  void out(XmlWriter &x,const std::pair<A,B> &v)
  {
    x("first",v.first);
    x("second",v.second);
  }

  template<class A,class B>
  void in(XmlReader &x,std::pair<A,B> &v)
  {
    x("first",v.first);
    x("second",v.second);
  }

  //////////////////////////////

  template<class T>
  void out(XmlWriter &x,const std::list<T> &v)
  {
    for(typename std::list<T>::const_iterator it=v.begin();
	it!=v.end();++it) x("item",*it);
  }

  template<class T>
  void in(XmlReader &x,std::list<T> &v)
  {
    while(x.nextTag()=="item")
      {
	v.push_back(T());
	x("item",v.back());
      }
  }

  //////////////////////////////

  template<class T>
  void out(XmlWriter &x,const std::vector<T> &v)
  {
    for(typename std::vector<T>::const_iterator it=v.begin();
	it!=v.end();++it) x("item",*it);
  }

  template<class T>
  void in(XmlReader &x,std::vector<T> &v)
  {
    while(x.nextTag()=="item")
      {
	v.push_back(T());
	x("item",v.back());      
      }
  }

  //////////////////////////////

  template<class K,class V>
  void out(XmlWriter &x,const std::map<K,V> &v)
  {
    for(typename std::map<K,V>::const_iterator it=v.begin();
	it!=v.end();++it) x("item",*it);
  }

  template<class K,class V>
  void in(XmlReader &x,std::map<K,V> &v)
  {
    while(x.nextTag()=="item")
      {
	typename std::map<K,V>::value_type it;
	x("item",it);
	v.insert(it);
      }
  }

  //////////////////////////////

  template<class T>
  void out(XmlWriter &x,const lemon::xy<T> &v)
  {
    //   x("x",v.x);
    //   x("y",v.y);
    { XmlWriter::LineTag t(x,"x"); x(v.x); }
    { XmlWriter::ContTag t(x,"y"); x(v.y); }
  }

  template<class T>
  void in(XmlReader &x,lemon::xy<T> &v)
  {
    x("x",v.x);
    x("y",v.y);
  }

  //////////////////////////////

  template<class T>
  void out(XmlWriter &x,const lemon::BoundingBox<T> &v)
  {
    if(!v.empty()) {
      x("point",v.bottomLeft());
      if(v.bottomLeft()!=v.topRight()) x("point",v.topRight());
    }
  }

  template<class T>
  void in(XmlReader &x,lemon::BoundingBox<T> &v)
  {
    v.clear();
    while(x.nextTag()=="point") {
      lemon::xy<T> co;
      x("point",co);
      v.add(co);
    }
  }
  
}
