//
// 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 VERTEX_HPP
#define VERTEX_HPP

/**
 * \file Vertex.hpp
 *
 * This file contain the definition of the Vertex class.
 * For now, the Vertex class can only be used with the VVGraph
 * class.
 */

#include <Config.hpp>

#include <Mangling.hpp>

#include <iostream>
#include <map>
#include <memory>
#include <stdint.h>
#include <typeinfo>
#include <utility>

#include <Common.hpp>

namespace mgx 
{
  /**
   * Type of the identifier of a vertex
   */
  typedef intptr_t vertex_identity_t;
  
  /**
   * Number used to enumerate the vertices of all types.
   */
  extern mgx_EXPORT size_t vertex_counter;
  
  /**
   * \class Vertex vertex.h <Vertex.hpp>
   * Vertex of a vv graph.
   *
   * The vertexes handle their associated data using a reference counting scheme.
   * As such, they can be used as smart pointers. They are also comparable
   * (<,>,==,!=), which allow for use in any sorted structure and hashable for
   * use in any hash table-based structure.
   *
   * They also all have a unique identifier. 
   */
  template <typename VertexContent> class Vertex 
  {
  
  protected:
    /**
     * Type of the reference counted content
     */
    struct CountedContent : public VertexContent {
      CountedContent() : VertexContent(), num(vertex_counter++)
      {
        count = 1u;       // To be used with tbb::atomic
      }
  
      CountedContent(const CountedContent& copy)
        : VertexContent(copy), count(copy.count), num(vertex_counter++) {}
  
      CountedContent(const VertexContent& copy)
        : VertexContent(copy), num(vertex_counter++)
      {
        count = 1u;       // To be used with tbb::atomic
      }
  
  public:
      /**
       * Reference count on the vertex
       */
      unsigned int count;
  
      size_t num;
    };
  
  public:
    /**
     * Type of the identifier of the vertex
     */
    typedef vertex_identity_t identity_t;
  
    /**
     * Type of the content of the vertex
     */
    typedef VertexContent content_t;
  
    /**
     * Type of the equivalent pointer
     */
    typedef VertexContent* pointer;
  
    /**
     * Creates a new vertex with a new content.
     *
     * Example:
     * \code
     *   struct VertexContent { int a; }
     *   // ...
     *   Vertex<VertexContent> v;
     *   v->a = 10;
     * \endcode
     */
    Vertex();
  
    /**
     * Creates a reference on the vertex of identifier \c id. If \c id
     * is 0, creates a null vertex.
     *
     * \param[in] id Label of the vertex to retrieve.
     *
     * \warning This function is very unsafe if used with anything but 0 as
     * an identifier.
     *
     * Example:
     * \code
     *   typedef Vertex<VertexContent> vertex;
     *   vertex v;
     *   vertex::identity_t i = v.id();
     *   vertex v1(i);
     *   assert(v1 == v);
     * \endcode
     */
    explicit Vertex(identity_t id); // bad idea to allow implicit conversion
  
    /**
     * Copy a vertex.
     *
     * The data is not copied.
     */
    Vertex(const Vertex& copy);
  
    /**
     * Desctructor
     */
    ~Vertex();
  
    /**
     * Access to the data.
     *
     * \warning Do not try to access the data of the null vertex.
     */
    VertexContent* operator->() const 
    {
      return content;
    }
    /**
     * Access to the data.
     *
     * \warning Do not try to access the data of the null vertex.
     */
    VertexContent& operator*() const {
      return *content;
    }
  
    /**
     * Change the vertex held by the current object.
     *
     * The data is never modified by this operation. If you wish to copy the data
     * of a vertex v1 into a vertex v2 use:
     * \code
     * *v2 = *v1;
     * \endcode
     */
    Vertex& operator=(const Vertex& other);
  
    Vertex& operator=(const identity_t& id);
  
    Vertex& operator=(const VertexContent* value);
  
    /**
     * Comparison operators.
     *
     * \note the comparisons work on the identifiers, not the contents!
     */
    bool operator==(const Vertex& other) const {
      return id() == other.id();
    }
    /**
     * Comparison operators.
     *
     * \note the comparisons work on the identifiers, not the contents!
     */
    bool operator!=(const Vertex& other) const {
      return id() != other.id();
    }
    /**
     * Comparison operators.
     *
     * \note the comparisons work on the identifiers, not the contents!
     */
    bool operator>(const Vertex& other) const {
      return id() > other.id();
    }
    /**
     * Comparison operators.
     *
     * \note the comparisons work on the identifiers, not the contents!
     */
    bool operator<(const Vertex& other) const {
      return id() < other.id();
    }
    /**
     * Comparison operators.
     *
     * \note the comparisons work on the identifiers, not the contents!
     */
    bool operator>=(const Vertex& other) const {
      return id() >= other.id();
    }
    /**
     * Comparison operators.
     *
     * \note the comparisons work on the identifiers, not the contents!
     */
    bool operator<=(const Vertex& other) const {
      return id() <= other.id();
    }
  
    /**
     * Test if a vertex is a null vertex.
     */
    bool isNull() const {
      return content == 0;
    }
  
    /**
     * Return the identifier of a vertex.
     */
    identity_t id() const {
      return (identity_t)content;
    }
  
    /**
     * Return a number unique to each vertex, globally.
     */
    size_t num() const {
      return content->num;
    }
  
    /**
     * Convert a vertex to \c true if it is not null.
     */
    operator bool() const { return content; }
  
    static Vertex null;
  
    unsigned int count() const
    {
      if(content)
        return content->count;
      return 0;
    }
  
  protected:
    /**
     * Content of the vertex
     *
     * This member is mutable to allow for modification of constant
     * references. This is useful as no operation on the vertex depend on
     * this.
     */
    mutable CountedContent* content;
  
    /**
     * Release the current pointer
     */
    void release();
  
    /**
     * Acquire the current pointer
     */
    void acquire();
  };
  
