/* Copyright (C) 2007-2013 Arjen G Lentz & Antony T Curtis for Open Query

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/* ======================================================================
   Open Query Graph Computation Engine, based on a concept by Arjen Lentz
   v3 implementation by Antony Curtis, Arjen Lentz, Andrew McDonnell
   For more information, documentation, support, enhancement engineering,
   see http://openquery.com/graph or contact graph@openquery.com
   ======================================================================
*/

#pragma once

#include "oqgraph_thunk.h"
#include "oqgraph_judy.h"

#define BOOST_NO_HASH 1
#define BOOST_NO_SLIST 1

#include <boost/graph/directed_graph.hpp>
#include <boost/graph/adjacency_iterator.hpp>

namespace open_query
{
  struct OQGraphTraversalCategory
    : public boost::bidirectional_graph_tag
    , public boost::adjacency_graph_tag
    , public boost::edge_list_graph_tag
  { };

}

namespace oqgraph3
{
  struct traversal_category
    : public boost::adjacency_graph_tag
    , public boost::bidirectional_graph_tag
    , public boost::edge_list_graph_tag
  { };

  struct edge_iterator
  {
    typedef edge_iterator self;
    typedef edge_info value_type;
    typedef edge_info& reference;
    typedef edge_info pointer;
    typedef std::ptrdiff_t difference_type;
    typedef std::input_iterator_tag iterator_category;
    edge_iterator() { }
    edge_iterator(const graph_ptr& graph, size_t offset=0)
      : _graph(graph)
      , _offset(offset) { }
    edge_iterator(const edge_iterator& pos)
      : _graph(pos._graph)
      , _offset(pos._offset) { }
    value_type operator*();
    self& operator+=(size_t n) { _offset+= n; return *this; }
    self& operator++() { ++_offset; return *this; }
    self operator++(int)
    { size_t temp= _offset++; return edge_iterator(_graph, temp); }
    bool seek();
    bool operator==(const self& x);
    bool operator!=(const self& x);
    graph_ptr _graph;
    size_t _offset;
  };

  struct vertex_iterator
  {
    typedef vertex_iterator self;
    typedef vertex_id value_type;
    typedef vertex_id& reference;
    typedef vertex_id pointer;
    typedef std::ptrdiff_t difference_type;
    typedef std::input_iterator_tag iterator_category;
    vertex_iterator() { }
    vertex_iterator(const cursor_ptr& pos) : _cursor(pos.operator->()) { }
    vertex_id operator*() const
    {
      edge_info edge(*_cursor);
      if (!_seen.test(edge.origid()))
        return edge.origid();
      else
        return edge.destid();
    }

    self& operator++()
    {
      edge_info edge(*_cursor);
      if (!_seen.test(edge.origid()))
      {
        _seen.set(edge.origid());
      }
      else
      {
        _seen.set(edge.destid());
      }

      while (_seen.test(edge.origid()) && _seen.test(edge.destid()))
      {
        if (_cursor->seek_next())
          break;
        edge= _cursor;
      }
      return *this;
    }
    self operator++(int) { cursor* t(new cursor(*_cursor)); ++(*this); return vertex_iterator(t); }
    bool operator==(const self& x) { return *_cursor == *x._cursor; }
    bool operator!=(const self& x) { return *_cursor != *x._cursor; }
    cursor_ptr _cursor;
    open_query::judy_bitset _seen;
  };


  struct out_edge_iterator
  {
    typedef out_edge_iterator self;
    typedef edge_info value_type;
    typedef edge_info& reference;
    typedef edge_info pointer;
    typedef std::ptrdiff_t difference_type;
    typedef std::input_iterator_tag iterator_category;
    out_edge_iterator() { }
    out_edge_iterator(const cursor_ptr& cursor) : _cursor(cursor) { }
    value_type operator*() { return value_type(_cursor); }
    self& operator++() { _cursor->seek_next(); return *this; }
    self operator++(int)
    { cursor_ptr t(new cursor(*_cursor)); ++(*this); return out_edge_iterator(t); }
    bool operator==(const self& x) { return _cursor == x._cursor; }
    bool operator!=(const self& x) { return _cursor != x._cursor; }
    cursor_ptr _cursor;
  };

