//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef INSERT_HPP
#define INSERT_HPP
/**
 * \file Insert.hpp
 *
 * Defines the algorithms::Insert class template
 */
#include <Config.hpp>

#include <Information.hpp>
#include <StaticAssert.hpp>
#include <VVGraph.hpp>

#include <utility>

namespace mgx 
{
  /**
   * \class Insert Insert.hpp <Insert.hpp>
   * \brief Insert a new vertex on an edge.
   * \param a an existing vertex
   * \param b an exsisting vertex
   * \param S the vv graph to edit
   *
   * This function creates a new vertex and inserts it between two existing
   * vertices by replacing it into the existing neighborhoods.  If the vertices
   * have no relation or an assymmetric relation, a warning is printed to stderr
   * and an empty vertex is returned.
   *
   * Example:
   * \code
   * vvGraph S;
   * vertex v1, v2;
   * S.insert(v1);
   * S.insert(v2);
   * S.insertEdge(v1,v2);
   * S.insertEdge(v2,v1);
   * Insert<vvGraph> insert;
   * vertex v = insert(v1, v2, S);
   * \endcode
   *
   * The pre-condition is that edges (v1,v2) and (v2,v1) exist.
   *
   * The post-condition is that v replaced v2 in the neighborhood of v1 and v1
   * in the neighborhood of v2, keeping the double connections.
   */
  template <class vvGraph> class Insert {
  public:
    typedef typename vvGraph::vertex_t vertex;
  
    vertex operator()(const vertex& a, const vertex& b, vvGraph& S) const
    {
      if(a.isNull() || b.isNull()) {
        return vertex(0);
      }
  
      unsigned int check = 0;
      if(S.edge(a, b))
        check++;
      if(S.edge(b, a))
        check++;
  
      switch(check) {
      case 0:
        Information::err << "Warning: Attempt to insert a vertex between vertices that have no relation." << endl;
        return vertex(0);
        break;
      case 1:
        Information::err << "Warning: Attempt to insert a vertex between vertices that have an assymetric relation."
                         << endl;
        return vertex(0);
        break;
      default:
        break;
      }
  
      vertex x;
      S.insert(x);
  
      S.replace(a, b, x);
      S.replace(b, a, x);
  
      S.insertEdge(x, a);
      S.insertEdge(x, b);
  
      return x;
    }
  };
  
  template <typename VertexContent, typename EdgeContent>
  const typename VVGraph<VertexContent, EdgeContent>::vertex_t&
  insert(const typename VVGraph<VertexContent, EdgeContent>::vertex_t& a,
         const typename VVGraph<VertexContent, EdgeContent>::vertex_t& b,
         VVGraph<VertexContent, EdgeContent>&S)
  {
    typedef Insert<VVGraph<VertexContent, EdgeContent> > Insert;
    static const Insert fct = Insert();
    return fct(a, b, S);
  }
  
  /**
   * Splice \c nv after \c ref in \c v if ref is not null. Otherwise just
   * insert the edge (v,nv) in S.
   */
  template <class Graph>
  typename Graph::edge_t insertAfter(const typename Graph::vertex_t& v, const typename Graph::vertex_t& ref,
                                     const typename Graph::vertex_t& nv, Graph& S)
  {
    if(ref)
      return S.spliceAfter(v, ref, nv);
    return S.insertEdge(v, nv);
  }
  
  /**
   * Splice \c nv before \c ref in \c v if ref is not null. Otherwise just
   * insert the edge (v,nv) in S.
   */
  template <class Graph>
  typename Graph::edge_t insertBefore(const typename Graph::vertex_t& v, const typename Graph::vertex_t& ref,
                                      const typename Graph::vertex_t& nv, Graph& S)
  {
    if(ref)
      return S.spliceBefore(v, ref, nv);
    return S.insertEdge(v, nv);
  }
}
#endif