  template <typename VertexContent> Vertex<VertexContent> Vertex<VertexContent>::null(0);
  
  template <typename VertexContent> Vertex<VertexContent>::~Vertex() 
  {
    this->release();
  }
  
  template <typename VertexContent>
  Vertex<VertexContent>::Vertex() : content(0)
  {
    content = new CountedContent();
  }
  
  template <typename VertexContent>
  Vertex<VertexContent>::Vertex(identity_t id)
    : content(reinterpret_cast<CountedContent*>(id))
  {
    acquire();
  }
  
  template <typename VertexContent>
  Vertex<VertexContent>::Vertex(const Vertex& copy)
    : content(copy.content)
  {
    acquire();
  }
  
  template <typename VertexContent>
  Vertex<VertexContent>& Vertex<VertexContent>::operator=(const identity_t& id)
  {
    if((identity_t)content == id)
      return *this;
    this->release();
    content = reinterpret_cast<CountedContent*>(id);
    acquire();
    return *this;
  }
  
  template <typename VertexContent>
  Vertex<VertexContent>& Vertex<VertexContent>::operator=(const VertexContent* id)
  {
    const CountedContent* cid = dynamic_cast<const CountedContent*>(id);
    if(cid != 0)
      return *this = (const identity_t&)cid;
    else {
      return *this = (const identity_t&)(*(new CountedContent(*id)));
    }
  }
  
  template <typename VertexContent> void Vertex<VertexContent>::acquire()
  {
    if(content) {
      if(content->count == 0)
        content = 0;
      else
        #pragma omp atomic
        ++(content->count);
    }
  }
  
  template <typename VertexContent> void Vertex<VertexContent>::release()
  {
    if(content) {
      #pragma omp atomic
      --(content->count);
      if(content->count == 0) {
        delete content;
      }
    }
  }
  
  template <typename VertexContent>
  Vertex<VertexContent>& Vertex<VertexContent>::operator=(const Vertex& other)
  {
    if(content == other.content) {
      return *this;
    } else
      this->release();
  	content = other.content;
  	acquire();
    return *this;
  }
  
  template <typename VertexContent, typename Alloc, typename charT>
  std::basic_ostream<charT>& operator<<(std::basic_ostream<charT>& ss, const Vertex<VertexContent>& v)
  {
    ss << "Vertex<" << demangle(typeid(VertexContent).name()) << ">(" << v.id() << ")";
    return ss;
  }
}

namespace std 
{
  HASH_NS_ENTER
  template <typename VertexContent>
  struct hash<mgx::Vertex<VertexContent> > 
  {
    size_t operator()(const mgx::Vertex<VertexContent> &v) const
    {
      hash<size_t> h;
      return h((size_t)v.id());
      //return ((size_t)v.id()) >> 2;         // Shift to consider memory alignment!
    }
  };
  HASH_NS_EXIT
}


#endif // VERTEX_HPP