  struct in_edge_iterator
  {
    typedef in_edge_iterator self;
    typedef edge_info value_type;
    typedef edge_info& reference;
    typedef edge_info pointer;
    typedef std::ptrdiff_t difference_type;
    typedef std::input_iterator_tag iterator_category;
    in_edge_iterator() { }
    in_edge_iterator(const cursor_ptr& cursor) : _cursor(cursor) { }
    value_type operator*() { return value_type(_cursor); }
    self& operator++() { _cursor->seek_next(); return *this; }
    self operator++(int)
    { cursor_ptr t(new cursor(*_cursor)); ++(*this); return in_edge_iterator(t); }
    bool operator==(const self& x) { return _cursor == x._cursor; }
    bool operator!=(const self& x) { return _cursor != x._cursor; }
    cursor_ptr _cursor;
  };

  struct vertex_index_property_map
  {
    typedef vertex_id value_type;
    typedef value_type reference;
    typedef vertex_id key_type;
    typedef boost::readable_property_map_tag category;
    vertex_index_property_map(const graph& g) : _g(g) { }
    const graph& _g;

    friend inline reference
    get(const vertex_index_property_map&, key_type key)
    { return key; }
  };

  struct edge_weight_property_map
  {
    typedef weight_t value_type;
    typedef value_type reference;
    typedef edge_info key_type;
    typedef boost::readable_property_map_tag category;
    edge_weight_property_map(const graph& g) : _g(g) { }
    const graph& _g;
  };

  struct edge_index_property_map
  {
    typedef cursor_ptr value_type;
    typedef cursor_ptr reference;
    typedef edge_info key_type;
    typedef boost::readable_property_map_tag category;
    edge_index_property_map(const graph& g) : _g(g) { }
    const graph& _g;
  };
}

namespace boost
{

  template<>
  struct graph_traits<oqgraph3::graph>
  {
    typedef oqgraph3::vertex_id vertex_descriptor;
    typedef oqgraph3::edge_info edge_descriptor;
    typedef boost::adjacency_iterator_generator<
        oqgraph3::graph,
        oqgraph3::vertex_id,
        oqgraph3::out_edge_iterator>::type adjacency_iterator;
    typedef oqgraph3::out_edge_iterator out_edge_iterator;
    typedef oqgraph3::in_edge_iterator in_edge_iterator;
    typedef oqgraph3::vertex_iterator vertex_iterator;
    typedef oqgraph3::edge_iterator edge_iterator;

    typedef boost::directed_tag directed_category;
    typedef boost::allow_parallel_edge_tag edge_parallel_category;
    typedef oqgraph3::traversal_category traversal_category;

    typedef oqgraph3::vertices_size_type vertices_size_type;
    typedef oqgraph3::edges_size_type edges_size_type;
    typedef oqgraph3::degree_size_type degree_size_type;

    static inline oqgraph3::vertex_id null_vertex()
    { return oqgraph3::vertex_id(-1); }
  };

  template<>
  struct graph_traits<const oqgraph3::graph>
    : public graph_traits<oqgraph3::graph>
  { };

  template <>
  struct graph_property_type<oqgraph3::graph>
  {
    typedef no_property type;
  };

  template <>
  struct vertex_property_type<oqgraph3::graph>
  {
    typedef no_property type;
  };

  template <>
  struct edge_property_type<oqgraph3::graph>
  {
    typedef no_property type;
  };

#if BOOST_VERSION >= 104601
  template <>
  struct graph_bundle_type<oqgraph3::graph>
  {
    typedef no_graph_bundle type;
  };

  template <>
  struct vertex_bundle_type<oqgraph3::graph>
  {
    typedef no_vertex_bundle type;
  };

