/* -*- C++ -*-
 * src/lemon/graph_writer.h - Part of LEMON, a generic C++ optimization library
 *
 * Copyright (C) 2004 Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
 * (Egervary Combinatorial Optimization Research Group, EGRES).
 *
 * Permission to use, modify and distribute this software is granted
 * provided that this copyright notice appears in all copies. For
 * precise terms see the accompanying LICENSE file.
 *
 * This software is provided "AS IS" with no warranty of any kind,
 * express or implied, and with no claim as to its suitability for any
 * purpose.
 *
 */

///\ingroup gio
///\file
///\brief Graph writer.


#include <iostream>
#include <sstream>

#include <map>
#include <vector>

#include <memory>

#include <lemon/invalid.h>
#include <lemon/error.h>


namespace lemon {

  struct DefaultWriterTraits {

    template <typename _Value>
    struct Writer {
      typedef _Value Value;

      void write(std::ostream& os, const Value& value) {
	os << value << '\t';
      }
    };

  };


  class QuotedStringWriter {
  public:
    typedef std::string Value;

    QuotedStringWriter(bool _escaped = true) : escaped(_escaped) {}

    void write(std::ostream& os, const std::string& value) {
      os << "\"";
      if (escaped) {
	ostringstream ls;
	for (int i = 0; i < (int)value.size(); ++i) {
	  writeEscape(ls, value[i]);
	}
	os << ls.str();
      } else {
	os << value;
      }
      os << "\"";
    }

  private:
    
    static void writeEscape(std::ostream& os, char c) {
      switch (c) {
      case '\\':
	os << "\\\\";
	return;
      case '\"':
	os << "\\\"";
	return;
      case '\'':
	os << "\\\'";
	return;
      case '\?':
	os << "\\\?";
	return;
      case '\a':
	os << "\\a";
	return;
      case '\b':
	os << "\\b";
	return;
      case '\f':
	os << "\\f";
	return;
      case '\r':
	os << "\\r";
	return;
      case '\n':
	os << "\\n";
	return;
      case '\t':
	os << "\\t";
	return;
      case '\v':
	os << "\\v";
	return;
      default:
	if (c < 0x20) {
	  os << '\\' << oct << (int)c;
	} else {
	  os << c;
	}
	return;
      }     
    }
  private:
    bool escaped;
  };

  // Graph writer
  
  template <typename _Graph, typename _WriterTraits = DefaultWriterTraits> 
  class GraphWriter {
  public:
    
    typedef _Graph Graph;
    typedef typename Graph::Node Node;
    typedef typename Graph::NodeIt NodeIt;
    typedef typename Graph::Edge Edge;
    typedef typename Graph::EdgeIt EdgeIt;

    typedef _WriterTraits WriterTraits;

    GraphWriter(std::ostream& _os, Graph& _graph) : os(_os), graph(_graph) {}


    ~GraphWriter() {
      for (typename NodeMapWriters::iterator it = node_map_writers.begin(); 
	   it != node_map_writers.end(); ++it) {
	delete it->second;
      }

      for (typename EdgeMapWriters::iterator it = edge_map_writers.begin();
	   it != edge_map_writers.end(); ++it) {
	delete it->second;
      }

    }

    // Node map rules

    template <typename Map>
    GraphWriter& addNodeMap(std::string name, const Map& map) {
      return addNodeMap<typename WriterTraits::template Writer<
	typename Map::Value>, Map>(name, map);
    }

    template <typename Writer, typename Map>
    GraphWriter& addNodeMap(std::string name, const Map& map, 
			      const Writer& writer = Writer()) {
      //      if (node_map_writers.find(name) != node_map_writers.end()) {
      //	throw Exception() << "Multiple write rule for node map: " 
      //        << name;
      //      }
      node_map_writers.push_back(
        make_pair(name, new MapWriter<Node, Map, Writer>(map, writer)));
      return *this;
    }

    // Edge map rules

    template <typename Map>
    GraphWriter& addEdgeMap(std::string name, const Map& map) { 
      return addEdgeMap<typename WriterTraits::template Writer<typename Map::Value>, Map>(name, map);
    }


    template <typename Writer, typename Map>
    GraphWriter& addEdgeMap(std::string name, const Map& map, const Writer& writer = Writer()) {
      //      if (edge_map_writers.find(name) != edge_map_writers.end()) {
      //	throw Exception() << "Multiple write rule for edge map: " << name;
      //      }
      edge_map_writers.push_back(make_pair(name, new MapWriter<Edge, Map, Writer>(map, writer)));
      return *this;
    }

