/*
 * Software License Agreement (BSD License)
 *
 *  Point Cloud Library (PCL) - www.pointclouds.org
 *  Copyright (c) 2012-, Open Perception, Inc.
 *
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of Willow Garage, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef PCL_SEGMENTATION_IMPL_RANDOM_WALKER_HPP
#define PCL_SEGMENTATION_IMPL_RANDOM_WALKER_HPP

#include <boost/bimap.hpp>

#include <Eigen/Sparse>

namespace pcl
{

  namespace segmentation
  {

    namespace detail
    {

      /** \brief Multilabel graph segmentation using random walks.
        *
        * This is an implementation of the algorithm described in "Random Walks
        * for Image Segmentation" by Leo Grady.
        *
        * See the documentation of the randomWalker() function for details.
        *
        * \author Sergey Alexandrov
        * \ingroup segmentation
        */
      template <class Graph, class EdgeWeightMap, class VertexColorMap>
      class RandomWalker
      {

        public:

          using Color = typename boost::property_traits<VertexColorMap>::value_type;
          using Weight = typename boost::property_traits<EdgeWeightMap>::value_type;
          using GraphTraits = boost::graph_traits<Graph>;
          using EdgeDescriptor = typename GraphTraits::edge_descriptor;
          using VertexDescriptor = typename GraphTraits::vertex_descriptor;
          using EdgeIterator = typename GraphTraits::edge_iterator;
          using OutEdgeIterator = typename GraphTraits::out_edge_iterator;
          using VertexIterator = typename GraphTraits::vertex_iterator;
          using VertexIndexMap = typename boost::property_map<Graph, boost::vertex_index_t>::type;
          using VertexDegreeMap = boost::iterator_property_map<typename std::vector<Weight>::iterator, VertexIndexMap>;
          using SparseMatrix = Eigen::SparseMatrix<Weight>;
          using Matrix = Eigen::Matrix<Weight, Eigen::Dynamic, Eigen::Dynamic>;
          using Vector = Eigen::Matrix<Weight, Eigen::Dynamic, 1>;

          RandomWalker (Graph& g, EdgeWeightMap weights, VertexColorMap colors)
          : g_ (g)
          , weight_map_ (weights)
          , color_map_ (colors)
          , index_map_ (boost::get (boost::vertex_index, g_))
          , degree_storage_ (boost::num_vertices (g_), 0)
          , degree_map_ (boost::make_iterator_property_map (degree_storage_.begin (), index_map_))
          {
          }

          bool
          segment ()
          {
            computeVertexDegrees ();
            buildLinearSystem ();
            return solveLinearSystem ();
          }

          void
          computeVertexDegrees ()
          {
            using namespace boost;
            EdgeIterator ei, e_end;
            for (tie (ei, e_end) = edges (g_); ei != e_end; ++ei)
            {
              Weight w = weight_map_[*ei];
              degree_map_[source (*ei, g_)] += w;
              degree_map_[target (*ei, g_)] += w;
            }
          }

          void
          buildLinearSystem ()
          {
            using namespace boost;

            using T = Eigen::Triplet<float>;
            using Triplets = std::vector<T>;
            Triplets L_triplets;
            Triplets B_triplets;

            VertexIterator vi, v_end;
            for (tie (vi, v_end) = vertices (g_); vi != v_end; ++vi)
            {
              // If this is a labeled vertex add it to the seeds list and register its color
              if (color_map_[*vi])
              {
                seeds_.push_back (*vi);
                colors_.insert (color_map_[*vi]);
              }
              // Skip seeds and vertices with zero connectivity
              if (color_map_[*vi] || std::fabs (degree_map_[*vi]) < std::numeric_limits<Weight>::epsilon ())
                continue;
              // Create a row in L matrix for the vertex
              std::size_t current_row = insertInBimap (L_vertex_bimap, *vi);
              // Add diagonal degree entry for the vertex
              L_triplets.push_back (T (current_row, current_row, degree_map_[*vi]));
              // Iterate over incident vertices and add entries on corresponding columns of L or B
              OutEdgeIterator ei, e_end;
              for (tie (ei, e_end) = out_edges (*vi, g_); ei != e_end; ++ei)
              {
                Weight w = weight_map_[*ei];
                VertexDescriptor tgt = target (*ei, g_);
                Color color = color_map_[tgt];
                if (color)
                {
                  // This is a seed and will go to B matrix
                  std::size_t column;
                  if (B_color_bimap.right.count (color) == 0)
                  {
                    // This is the first time we encountered this color, create a new column in B
                    column = insertInBimap (B_color_bimap, color);
                  }
                  else
                  {
                    column = B_color_bimap.right.at (color);
                  }
                  B_triplets.push_back (T (current_row, column, w));
                }
                else
                {
                  // This is a non-seed and will go to L matrix,
                  // but only if a row for this vertex already exists
                  if (L_vertex_bimap.right.count (tgt) && L_vertex_bimap.right.at (tgt) != current_row)
                  {
                    L_triplets.push_back (T (current_row, L_vertex_bimap.right.at (tgt), -w));
                  }
                }
              }
            }

            std::size_t num_equations = L_vertex_bimap.size ();
            std::size_t num_colors = B_color_bimap.size ();
            L.resize (num_equations, num_equations);
            B.resize (num_equations, num_colors);
            if (!L_triplets.empty ())
              L.setFromTriplets(L_triplets.begin(), L_triplets.end());
            if (!B_triplets.empty ())
              B.setFromTriplets(B_triplets.begin(), B_triplets.end());
          }

          bool solveLinearSystem()
          {
            X.resize (L.rows (), B.cols ());

            // Nothing to solve
            if (L.rows () == 0 || B.cols () == 0)
              return true;

            Eigen::SimplicialCholesky<SparseMatrix, Eigen::Lower> cg;
            cg.compute (L);
            bool succeeded = true;
            for (Eigen::Index i = 0; i < B.cols (); ++i)
            {
              Vector b = B.col (i);
              X.col (i) = cg.solve (b);
              if (cg.info () != Eigen::Success)
                succeeded = false;
            }

            assignColors ();
            return succeeded;
          }

          void
          assignColors ()
          {
            using namespace boost;
            if (X.cols ())
              for (Eigen::Index i = 0; i < X.rows (); ++i)
              {
                std::size_t max_column;
                X.row (i).maxCoeff (&max_column);
                VertexDescriptor vertex = L_vertex_bimap.left.at (i);
                Color color = B_color_bimap.left.at (max_column);
                color_map_[vertex] = color;
              }
          }

          void
          getPotentials (Matrix& potentials, std::map<Color, std::size_t>& color_to_column_map)
          {
            using namespace boost;
            potentials = Matrix::Zero (num_vertices (g_), colors_.size ());
            // Copy over rows from X
            for (Eigen::Index i = 0; i < X.rows (); ++i)
              potentials.row (L_vertex_bimap.left.at (i)).head (X.cols ()) = X.row (i);
            // In rows that correspond to seeds put ones in proper columns
            for (std::size_t i = 0; i < seeds_.size (); ++i)
            {
              VertexDescriptor v = seeds_[i];
              insertInBimap (B_color_bimap, color_map_[v]);
              potentials (seeds_[i], B_color_bimap.right.at (color_map_[seeds_[i]])) = 1;
            }
            // Fill in a map that associates colors with columns in potentials matrix
            color_to_column_map.clear ();
            for (Eigen::Index i = 0; i < potentials.cols (); ++i)
              color_to_column_map[B_color_bimap.left.at (i)] = i;
          }

          template <typename T> static inline std::size_t
          insertInBimap (boost::bimap<std::size_t, T>& bimap, T value)
          {
            if (bimap.right.count (value) != 0)
            {
              return bimap.right.at (value);
            }
            std::size_t s = bimap.size ();
            bimap.insert (typename boost::bimap<std::size_t, T>::value_type (s, value));
            return s;
          }

          Graph& g_;
          EdgeWeightMap weight_map_;
          VertexColorMap color_map_;
          VertexIndexMap index_map_;

          std::vector<VertexDescriptor> seeds_;
          std::set<Color> colors_;

          std::vector<Weight> degree_storage_;
          VertexDegreeMap degree_map_;
          SparseMatrix L;
          SparseMatrix B;
          Matrix X;

          // Map vertex identifiers to the rows/columns of L and vice versa
          boost::bimap<std::size_t, VertexDescriptor> L_vertex_bimap;
          // Map colors to the columns of B and vice versa
          boost::bimap<std::size_t, Color> B_color_bimap;

      };

    }

    template <class Graph> bool
    randomWalker (Graph& graph)
    {
      return randomWalker (graph,
                           boost::get (boost::edge_weight, graph),
                           boost::get (boost::vertex_color, graph));
    }

    template <class Graph, class EdgeWeightMap, class VertexColorMap> bool
    randomWalker (Graph& graph,
                  EdgeWeightMap weights,
                  VertexColorMap colors)
    {
      using namespace boost;

      using EdgeDescriptor = typename graph_traits<Graph>::edge_descriptor;
      using VertexDescriptor = typename graph_traits<Graph>::vertex_descriptor;

      BOOST_CONCEPT_ASSERT ((VertexListGraphConcept<Graph>));                                 // to have vertices(), num_vertices()
      BOOST_CONCEPT_ASSERT ((EdgeListGraphConcept<Graph>));                                   // to have edges()
      BOOST_CONCEPT_ASSERT ((IncidenceGraphConcept<Graph>));                                  // to have source(), target() and out_edges()
      BOOST_CONCEPT_ASSERT ((ReadablePropertyMapConcept<EdgeWeightMap, EdgeDescriptor>));     // read weight-values from edges
      BOOST_CONCEPT_ASSERT ((ReadWritePropertyMapConcept<VertexColorMap, VertexDescriptor>)); // read and write color-values from vertices

      ::pcl::segmentation::detail::RandomWalker
      <
        Graph,
        EdgeWeightMap,
        VertexColorMap
      >
      rw (graph, weights, colors);
      return rw.segment ();
    }

    template <class Graph, class EdgeWeightMap, class VertexColorMap> bool
    randomWalker (Graph& graph,
                  EdgeWeightMap weights,
                  VertexColorMap colors,
                  Eigen::Matrix<typename boost::property_traits<EdgeWeightMap>::value_type, Eigen::Dynamic, Eigen::Dynamic>& potentials,
                  std::map<typename boost::property_traits<VertexColorMap>::value_type, std::size_t>& colors_to_columns_map)
    {
      using namespace boost;

      using EdgeDescriptor = typename graph_traits<Graph>::edge_descriptor;
      using VertexDescriptor = typename graph_traits<Graph>::vertex_descriptor;

      BOOST_CONCEPT_ASSERT ((VertexListGraphConcept<Graph>));                                 // to have vertices(), num_vertices()
      BOOST_CONCEPT_ASSERT ((EdgeListGraphConcept<Graph>));                                   // to have edges()
      BOOST_CONCEPT_ASSERT ((IncidenceGraphConcept<Graph>));                                  // to have source(), target() and out_edges()
      BOOST_CONCEPT_ASSERT ((ReadablePropertyMapConcept<EdgeWeightMap, EdgeDescriptor>));     // read weight-values from edges
      BOOST_CONCEPT_ASSERT ((ReadWritePropertyMapConcept<VertexColorMap, VertexDescriptor>)); // read and write color-values from vertices

      ::pcl::segmentation::detail::RandomWalker
      <
        Graph,
        EdgeWeightMap,
        VertexColorMap
      >
      rw (graph, weights, colors);
      bool result = rw.segment ();
      rw.getPotentials (potentials, colors_to_columns_map);
      return result;
    }

  }

}

#endif /* PCL_SEGMENTATION_IMPL_RANDOM_WALKER_HPP */

