/* -*- C++ -*-
 * src/lemon/graph_writer.h - Part of LEMON, a generic C++ optimization library
 *
 * Copyright (C) 2005 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 {

  /// \brief Standard WriterTraits for the GraphWriter class.
  ///
  /// Standard WriterTraits for the GraphWriter class.
  /// It defines standard writing method for all type of value. 
  struct DefaultWriterTraits {

    /// \brief Template class for writing an value.
    ///
    /// Template class for writing an value.
    template <typename _Value>
    struct Writer {
      /// The value type.
      typedef _Value Value;

      /// \brief Writes a value to the given stream.
      ///
      /// Writes a value to the given stream.
      void write(std::ostream& os, const Value& value) {
	os << value << '\t';
      }
    };

  };


  /// \brief Writer class for quoted strings.
  ///
  /// Writer class for quoted strings. It can process the escape
  /// sequences in the string.
  class QuotedStringWriter {
  public:
    typedef std::string Value;

    /// \brief Constructor for the writer.
    ///
    /// Constructor for the writer. If the given parameter is true
    /// the writer creates escape sequences from special characters.
    QuotedStringWriter(bool _escaped = true) : escaped(_escaped) {}

    /// \brief Writes a quoted string to the given stream.
    ///
    /// Writes a quoted string to the given stream.
    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;
  };

  
  /// \brief The graph writer class.
  ///
  /// The writer class for the graph output.
  /// \see \ref GraphReader
  /// \see \ref graph-io-page
  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;
 
    /// \brief Construct a new GraphWriter.
    ///
    /// Construct a new GraphWriter. It writes from the given map,
    /// it constructs the given map and it use the given writer as the
    /// default skipper.
    GraphWriter(std::ostream& _os, Graph& _graph) : os(_os), graph(_graph) {}


    /// \brief Destruct the graph writer.
    ///
    /// Destruct the graph writer.
    ~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

    /// \brief Add a new node map writer command for the writer.
    ///
    /// Add a new node map writer command for the writer.
    template <typename Map>
    GraphWriter& addNodeMap(std::string name, const Map& map) {
      return addNodeMap<typename WriterTraits::template Writer<
	typename Map::Value>, Map>(name, map);
    }

    /// \brief Add a new node map writer command for the writer.
    ///
    /// Add a new node map writer command for the writer.
    template <typename Writer, typename Map>
    GraphWriter& addNodeMap(std::string name, const Map& map, 
			      const Writer& writer = Writer()) {
      node_map_writers.push_back(
        make_pair(name, new MapWriter<Node, Map, Writer>(map, writer)));
      return *this;
    }

    // Edge map rules

    /// \brief Add a new edge map writer command for the writer.
    ///
    /// Add a new edge map writer command for the writer.
    template <typename Map>
    GraphWriter& addEdgeMap(std::string name, const Map& map) { 
      return addEdgeMap<typename WriterTraits::template Writer<
        typename Map::Value>, Map>(name, map);
    }


    /// \brief Add a new edge map writer command for the writer.
    ///
    /// Add a new edge map writer command for the writer.
    template <typename Writer, typename Map>
    GraphWriter& addEdgeMap(std::string name, 
			    const Map& map, const Writer& writer = Writer()) {
      edge_map_writers.push_back(make_pair(name, 
	new MapWriter<Edge, Map, Writer>(map, writer)));
      return *this;
    }

    /// \brief Add a new labeled node writer for the writer.
    ///
    /// Add a new labeled node writer for the writer.
    GraphWriter& addNode(std::string name, const Node& node) {
      node_writers.push_back(make_pair(name, node));
      return *this;
    }

    /// \brief Add a new labeled edge writer for the writer.
    ///
    /// Add a new labeled edge writer for the writer.
    GraphWriter& addEdge(std::string name, const Edge& edge) {
      edge_writers.push_back(make_pair(name, edge));
      return *this;
    }

    /// \brief Executes the writer commands.
    ///
    /// Executes the writer commands.
    void run() {   
      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) {
	//	ErrorMessage message;
	//	message << "Missing node id map";
	//	throw IOLogicError(message);
      }
      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;

  };


}
