/*********************************************************************
* Software License Agreement (BSD License)
*
*  Copyright (c) 2011, Rice University
*  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 the Rice University 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.
*********************************************************************/

/* Author: Matt Maly */

#include "ompl/control/planners/syclop/GridDecomposition.h"

namespace
{
    /** \brief Helper method to return len^dim for calculating number of regions. */
    static int calcNumGridCells(int len, int dim);
}

ompl::control::GridDecomposition::GridDecomposition(int len, int dim, const base::RealVectorBounds &b)
    : Decomposition(dim, b), length_(len), cellVolume_(b.getVolume()), numGridCells_(calcNumGridCells(len, dim))
{
    double lenInv = 1.0 / len;
    for (int i = 0; i < dim; ++i)
        cellVolume_ *= lenInv;
}

void ompl::control::GridDecomposition::getNeighbors(int rid, std::vector<int>& neighbors) const
{
    //We efficiently compute neighbors for dim = 1, 2, or 3; for higher dimensions we use a general approach.
    if (dimension_ == 1)
    {
        if (rid > 0)
            neighbors.push_back(rid-1);
        if (rid < length_-1)
            neighbors.push_back(rid+1);
    }
    else if (dimension_ == 2)
    {
        static const int offset[] = {
            -1, -1,
             0, -1,
            +1, -1,
            -1,  0,
            +1,  0,
            -1, +1,
             0, +1,
            +1, +1
        };
        std::vector<int> coord(2);
        regionToGridCoord(rid, coord);
        std::vector<int> nc(2);
        for (std::size_t i = 0; i < 16; i += 2)
        {
            nc[0] = coord[0] + offset[i];
            nc[1] = coord[1] + offset[i+1];
            if (nc[0] >= 0 && (int) nc[0] < length_ && nc[1] >= 0
               && (int) nc[1] < length_)
                neighbors.push_back(nc[0]*length_ + nc[1]);
        }
    }
    else if (dimension_ == 3)
    {
        static const int offset[] = {
            -1,  0, 0,
            +1,  0, 0,
             0, -1, 0,
             0, +1, 0,
            -1, -1, 0,
            -1, +1, 0,
            +1, -1, 0,
            +1, +1, 0,
            -1,  0, -1,
            +1,  0, -1,
             0, -1, -1,
             0, +1, -1,
            -1, -1, -1,
            -1, +1, -1,
            +1, -1, -1,
            +1, +1, -1,
            -1,  0, +1,
            +1,  0, +1,
             0, -1, +1,
             0, +1, +1,
            -1, -1, +1,
            -1, +1, +1,
            +1, -1, +1,
            +1, +1, +1,
            0, 0, -1,
            0, 0, +1
        };
        std::vector<int> coord(3);
        regionToGridCoord(rid, coord);
        std::vector<int> nc(3);
        for (int i = 0; i < 78; i += 3)
        {
            nc[0] = coord[0] + offset[i];
            nc[1] = coord[1] + offset[i+1];
            nc[2] = coord[2] + offset[i+2];
            if (nc[0] >= 0 && (int) nc[0] < length_
              && nc[1] >= 0 && (int) nc[1] < length_
              && nc[2] >= 0 && (int) nc[2] < length_)
                neighbors.push_back(nc[0]*length_*length_ + nc[1]*length_ + nc[2]);
        }
    }
    else
    {
        computeGridNeighbors (rid, neighbors);
    }
}

int ompl::control::GridDecomposition::locateRegion(const base::State *s) const
{
    std::vector<double> coord(dimension_);
    project(s, coord);
    return coordToRegion(coord);
}

void ompl::control::GridDecomposition::sampleFromRegion(int rid, RNG &rng, std::vector<double> &coord) const
{
    coord.resize(dimension_);
    const base::RealVectorBounds& regionBounds(getRegionBounds(rid));
    for (int i = 0; i < dimension_; ++i)
        coord[i] = rng.uniformReal(regionBounds.low[i], regionBounds.high[i]);
}

void ompl::control::GridDecomposition::computeGridNeighbors (int rid, std::vector <int> &neighbors) const
{
    std::vector <int> candidate (dimension_, -1);
    std::vector <int> coord;
    regionToGridCoord (rid, coord);

    computeGridNeighborsSub (coord, neighbors, 0, candidate);
}