    // Node rules
    GraphWriter& addNode(std::string name, const Node& node) {
      //      if (node_writers.find(name) != node_writers.end()) {
      //	throw Exception() << "Multiple write rule for node";
      //      }
      node_writers.push_back(make_pair(name, node));
      return *this;
    }

    // Edge rules

    GraphWriter& addEdge(std::string name, const Edge& edge) {
      //      if (edge_writers.find(name) != edge_writers.end()) {
      //	throw Exception() << "Multiple write rule for edge";
      //      }
      edge_writers.push_back(make_pair(name, edge));
      return *this;
    }

    void write() {   
      writeNodeSet();
      writeEdgeSet();
      writeNodes();
      writeEdges();
      os << "@end" << std::endl;
    }

  private:

    void writeNodeSet() {
      if (node_map_writers.size() == 0) return;
      os << "@nodeset" << std::endl;
      for (int i = 0; i < (int)node_map_writers.size(); ++i) {
	os << node_map_writers[i].first << '\t';
      } 
      os << std::endl;
      for (NodeIt it(graph); it != INVALID; ++it) {
	for (int i = 0; i < (int)node_map_writers.size(); ++i) {
	  node_map_writers[i].second->write(os, it);
	}
	os << std::endl;
      }

    }

    void writeEdgeSet() {
      if (edge_map_writers.size() == 0) return;
      if (node_map_writers.size() == 0) {
	throw Exception() << "Missing node id map";
      }
      os << "@edgeset" << std::endl;
      os << "\t\t";
      for (int i = 0; i < (int)edge_map_writers.size(); ++i) {
	os << edge_map_writers[i].first << '\t';
      } 
      os << std::endl;
      for (EdgeIt it(graph); it != INVALID; ++it) {
	node_map_writers[0].second->write(os, graph.source(it));
	node_map_writers[0].second->write(os, graph.target(it));
	for (int i = 0; i < (int)edge_map_writers.size(); ++i) {
	  edge_map_writers[i].second->write(os, it);
	}
	os << std::endl;
      }
    }

    void writeNodes() {
      if (node_writers.size() == 0) return;
      if (node_map_writers.size() == 0) {
	throw Exception() << "Missing node id map";
      }
      os << "@nodes" << std::endl;
      for (int i = 0; i < (int)node_writers.size(); ++i) {
	os << node_writers[i].first << '\t';
	node_map_writers[0].second->write(os, node_writers[i].second);
	os << std::endl;
      } 
    }

    void writeEdges() {
      if (edge_writers.size() == 0) return;
      if (edge_map_writers.size() == 0) {
	throw Exception() << "Missing edge id map";
      }
      os << "@edges" << std::endl;
      for (int i = 0; i < (int)edge_writers.size(); ++i) {
	os << edge_writers[i].first << '\t';
	edge_map_writers[0].second->write(os, edge_writers[i].second);
	os << std::endl;
      } 
    }
    
    // Writers

    template <class _Item>
    class WriterBase {
    public:
      typedef _Item Item;
      virtual void write(std::ostream&, const Item&) = 0;
    };

    template <class _Item, typename _Map, typename _Writer>
    class MapWriter : public WriterBase<_Item> {
    public:
      typedef _Map Map;
      typedef _Writer Writer;
      typedef typename Writer::Value Value;
      typedef _Item Item;
      
      const Map& map;
      Writer writer;

      MapWriter(const Map& _map, const Writer& _writer) 
	: map(_map), writer(_writer) {}


      virtual void write(std::ostream& os, const Item& item) {
	writer.write(os, map[item]);
      }

    };



    typedef std::vector< std::pair<std::string, WriterBase<Node>*> > 
      NodeMapWriters;
    NodeMapWriters node_map_writers;

    typedef std::vector< std::pair<std::string, WriterBase<Edge>*> > 
      EdgeMapWriters;
    EdgeMapWriters edge_map_writers;

    typedef std::vector<std::pair<std::string, Node> > NodeWriters;
    NodeWriters node_writers;

    typedef std::vector<std::pair<std::string, Edge> > EdgeWriters;
    EdgeWriters edge_writers;

    std::ostream& os;
    Graph& graph;

  };


}