  template <>
  struct edge_bundle_type<oqgraph3::graph>
  {
    typedef no_edge_bundle type;
  };
#endif

  inline graph_traits<oqgraph3::graph>::vertex_descriptor
  source(
      const graph_traits<oqgraph3::graph>::edge_descriptor& e,
      const oqgraph3::graph&)
  { return e.origid(); }

  inline graph_traits<oqgraph3::graph>::vertex_descriptor
  target(
      const graph_traits<oqgraph3::graph>::edge_descriptor& e,
      const oqgraph3::graph&)
  { return e.destid(); }

  inline std::pair<
      graph_traits<oqgraph3::graph>::out_edge_iterator,
      graph_traits<oqgraph3::graph>::out_edge_iterator>
  out_edges(
      graph_traits<oqgraph3::graph>::vertex_descriptor v,
      const oqgraph3::graph& g)
  {
    oqgraph3::cursor*
        end= new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g));
    oqgraph3::cursor*
        start= new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g));
    start->seek_to(v, boost::none);
    return std::make_pair(
        graph_traits<oqgraph3::graph>::out_edge_iterator(start),
        graph_traits<oqgraph3::graph>::out_edge_iterator(end));
  }

  inline graph_traits<oqgraph3::graph>::degree_size_type
  out_degree(
      graph_traits<oqgraph3::graph>::vertex_descriptor v,
      const oqgraph3::graph& g)
  {
    std::size_t count = 0;
    for (std::pair<
            graph_traits<oqgraph3::graph>::out_edge_iterator,
            graph_traits<oqgraph3::graph>::out_edge_iterator> i= out_edges(v, g);
        i.first != i.second; ++i.first)
    {
      ++count;
    }
    return count;
  }


  inline std::pair<
      graph_traits<oqgraph3::graph>::in_edge_iterator,
      graph_traits<oqgraph3::graph>::in_edge_iterator>
  in_edges(
      graph_traits<oqgraph3::graph>::vertex_descriptor v,
      const oqgraph3::graph& g)
  {
    oqgraph3::cursor*
        end= new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g));
    oqgraph3::cursor*
        start= new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g));
    start->seek_to(boost::none, v);
    return std::make_pair(
        graph_traits<oqgraph3::graph>::in_edge_iterator(start),
        graph_traits<oqgraph3::graph>::in_edge_iterator(end));
  }

  inline graph_traits<oqgraph3::graph>::degree_size_type
  in_degree(
      graph_traits<oqgraph3::graph>::vertex_descriptor v,
      const oqgraph3::graph& g)
  {
    std::size_t count = 0;
    for (std::pair<
            graph_traits<oqgraph3::graph>::in_edge_iterator,
            graph_traits<oqgraph3::graph>::in_edge_iterator> it= in_edges(v, g);
        it.first != it.second; ++it.first)
    {
      ++count;
    }
    return count;
  }


  // EdgeListGraph concepts
  inline std::pair<
      graph_traits<oqgraph3::graph>::edge_iterator,
      graph_traits<oqgraph3::graph>::edge_iterator>
  edges(const oqgraph3::graph& g)
  {
    std::size_t end= std::size_t(-1);
    std::size_t start= end;

    if (g.num_edges())
      start= 0;

    return std::make_pair(
        graph_traits<oqgraph3::graph>::edge_iterator(
            const_cast<oqgraph3::graph*>(&g), start),
        graph_traits<oqgraph3::graph>::edge_iterator(
            const_cast<oqgraph3::graph*>(&g), end));
  }

  inline std::pair<
      graph_traits<oqgraph3::graph>::vertex_iterator,
      graph_traits<oqgraph3::graph>::vertex_iterator>
  vertices(const oqgraph3::graph& g)
  {
    oqgraph3::cursor*
        start= new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g));
    start->seek_to(boost::none, boost::none);
    return std::make_pair(
        graph_traits<oqgraph3::graph>::vertex_iterator(start),
        graph_traits<oqgraph3::graph>::vertex_iterator(
            new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g))));
  }

  inline graph_traits<oqgraph3::graph>::vertices_size_type
  num_vertices(const oqgraph3::graph& g)
  {
    std::size_t count = 0;
    for (std::pair<
            graph_traits<oqgraph3::graph>::vertex_iterator,
            graph_traits<oqgraph3::graph>::vertex_iterator> i= vertices(g);
        i.first != i.second; ++i.first)
    {
      ++count;
    }
    return count;
  }

  template<>
  struct property_map<oqgraph3::graph, edge_weight_t>
  {
    typedef void type;
    typedef oqgraph3::edge_weight_property_map const_type;
  };

  template<>
  struct property_map<oqgraph3::graph, vertex_index_t>
  {
    typedef void type;
    typedef oqgraph3::vertex_index_property_map const_type;
  };

  template<>
  struct property_map<oqgraph3::graph, edge_index_t>
  {
    typedef void type;
    typedef oqgraph3::edge_index_property_map const_type;
  };

  inline property_map<
      oqgraph3::graph,
      edge_weight_t>::const_type::reference
  get(
      edge_weight_t,
      const oqgraph3::graph& g,
      const property_map<
          oqgraph3::graph,
          edge_weight_t>::const_type::key_type& key)
  { return key.weight(); }

  inline property_map<
      oqgraph3::graph,
      edge_weight_t>::const_type
  get(edge_weight_t,
      const oqgraph3::graph& g)
  { return property_map<oqgraph3::graph, edge_weight_t>::const_type(g); }

  inline property_map<
      oqgraph3::graph,
      edge_weight_t>::const_type::reference
  get(const property_map<oqgraph3::graph,
          edge_weight_t>::const_type& p,
      const property_map<
          oqgraph3::graph,
          edge_weight_t>::const_type::key_type& key)
  { return key.weight(); }

  inline property_map<
      oqgraph3::graph,
      edge_index_t>::const_type::reference
  get(edge_index_t,
      const oqgraph3::graph&,
      const property_map<
          oqgraph3::graph,
          edge_index_t>::const_type::key_type& key)
  { return key._cursor; }

  inline property_map<oqgraph3::graph, edge_index_t>::const_type
  get(edge_index_t, const oqgraph3::graph& g)
  { return property_map<oqgraph3::graph, edge_index_t>::const_type(g); }

  inline property_map<oqgraph3::graph, edge_index_t>::const_type::reference
  get(const property_map<oqgraph3::graph, edge_index_t>::const_type&,
      const property_map<oqgraph3::graph,
      edge_index_t>::const_type::key_type& key)
  { return key._cursor; }

  inline property_map<oqgraph3::graph, vertex_index_t>::const_type::reference
  get(vertex_index_t, const oqgraph3::graph&,
      const property_map<oqgraph3::graph,
      vertex_index_t>::const_type::key_type& key)
  { return key; }

  inline property_map<oqgraph3::graph, vertex_index_t>::const_type
  get(vertex_index_t, const oqgraph3::graph& g)
  { return property_map<oqgraph3::graph, vertex_index_t>::const_type(g); }

  inline optional<graph_traits<oqgraph3::graph>::vertex_descriptor>
  find_vertex(oqgraph3::vertex_id id, const oqgraph3::graph& g)
  {
    // Fix for https://bugs.launchpad.net/oqgraph/+bug/1196020 returning vertex even when not in graph
    // Psuedocode for fix:
    // if count(*) from g->TABLE where source=id or target=id > 0 then return id else return null
    oqgraph3::cursor* found_cursor = new oqgraph3::cursor(const_cast<oqgraph3::graph*>(&g));
    bool found = (found_cursor->seek_to(id, boost::none) && found_cursor->seek_to(boost::none, id));
    delete found_cursor;
    if (found) {
      // id is neither a from or a to in a link
      return optional<graph_traits<oqgraph3::graph>::vertex_descriptor>();
    }
    return id;
  }

}