void ompl::control::GridDecomposition::computeGridNeighborsSub (const std::vector <int> &coord,
                                                                std::vector <int> &neighbors,
                                                                int dim,
                                                                std::vector <int> &candidate) const
{
    // Stopping condition for recursive method.
    if (dim == dimension_)
    {
        // Make sure we don't push back ourselves as a neighbor
        bool same = true;
        for (unsigned int i = 0; i < coord.size() && same; ++i)
            same = (coord[i] == candidate[i]);

        if (!same)
        {
            neighbors.push_back (gridCoordToRegion (candidate));
        }
    }
    else
    {
        // Check neighbor in the cell preceding this one in this dimension
        if (coord[dim] >= 1)
        {
            candidate[dim] = coord[dim]-1;
            computeGridNeighborsSub (coord, neighbors, dim+1, candidate);
        }

        // Make sure to include the same coordinate, for neighbors "above", "below", "in front of", "behind", etcetera.
        candidate[dim] = coord[dim];
        computeGridNeighborsSub (coord, neighbors, dim+1, candidate);

        // Check neighbor in the cell after this one in this dimension
        if (coord[dim] +1 < length_)
        {
            candidate[dim] = coord[dim]+1;
            computeGridNeighborsSub (coord, neighbors, dim+1, candidate);
        }
    }
}

void ompl::control::GridDecomposition::regionToGridCoord(int rid, std::vector<int>& coord) const
{
    coord.resize(dimension_);
    for (int i = dimension_-1; i >= 0; --i)
    {
        int remainder = rid % length_;
        coord[i] = remainder;
        rid /= length_;
    }
}

int ompl::control::GridDecomposition::gridCoordToRegion (const std::vector <int> &coord) const
{
    int region = 0;
    for (unsigned int i = 0; i < coord.size (); i++)
    {
        // Computing length_^(dimension of coord -1)
        int multiplicand = 1;
        for (unsigned int j = 1; j < coord.size () - i; j++)
            multiplicand *= length_;

        region += (coord[i] * multiplicand);
    }
    return region;
}

int ompl::control::GridDecomposition::coordToRegion(const std::vector<double>& coord) const
{
    int region = 0;
    int factor = 1;
    int index;
    for (int i = dimension_-1; i >= 0; --i)
    {
        index = (int) (length_*(coord[i]-bounds_.low[i])/(bounds_.high[i]-bounds_.low[i]));

        // There is an edge case when the coordinate lies exactly on the upper bound where
        // the region index will be out of bounds.  Ensure index lies within [0, length_)
        if (index >= length_)
            index = length_-1;

        region += factor*index;
        factor *= length_;
    }
    return region;
}

void ompl::control::GridDecomposition::coordToGridCoord(const std::vector<double>& coord, std::vector<int>& gridCoord) const
{
    gridCoord.resize(dimension_);
    for (int i = 0; i < dimension_; ++i)
    {
        gridCoord[i] = (int) (length_*(coord[i]-bounds_.low[i])/(bounds_.high[i]-bounds_.low[i]));

        // There is an edge case when the coordinate lies exactly on the upper bound where
        // the region index will be out of bounds.  Ensure index lies within [0, length_)
        if (gridCoord[i] >= length_)
            gridCoord[i] = length_-1;
    }
}

const ompl::base::RealVectorBounds& ompl::control::GridDecomposition::getRegionBounds(int rid) const
{
    if (regToBounds_.count(rid) > 0)
        return *regToBounds_[rid].get();
    ompl::base::RealVectorBounds *regionBounds = new ompl::base::RealVectorBounds(dimension_);
    std::vector<int> rc(dimension_);
    regionToGridCoord(rid, rc);
    for (int i = 0; i < dimension_; ++i)
    {
        const double length = (bounds_.high[i] - bounds_.low[i]) / length_;
        regionBounds->low[i] = bounds_.low[i] + length*rc[i];
        regionBounds->high[i] = regionBounds->low[i] + length;
    }
    regToBounds_[rid] = boost::shared_ptr<ompl::base::RealVectorBounds>(regionBounds);
    return *regToBounds_[rid].get();
}

namespace
{
    int calcNumGridCells(int len, int dim)
    {
        int numCells = 1;
        for (int i = 0; i < dim; ++i)
            numCells *= len;
        return numCells;
    }
}
