#include "Graph.h"

#include <queue>
#include <algorithm>

#include <boost/math/constants/constants.hpp>

namespace {
    double clamp(double val, double min, double max) {
        return std::max(min, std::min(max, val));
    }

    bool pointOnLine(const std::array<cglib::vec3<double>, 2>& line, const cglib::vec3<double>& pos) {
        cglib::vec3<double> v0 = line[1] - line[0];
        cglib::vec3<double> v1 = pos - line[0];
        return (v1 == cglib::vec3<double>(0, 0, 0) || cglib::dot_product(cglib::unit(v0), cglib::unit(v1)) == 1);
    }

    bool pointInsideTriangle(const std::array<cglib::vec3<double>, 3>& triangle, const cglib::vec3<double>& pos) {
        cglib::vec3<double> v0 = triangle[2] - triangle[0];
        cglib::vec3<double> v1 = triangle[1] - triangle[0];
        cglib::vec3<double> v2 = pos - triangle[0];
        if (cglib::dot_product(v2, cglib::vector_product(v0, v1)) != 0) {
            return false;
        }

        double dot00 = cglib::dot_product(v0, v0);
        double dot01 = cglib::dot_product(v0, v1);
        double dot02 = cglib::dot_product(v0, v2);
        double dot11 = cglib::dot_product(v1, v1);
        double dot12 = cglib::dot_product(v1, v2);

        double invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
        double u = (dot11 * dot02 - dot01 * dot12) * invDenom;
        double v = (dot00 * dot12 - dot01 * dot02) * invDenom;

        return u >= 0 && v >= 0 && u + v <= 1;
    }

    cglib::vec3<double> closestPointOnLine(const std::array<cglib::vec3<double>, 2>& line, const cglib::vec3<double>& pos) {
        cglib::vec3<double> edge = line[1] - line[0];
        if (cglib::norm(edge) == 0) {
            return line[0];
        }
        cglib::vec3<double> v0 = pos - line[0];

        double c = cglib::dot_product(edge, v0);
        double d = cglib::dot_product(edge, edge);

        double s = (v0 != cglib::vec3<double>(0, 0, 0) ? clamp(c / d, 0, 1) : 0);
        return line[0] + edge * s;
    }

    cglib::vec3<double> closestPointOnTriangle(const std::array<cglib::vec3<double>, 3>& triangle, const cglib::vec3<double>& pos) {
        cglib::vec3<double> edge0 = triangle[1] - triangle[0];
        if (cglib::norm(edge0) == 0) {
            return closestPointOnLine(std::array<cglib::vec3<double>, 2> {{ triangle[2], triangle[1] }}, pos);
        }
        cglib::vec3<double> edge1 = triangle[2] - triangle[0];
        if (cglib::norm(edge1) == 0) {
            return closestPointOnLine(std::array<cglib::vec3<double>, 2> {{ triangle[1], triangle[0] }}, pos);
        }
        cglib::vec3<double> edge2 = triangle[1] - triangle[2];
        if (cglib::norm(edge2) == 0) {
            return closestPointOnLine(std::array<cglib::vec3<double>, 2> {{ triangle[2], triangle[0] }}, pos);
        }
        cglib::vec3<double> v0 = triangle[0] - pos;

        double a = cglib::dot_product(edge0, edge0);
        double b = cglib::dot_product(edge0, edge1);
        double c = cglib::dot_product(edge1, edge1);
        double d = cglib::dot_product(edge0, v0);
        double e = cglib::dot_product(edge1, v0);

        double det = a * c - b * b;
        double s = b * e - c * d;
        double t = b * d - a * e;

        if (s + t < det) {
            if (s < 0) {
                if (t < 0) {
                    if (d < 0) {
                        s = clamp(-d / a, 0, 1);
                        t = 0;
                    } else {
                        s = 0;
                        t = clamp(-e / c, 0, 1);
                    }
                } else {
                    s = 0;
                    t = clamp(-e / c, 0, 1);
                }
            } else if (t < 0) {
                s = clamp(-d / a, 0, 1);
                t = 0;
            } else {
                double invDet = 1 / det;
                s *= invDet;
                t *= invDet;
            }
        } else {
            if (s < 0) {
                double tmp0 = b + d;
                double tmp1 = c + e;
                if (tmp1 > tmp0) {
                    double numer = tmp1 - tmp0;
                    double denom = a - 2 * b + c;
                    s = clamp(numer / denom, 0, 1);
                    t = 1 - s;
                } else {
                    t = clamp(-e / c, 0, 1);
                    s = 0;
                }
            } else if (t < 0) {
                if (a + d > b + e) {
                    double numer = c + e - b - d;
                    double denom = a - 2 * b + c;
                    s = clamp(numer / denom, 0, 1);
                    t = 1 - s;
                } else {
                    s = clamp(-e / c, 0, 1);
                    t = 0;
                }
            } else {
                double numer = c + e - b - d;
                double denom = a - 2 * b + c;
                s = clamp(numer / denom, 0, 1);
                t = 1 - s;
            }
        }

        return triangle[0] + edge0 * s + edge1 * t;
    }
}

namespace carto { namespace sgre {
    StaticGraph::StaticGraph(std::vector<Node> nodes, std::vector<Edge> edges, std::vector<FeatureProperties> properties, std::vector<Attributes> attributes) :
        _nodes(std::move(nodes)), _edges(std::move(edges)), _featureProperties(std::move(properties)), _attributes(std::move(attributes))
    {
        // Add edge ids to nodes
        linkNodeEdgeIds(_nodes, _edges);

        // Build RTree for faster spatial queries
        cglib::bbox3<double> bounds = cglib::bbox3<double>::smallest();
        std::vector<EdgeId> edgeIds;
        edgeIds.reserve(_edges.size());
        for (std::size_t i = 0; i < _edges.size(); i++) {
            EdgeId edgeId = static_cast<EdgeId>(i);
            const Edge& edge = getEdge(edgeId);
            const Node& node0 = getNode(edge.nodeIds[0]);
            const Node& node1 = getNode(edge.nodeIds[1]);
            bounds.add(node0.points.begin(), node0.points.end());
            bounds.add(node1.points.begin(), node1.points.end());
            edgeIds.push_back(edgeId);
        }
        _rootNode = buildRTree(bounds, edgeIds);
    }

    std::vector<std::pair<Graph::EdgeId, Point>> StaticGraph::findNearestEdgePoint(const Point& pos, const FeatureFilter& filter, const SearchOptions& options) const {
        struct RTreeNodeRecord {
            std::shared_ptr<const RTreeNode> node;
            double dist;

            bool operator < (const RTreeNodeRecord& rec) const { return rec.dist < dist; }
        };

        static constexpr double DIST_EPSILON = 1.0e-6;
        static constexpr double EARTH_RADIUS = 6378137.0;

        double latScale = EARTH_RADIUS * boost::math::constants::pi<double>() / 180.0;
        double lngScale = latScale * std::max(std::cos(pos(1) * boost::math::constants::pi<double>() / 180.0), 0.01);
        cglib::vec3<double> scale(lngScale, latScale, options.zSensitivity);

        double bestDist = std::numeric_limits<double>::infinity();
        std::vector<std::pair<Graph::EdgeId, Point>> bestResults;

        // Do the matching starting from root RTree node. Use priority queue for sorting and rejecting subnodes.
        std::priority_queue<RTreeNodeRecord> nodeQueue;
        Point nearestRootPos = _rootNode->bounds.nearest_point(pos);
        double rootDist = calculateDistance(nearestRootPos, pos, scale);
        nodeQueue.push({ _rootNode, rootDist });
        while (!nodeQueue.empty()) {
            std::shared_ptr<const RTreeNode> node = nodeQueue.top().node;
            double dist = nodeQueue.top().dist;
            nodeQueue.pop();

            if (dist > bestDist + DIST_EPSILON) {
                break;
            }

            // Store subnodes in the priority queue
            for (const std::shared_ptr<const RTreeNode>& subNode : node->subNodes) {
                if (subNode) {
                    Point nearestPos = subNode->bounds.nearest_point(pos);
                    double dist = calculateDistance(nearestPos, pos, scale);
                    nodeQueue.push({ subNode, dist });
                }
            }

            // Do slow matching for edges store in this RTree node
            for (EdgeId edgeId : node->edgeIds) {
                const Edge& edge = getEdge(edgeId);

                // Apply filter?
                if (!filter.empty()) {
                    if (edge.featureId == FeatureId(-1)) {
                        continue;
                    }
                    const FeatureProperties& properties = getFeatureProperties(edge.featureId);
                    if (!properties.is<picojson::object>()) {
                        continue;
                    }

                    auto it = filter.begin();
                    for (; it != filter.end(); it++) {
                        if (!properties.contains(it->first) || properties.get(it->first) != it->second) {
                            break;
                        }
                    }
                    if (it != filter.end()) {
                        continue;
                    }
                }

                if (auto closestPos = findNearestEdgePoint(edge, pos, scale)) {
                    double dist = calculateDistance(*closestPos, pos, scale);
                    if (dist <= bestDist + DIST_EPSILON) {
                        if (dist + DIST_EPSILON < bestDist) {
                            bestDist = dist;
                            bestResults.clear();
                        }
                        bestResults.emplace_back(edgeId, *closestPos);
                    }
                }
            }
        }

        return bestResults;
    }

    boost::optional<Point> StaticGraph::findNearestEdgePoint(const Edge& edge, const Point& pos, const cglib::vec3<double>& scale) const {
        const Node& node0 = getNode(edge.nodeIds[0]);
        const Node& node1 = getNode(edge.nodeIds[1]);

        // First find i0 and i1 for node points such that P[i0] == P[i1] (find the shared point of triangle edges)
        int i0 = 0, i1 = 0;
        for (int i = 0; i < 2; i++) {
            if (node0.points[i] == node1.points[0]) {
                i0 = i; i1 = 0;
                break;
            } else if (node0.points[i] == node1.points[1]) {
                i0 = i; i1 = 1;
                break;
            }
        }

        // Convert WGS84 coordinates to local orthonormal basis for simpler processing
        Point posScaled = cglib::pointwise_product(pos, scale);
        Point node0PointsScaled[2] = { cglib::pointwise_product(node0.points[0], scale), cglib::pointwise_product(node0.points[1], scale) };
        Point node1PointsScaled[2] = { cglib::pointwise_product(node1.points[0], scale), cglib::pointwise_product(node1.points[1], scale) };

        cglib::vec3<double> invScale(1.0 / scale(0), 1.0 / scale(1), (scale(2) != 0 ? 1.0 / scale(2) : std::numeric_limits<double>::max()));

        // Handle the edge based on its search criteria. Some cases are very tricky and the code depends on the graph builder.
        switch (edge.searchCriteria) {
        case SearchCriteria::NONE:
            return boost::optional<Point>();
        case SearchCriteria::VERTEX:
            if (node0.nodeFlags & NodeFlags::GEOMETRY_VERTEX) {
                if (node1.nodeFlags & NodeFlags::GEOMETRY_VERTEX) {
                    return (cglib::norm(posScaled - node0PointsScaled[i0]) < cglib::norm(posScaled - node1PointsScaled[i1]) ? node0.points[i0] : node1.points[i1]);
                }
                return node0.points[i0];
            } else if (node1.nodeFlags & NodeFlags::GEOMETRY_VERTEX) {
                return node1.points[i1];
            }
            return boost::optional<Point>();
        case SearchCriteria::FIRST_LAST_VERTEX:
            if (node0.nodeFlags & NodeFlags::ENDPOINT_VERTEX) {
                if (node1.nodeFlags & NodeFlags::ENDPOINT_VERTEX) {
                    return (cglib::norm(posScaled - node0PointsScaled[i0]) < cglib::norm(posScaled - node1PointsScaled[i1]) ? node0.points[i0] : node1.points[i1]);
                }
                return node0.points[i0];
            } else if (node1.nodeFlags & NodeFlags::ENDPOINT_VERTEX) {
                return node1.points[i1];
            }
            return boost::optional<Point>();
        case SearchCriteria::EDGE:
            if (edge.edgeFlags & EdgeFlags::GEOMETRY_EDGE) {
                if (pointOnLine(std::array<cglib::vec3<double>, 2> {{ node0.points[1 - i0], node1.points[1 - i1] }}, pos)) {
                    return pos;
                }
                Point closestScaled = closestPointOnLine(std::array<cglib::vec3<double>, 2> {{ node0PointsScaled[1 - i0], node1PointsScaled[1 - i1] }}, posScaled);
                return cglib::pointwise_product(closestScaled, invScale);
            }
            return boost::optional<Point>();
        case SearchCriteria::SURFACE:
            if (node0.points[i0] == node1.points[i1]) {
                if (pointInsideTriangle(std::array<cglib::vec3<double>, 3> {{ node0.points[i0], node0.points[1 - i0], node1.points[1 - i1] }}, pos)) {
                    return pos;
                }
                Point closestScaled = closestPointOnTriangle(std::array<cglib::vec3<double>, 3> {{ node0PointsScaled[i0], node0PointsScaled[1 - i0], node1PointsScaled[1 - i1] }}, posScaled);
                return cglib::pointwise_product(closestScaled, invScale);
            } else {
                if (pointOnLine(std::array<cglib::vec3<double>, 2> {{ node0.points[0], node1.points[0] }}, pos)) {
                    return pos;
                }
                Point closestScaled = closestPointOnLine(std::array<cglib::vec3<double>, 2> {{ node0PointsScaled[0], node1PointsScaled[0] }}, posScaled);
                return cglib::pointwise_product(closestScaled, invScale);
            }
        }

        return boost::optional<Point>();
    }

    std::shared_ptr<StaticGraph::RTreeNode> StaticGraph::buildRTree(const cglib::bbox3<double>& bounds, std::vector<EdgeId> edgeIds) const {
        static const std::size_t SPLIT_THRESHOLD = 4;

        // Create non-leaf nodes?
        if (edgeIds.size() > SPLIT_THRESHOLD) {
            cglib::bbox3<double> splitBounds[3][2];
            std::vector<EdgeId> splitEdgeIds[3][2];
            for (int dim = 0; dim < 3; dim++) {
                splitBounds[dim][0] = splitBounds[dim][1] = cglib::bbox3<double>::smallest();
            }

            // Do simple splitting by comparing the center of each edge to the center of the parent
            cglib::vec3<double> center = bounds.center();
            for (EdgeId edgeId : edgeIds) {
                const Edge& edge = getEdge(edgeId);
                const Node& node0 = getNode(edge.nodeIds[0]);
                const Node& node1 = getNode(edge.nodeIds[1]);
                cglib::bbox3<double> edgeBounds = cglib::bbox3<double>::smallest();
                edgeBounds.add(node0.points.begin(), node0.points.end());
                edgeBounds.add(node1.points.begin(), node1.points.end());
                for (int dim = 0; dim < 3; dim++) {
                    int idx = edgeBounds.center()(dim) < center(dim) ? 0 : 1;
                    splitBounds[dim][idx].add(edgeBounds);
                    splitEdgeIds[dim][idx].push_back(edgeId);
                }
            }

            // Find best splitting dimension (best means 'most balanced' by the number of edges here)
            int bestDim = 0;
            for (int dim = 1; dim < 3; dim++) {
                if (std::min(splitEdgeIds[dim][0].size(), splitEdgeIds[dim][1].size()) > std::min(splitEdgeIds[bestDim][0].size(), splitEdgeIds[bestDim][1].size())) {
                    bestDim = dim;
                }
            }

            // If splitting succeeded (resulting subtrees contain enough elements), recurse
            if (splitEdgeIds[bestDim][0].size() > SPLIT_THRESHOLD / 4 && splitEdgeIds[bestDim][1].size() > SPLIT_THRESHOLD / 4) {
                auto node = std::make_shared<RTreeNode>();
                node->bounds = bounds;
                node->subNodes[0] = buildRTree(splitBounds[bestDim][0], std::move(splitEdgeIds[bestDim][0]));
                node->subNodes[1] = buildRTree(splitBounds[bestDim][1], std::move(splitEdgeIds[bestDim][1]));
                return node;
            }
        }

        // Create final leaf node
        auto node = std::make_shared<RTreeNode>();
        node->bounds = bounds;
        node->edgeIds = std::move(edgeIds);
        return node;
    }

    void StaticGraph::linkNodeEdgeIds(std::vector<Node>& nodes, const std::vector<Edge>& edges) {
        for (std::size_t i = 0; i < nodes.size(); i++) {
            nodes[i].edgeIds.reserve(3); // 3 should be optimal in most cases
        }
        for (std::size_t i = 0; i < edges.size(); i++) {
            const Edge& edge = edges[i];
            Node& node0 = nodes.at(edge.nodeIds[0]);
            node0.edgeIds.push_back(static_cast<EdgeId>(i));
        }
    }

    double StaticGraph::calculateDistance(const Point& pos0, const Point& pos1, const cglib::vec3<double>& scale) {
        Point pos0Scaled = cglib::pointwise_product(pos0, scale);
        Point pos1Scaled = cglib::pointwise_product(pos1, scale);
        return cglib::length(pos0Scaled - pos1Scaled);
    }

    Graph::NodeId DynamicGraph::getNodeIdRangeEnd() const {
        NodeId nodeIdRangeEnd = _staticGraph->getNodeIdRangeEnd();
        for (const std::pair<NodeId, Node>& nodePair : _nodes) {
            if (nodePair.first >= nodeIdRangeEnd) {
                nodeIdRangeEnd = nodePair.first + 1;
            }
        }
        return nodeIdRangeEnd;
    }

    const Graph::Node& DynamicGraph::getNode(NodeId nodeId) const {
        auto it = _nodes.find(nodeId);
        if (it != _nodes.end()) {
            return it->second;
        }
        return _staticGraph->getNode(nodeId);
    }

    const Graph::Edge& DynamicGraph::getEdge(EdgeId edgeId) const {
        if (edgeId < _staticGraph->getEdgeIdRangeEnd()) {
            return _staticGraph->getEdge(edgeId);
        }
        return _edges.at(edgeId - _staticGraph->getEdgeIdRangeEnd());
    }

    const Graph::FeatureProperties& DynamicGraph::getFeatureProperties(FeatureId featureId) const {
        if (featureId < _staticGraph->getFeatureIdRangeEnd()) {
            return _staticGraph->getFeatureProperties(featureId);
        }
        return _featureProperties.at(featureId - _staticGraph->getFeatureIdRangeEnd());
    }

    const Graph::Attributes& DynamicGraph::getAttributes(AttributesId attribsId) const {
        if (attribsId < _staticGraph->getAttributesIdRangeEnd()) {
            return _staticGraph->getAttributes(attribsId);
        }
        return _attributes.at(attribsId - _staticGraph->getAttributesIdRangeEnd());
    }

    void DynamicGraph::reset() {
        _nodes.clear();
        _edges.clear();
        _featureProperties.clear();
        _attributes.clear();
    }

    Graph::NodeId DynamicGraph::addNode(Node node) {
        NodeId nodeId = getNodeIdRangeEnd();
        _nodes[nodeId] = std::move(node);
        return nodeId;
    }

    Graph::EdgeId DynamicGraph::addEdge(Edge edge) {
        EdgeId edgeId = getEdgeIdRangeEnd();
        Node node0 = getNode(edge.nodeIds[0]);
        node0.edgeIds.push_back(edgeId);
        _nodes[edge.nodeIds[0]] = std::move(node0);
        _edges.push_back(std::move(edge));
        return edgeId;
    }

    Graph::FeatureId DynamicGraph::addFeature(FeatureProperties properties) {
        FeatureId featureId = getFeatureIdRangeEnd();
        _featureProperties.push_back(std::move(properties));
        return featureId;
    }

    Graph::AttributesId DynamicGraph::addAttributes(Attributes attribs) {
        AttributesId attribsId = getAttributesIdRangeEnd();
        _attributes.push_back(std::move(attribs));
        return attribsId;
    }
} }


$TEMPLATE[gelsd.includes]
#include <boost/numeric/bindings/lapack/auxiliary/ilaenv.hpp>
$TEMPLATE[gelsd.complex.min_size_rwork.args]
MINMN,SMLSIZ,NLVL,NRHS
$TEMPLATE[gelsd.all.extra_variables]
MINMN,SMLSIZ,NLVL
$TEMPLATE[gelsd.all.extra_opt_variables]
MINMN,NLVL
$TEMPLATE[gelsd.all.MINMN.init]
$INTEGER_TYPE minmn = std::min< $INTEGER_TYPE >( size_row(a), size_column(a) );
$TEMPLATE[gelsd.all.SMLSIZ.init]
$INTEGER_TYPE smlsiz = ilaenv(9, "GELSD", "");
$TEMPLATE[gelsd.all.NLVL.init]
$INTEGER_TYPE nlvl = std::max< $INTEGER_TYPE >( static_cast<$INTEGER_TYPE>(std::log(static_cast<real_type>(minmn)/static_cast<real_type>(smlsiz+1))/std::log(2.0)) + 1, 0 );
$TEMPLATE[gelsd.complex.min_size_rwork]
$INTEGER_TYPE smlsiz_plus_one = smlsiz + 1;
return std::max< $INTEGER_TYPE >( 1, 10*minmn + 2*minmn*smlsiz + 8*minmn*nlvl + 3*smlsiz*nrhs + smlsiz_plus_one * smlsiz_plus_one );
$TEMPLATE[gelsd.complex.min_size_work.args]
N, MINMN, NRHS
$TEMPLATE[gelsd.complex.min_size_work]
return std::max< $INTEGER_TYPE >( 1, 2*minmn + std::max< $INTEGER_TYPE >( n, minmn*nrhs ) );
$TEMPLATE[gelsd.all.min_size_iwork.args]
MINMN,NLVL
$TEMPLATE[gelsd.all.min_size_iwork]
return std::max< $INTEGER_TYPE >( 1, 3*minmn*nlvl + 11*minmn );
$TEMPLATE[gelsd.real.min_size_work.args]
MINMN,SMLSIZ, NLVL, NRHS
$TEMPLATE[gelsd.real.min_size_work]
$INTEGER_TYPE smlsiz_plus_one = smlsiz + 1;
return std::max< $INTEGER_TYPE >( 1, 12*minmn + 2*minmn*smlsiz + 8*minmn*nlvl + minmn*nrhs + smlsiz_plus_one * smlsiz_plus_one );
$TEMPLATE[gelsd.real.A.io]
input;output
$TEMPLATE[end]


// Ben Martin
// October 11, 2005

// This currently works only on undirected graphs...
// Graph must model Adjacency Graph, Incidence Graph, VertexListGraph

#ifndef BOOST_GRAPH_SPECTRAL_EMBEDDING_LAYOUT_HPP
#define BOOST_GRAPH_SPECTRAL_EMBEDDING_LAYOUT_HPP

//#include <boost/graph/graph_traits.hpp>
//#include <boost/graph/named_function_params.hpp>
//#include <boost/graph/properties.hpp>
//#include <boost/vector_property_map.hpp>

#include <boost/graph/spectrum.hpp>
#include <utility> // for pair
#include <vector>

namespace boost {

  template <typename Graph, typename PositionMap>
  void spectral_embedding_layout(Graph& g, PositionMap pos_map, double rel_tol = 100, double abs_tol = 1000) {

    using std::vector;
    using std::pair;

    // typedef std::vector<double> Vector;
    // typedef std::vector<std::vector<double> > Matrix;
    typedef typename Graph::vertex_iterator VertexIterator;

    int N = num_vertices(g);
    //    std::vector<Vector> evecs(3);
    std::vector<std::vector<double> > evecs(3);
    evecs[0] = *(new std::vector<double>(N));
    evecs[1] = *(new std::vector<double>(N));
    evecs[2] = *(new std::vector<double>(N));
    //    spectrum<Graph, Vector>(g, 1, 3, evecs, rel_tol, abs_tol);
    //    spectrum<Graph, Matrix >(g, 1, 3, &evecs, rel_tol, abs_tol);
    spectrum(g, 1, 3, evecs, rel_tol, abs_tol);

    int i;
    VertexIterator v, vs, ve;
    std::pair<VertexIterator, VertexIterator> p;
    p = vertices(g);
    vs = p.first;
    ve = p.second;

    i = 0;
    for (v = vs; v != ve; ++v)
    {
      pos_map[*v][0] = evecs[0][i];
      i++;
    }
    i = 0;
    for (v = vs; v != ve; ++v)
    {
      pos_map[*v][1] = evecs[1][i];
      i++;
    }
    i = 0;
    for (v = vs; v != ve; ++v)
    {
      pos_map[*v][2] = evecs[2][i];
      i++;
    }

  } // end spectral_embedding_layout()

} // end namespace boost

#endif // BOOST_GRAPH_SPECTRAL_EMBEDDING_LAYOUT_HPP


#include "Common/Common.h"
#include "Demos/Visualization/MiniGL.h"
#include "Demos/Visualization/Selection.h"
#include "GL/glut.h"
#include "Demos/Simulation/TimeManager.h"
#include <Eigen/Dense>
#include "Demos/Simulation/SimulationModel.h"
#include "Demos/Simulation/TimeStepController.h"
#include <iostream>
#include "Demos/Visualization/Visualization.h"
#include "Demos/Utils/Logger.h"
#include "Demos/Utils/Timing.h"
#include "Demos/Utils/FileSystem.h"

// Enable memory leak detection
#if defined(_DEBUG) && !defined(EIGEN_ALIGN)
	#define new DEBUG_NEW
#endif

INIT_TIMING
INIT_LOGGING

using namespace PBD;
using namespace Eigen;
using namespace std;

void timeStep ();
void buildModel ();
void createMesh();
void render ();
void cleanup();
void reset();
void initShader();
void selection(const Eigen::Vector2i &start, const Eigen::Vector2i &end);
void TW_CALL setTimeStep(const void *value, void *clientData);
void TW_CALL getTimeStep(void *value, void *clientData);
void TW_CALL setStiffness(const void *value, void *clientData);
void TW_CALL getStiffness(void *value, void *clientData);
void TW_CALL setPoissonRatio(const void *value, void *clientData);
void TW_CALL getPoissonRatio(void *value, void *clientData);
void TW_CALL setNormalizeStretch(const void *value, void *clientData);
void TW_CALL getNormalizeStretch(void *value, void *clientData);
void TW_CALL setNormalizeShear(const void *value, void *clientData);
void TW_CALL getNormalizeShear(void *value, void *clientData);
void TW_CALL setSimulationMethod(const void *value, void *clientData);
void TW_CALL getSimulationMethod(void *value, void *clientData);
void TW_CALL setVelocityUpdateMethod(const void *value, void *clientData);
void TW_CALL getVelocityUpdateMethod(void *value, void *clientData);

SimulationModel model;
TimeStepController sim;

const unsigned int width = 30;
const unsigned int depth = 5;
const unsigned int height = 5;
short simulationMethod = 2;
bool doPause = true;
std::vector<unsigned int> selectedParticles;
Vector3r oldMousePos;
Shader *shader;
string exePath;
string dataPath;

// main
int main( int argc, char **argv )
{
	REPORT_MEMORY_LEAKS

	std::string logPath = FileSystem::normalizePath(FileSystem::getProgramPath() + "/log");
	FileSystem::makeDirs(logPath);
	logger.addSink(unique_ptr<ConsoleSink>(new ConsoleSink(LogLevel::INFO)));
	logger.addSink(unique_ptr<FileSink>(new FileSink(LogLevel::DEBUG, logPath + "/PBD.log")));

	exePath = FileSystem::getFilePath(argv[0]);
	dataPath = exePath + "/" + std::string(PBD_DATA_PATH);

	// OpenGL
	MiniGL::init (argc, argv, 1024, 768, 0, 0, "Bar demo");
	MiniGL::initLights ();
	MiniGL::initTexture();
	MiniGL::setClientIdleFunc (50, timeStep);
	MiniGL::setKeyFunc(0, 'r', reset);
	MiniGL::setSelectionFunc(selection);
	initShader();

	buildModel ();

	MiniGL::setClientSceneFunc(render);
	MiniGL::setViewport (40.0f, 0.1f, 500.0f, Vector3r (5.0, 10.0, 30.0), Vector3r (5.0, 0.0, 0.0));

	TwAddVarRW(MiniGL::getTweakBar(), "Pause", TW_TYPE_BOOLCPP, &doPause, " label='Pause' group=Simulation key=SPACE ");
	TwAddVarCB(MiniGL::getTweakBar(), "TimeStepSize", TW_TYPE_REAL, setTimeStep, getTimeStep, &model, " label='Time step size'  min=0.0 max = 0.1 step=0.001 precision=4 group=Simulation ");
	TwType enumType = TwDefineEnum("VelocityUpdateMethodType", NULL, 0);
	TwAddVarCB(MiniGL::getTweakBar(), "VelocityUpdateMethod", enumType, setVelocityUpdateMethod, getVelocityUpdateMethod, &sim, " label='Velocity update method' enum='0 {First Order Update}, 1 {Second Order Update}' group=Simulation");
	TwType enumType2 = TwDefineEnum("SimulationMethodType", NULL, 0);
	TwAddVarCB(MiniGL::getTweakBar(), "SimulationMethod", enumType2, setSimulationMethod, getSimulationMethod, &simulationMethod,
			" label='Simulation method' enum='0 {None}, 1 {Volume constraints}, 2 {FEM based PBD}, 3 {Strain based dynamics (no inversion handling)}, 4 {Shape matching (no inversion handling)}' group=Simulation");
	TwAddVarCB(MiniGL::getTweakBar(), "Stiffness", TW_TYPE_REAL, setStiffness, getStiffness, &model, " label='Stiffness'  min=0.0 step=0.1 precision=4 group='Simulation' ");
	TwAddVarCB(MiniGL::getTweakBar(), "PoissonRatio", TW_TYPE_REAL, setPoissonRatio, getPoissonRatio, &model, " label='Poisson ratio XY'  min=0.0 step=0.1 precision=4 group='Simulation' ");
	TwAddVarCB(MiniGL::getTweakBar(), "NormalizeStretch", TW_TYPE_BOOL32, setNormalizeStretch, getNormalizeStretch, &model, " label='Normalize stretch' group='Strain based dynamics' ");
	TwAddVarCB(MiniGL::getTweakBar(), "NormalizeShear", TW_TYPE_BOOL32, setNormalizeShear, getNormalizeShear, &model, " label='Normalize shear' group='Strain based dynamics' ");

	glutMainLoop ();

	cleanup ();

	Timing::printAverageTimes();

	return 0;
}

void initShader()
{
	std::string vertFile = dataPath + "/shaders/vs_smooth.glsl";
	std::string fragFile = dataPath + "/shaders/fs_smooth.glsl";
	shader = MiniGL::createShader(vertFile, "", fragFile);

	if (shader == NULL)
		return;

	shader->begin();
	shader->addUniform("modelview_matrix");
	shader->addUniform("projection_matrix");
	shader->addUniform("surface_color");
	shader->addUniform("shininess");
	shader->addUniform("specular_factor");
	shader->end();
}

void cleanup()
{
	delete TimeManager::getCurrent();
	delete shader;
}

void reset()
{
	Timing::printAverageTimes();
	Timing::reset();
	model.reset();
	sim.reset();
	TimeManager::getCurrent()->setTime(0.0);

	model.cleanup();
	buildModel();
}

void mouseMove(int x, int y)
{
	Vector3r mousePos;
	MiniGL::unproject(x, y, mousePos);
	const Vector3r diff = mousePos - oldMousePos;

	TimeManager *tm = TimeManager::getCurrent();
	const Real h = tm->getTimeStepSize();

	ParticleData &pd = model.getParticles();
	for (unsigned int j = 0; j < selectedParticles.size(); j++)
	{
		pd.getVelocity(selectedParticles[j]) += 5.0*diff/h;
	}
	oldMousePos = mousePos;
}

void selection(const Eigen::Vector2i &start, const Eigen::Vector2i &end)
{
	std::vector<unsigned int> hits;
	selectedParticles.clear();
	ParticleData &pd = model.getParticles();
	Selection::selectRect(start, end, &pd.getPosition(0), &pd.getPosition(pd.size() - 1), selectedParticles);
	if (selectedParticles.size() > 0)
		MiniGL::setMouseMoveFunc(GLUT_MIDDLE_BUTTON, mouseMove);
	else
		MiniGL::setMouseMoveFunc(-1, NULL);

	MiniGL::unproject(end[0], end[1], oldMousePos);
}

void timeStep ()
{
	if (doPause)
		return;

	// Simulation code
	for (unsigned int i = 0; i < 8; i++)
		sim.step(model);

	for (unsigned int i = 0; i < model.getTetModels().size(); i++)
	{
		model.getTetModels()[i]->updateMeshNormals(model.getParticles());
	}
}

void buildModel ()
{
	TimeManager::getCurrent ()->setTimeStepSize (0.005);

	createMesh();
}

void renderTetModels()
{
	// Draw simulation model

	const ParticleData &pd = model.getParticles();
	float surfaceColor[4] = { 0.2f, 0.5f, 1.0f, 1 };

	if (shader)
	{
		shader->begin();
		glUniform3fv(shader->getUniform("surface_color"), 1, surfaceColor);
		glUniform1f(shader->getUniform("shininess"), 5.0f);
		glUniform1f(shader->getUniform("specular_factor"), 0.2f);

		GLfloat matrix[16];
		glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
		glUniformMatrix4fv(shader->getUniform("modelview_matrix"), 1, GL_FALSE, matrix);
		GLfloat pmatrix[16];
		glGetFloatv(GL_PROJECTION_MATRIX, pmatrix);
		glUniformMatrix4fv(shader->getUniform("projection_matrix"), 1, GL_FALSE, pmatrix);
	}

	for (unsigned int i = 0; i < model.getTetModels().size(); i++)
	{
		TetModel *tetModel = model.getTetModels()[i];
		const IndexedFaceMesh &surfaceMesh = tetModel->getSurfaceMesh();
		Visualization::drawMesh(pd, surfaceMesh, tetModel->getIndexOffset(), surfaceColor);
	}
	if (shader)
		shader->end();
}

void render ()
{
	MiniGL::coordinateSystem();

	// Draw simulation model
	renderTetModels();

	float red[4] = { 0.8f, 0.0f, 0.0f, 1 };
	const ParticleData &pd = model.getParticles();
	for (unsigned int j = 0; j < selectedParticles.size(); j++)
	{
		MiniGL::drawSphere(pd.getPosition(selectedParticles[j]), 0.08f, red);
	}

	MiniGL::drawTime( TimeManager::getCurrent ()->getTime ());
}

void createMesh()
{
	Vector3r points[width*height*depth];
	for (unsigned int i = 0; i < width; i++)
	{
		for (unsigned int j = 0; j < height; j++)
		{
			for (unsigned int k = 0; k < depth; k++)
			{
				points[i*height*depth + j*depth + k] = 0.3*Vector3r((Real)i, (Real)j, (Real)k);
			}
		}
	}

	vector<unsigned int> indices;
	for (unsigned int i = 0; i < width - 1; i++)
	{
		for (unsigned int j = 0; j < height - 1; j++)
		{
			for (unsigned int k = 0; k < depth - 1; k++)
			{
				// For each block, the 8 corners are numerated as:
				//     4*-----*7
				//     /|    /|
				//    / |   / |
				//  5*-----*6 |
				//   | 0*--|--*3
				//   | /   | /
				//   |/    |/
				//  1*-----*2
				unsigned int p0 = i*height*depth + j*depth + k;
				unsigned int p1 = p0 + 1;
				unsigned int p3 = (i + 1)*height*depth + j*depth + k;
				unsigned int p2 = p3 + 1;
				unsigned int p7 = (i + 1)*height*depth + (j + 1)*depth + k;
				unsigned int p6 = p7 + 1;
				unsigned int p4 = i*height*depth + (j + 1)*depth + k;
				unsigned int p5 = p4 + 1;

				// Ensure that neighboring tetras are sharing faces
				if ((i + j + k) % 2 == 1)
				{
					indices.push_back(p2); indices.push_back(p1); indices.push_back(p6); indices.push_back(p3);
					indices.push_back(p6); indices.push_back(p3); indices.push_back(p4); indices.push_back(p7);
					indices.push_back(p4); indices.push_back(p1); indices.push_back(p6); indices.push_back(p5);
					indices.push_back(p3); indices.push_back(p1); indices.push_back(p4); indices.push_back(p0);
					indices.push_back(p6); indices.push_back(p1); indices.push_back(p4); indices.push_back(p3);
				}
				else
				{
					indices.push_back(p0); indices.push_back(p2); indices.push_back(p5); indices.push_back(p1);
					indices.push_back(p7); indices.push_back(p2); indices.push_back(p0); indices.push_back(p3);
					indices.push_back(p5); indices.push_back(p2); indices.push_back(p7); indices.push_back(p6);
					indices.push_back(p7); indices.push_back(p0); indices.push_back(p5); indices.push_back(p4);
					indices.push_back(p0); indices.push_back(p2); indices.push_back(p7); indices.push_back(p5);
				}
			}
		}
	}
	model.addTetModel(width*height*depth, (unsigned int)indices.size() / 4u, points, indices.data());

	ParticleData &pd = model.getParticles();
	for (unsigned int i = 0; i < pd.getNumberOfParticles(); i++)
	{
		pd.setMass(i, 1.0);
	}
	for (unsigned int i = 0; i < 1; i++)
	{
		for (unsigned int j = 0; j < height; j++)
		{
			for (unsigned int k = 0; k < depth; k++)
				pd.setMass(i*height*depth + j*depth + k, 0.0);
		}
	}

	// init constraints
	for (unsigned int cm = 0; cm < model.getTetModels().size(); cm++)
	{
		const unsigned int nTets = model.getTetModels()[cm]->getParticleMesh().numTets();
		const unsigned int *tets = model.getTetModels()[cm]->getParticleMesh().getTets().data();
		const IndexedTetMesh::VertexTets *vTets = model.getTetModels()[cm]->getParticleMesh().getVertexTets().data();
		if (simulationMethod == 1)
		{
			const unsigned int offset = model.getTetModels()[cm]->getIndexOffset();
			const unsigned int nEdges = model.getTetModels()[cm]->getParticleMesh().numEdges();
			const IndexedTetMesh::Edge *edges = model.getTetModels()[cm]->getParticleMesh().getEdges().data();
			for (unsigned int i = 0; i < nEdges; i++)
			{
				const unsigned int v1 = edges[i].m_vert[0] + offset;
				const unsigned int v2 = edges[i].m_vert[1] + offset;

				model.addDistanceConstraint(v1, v2);
			}

			for (unsigned int i = 0; i < nTets; i++)
			{
				const unsigned int v1 = tets[4 * i];
				const unsigned int v2 = tets[4 * i + 1];
				const unsigned int v3 = tets[4 * i + 2];
				const unsigned int v4 = tets[4 * i + 3];

				model.addVolumeConstraint(v1, v2, v3, v4);
			}
		}
		else if (simulationMethod == 2)
		{
			TetModel::ParticleMesh &mesh = model.getTetModels()[cm]->getParticleMesh();
			for (unsigned int i = 0; i < nTets; i++)
			{
				const unsigned int v1 = tets[4 * i];
				const unsigned int v2 = tets[4 * i + 1];
				const unsigned int v3 = tets[4 * i + 2];
				const unsigned int v4 = tets[4 * i + 3];

				model.addFEMTetConstraint(v1, v2, v3, v4);
			}
		}
		else if (simulationMethod == 3)
		{
			TetModel::ParticleMesh &mesh = model.getTetModels()[cm]->getParticleMesh();
			for (unsigned int i = 0; i < nTets; i++)
			{
				const unsigned int v1 = tets[4 * i];
				const unsigned int v2 = tets[4 * i + 1];
				const unsigned int v3 = tets[4 * i + 2];
				const unsigned int v4 = tets[4 * i + 3];

				model.addStrainTetConstraint(v1, v2, v3, v4);
			}
		}
		else if (simulationMethod == 4)
		{
			TetModel::ParticleMesh &mesh = model.getTetModels()[cm]->getParticleMesh();
			for (unsigned int i = 0; i < nTets; i++)
			{
				const unsigned int v[4] = { tets[4 * i], tets[4 * i + 1], tets[4 * i + 2], tets[4 * i + 3] };
				// Important: Divide position correction by the number of clusters
				// which contain the vertex.
				const unsigned int nc[4] = { vTets[v[0]].m_numTets, vTets[v[1]].m_numTets, vTets[v[2]].m_numTets, vTets[v[3]].m_numTets };
				model.addShapeMatchingConstraint(4, v, nc);
			}
		}
		model.getTetModels()[cm]->updateMeshNormals(pd);
	}

	LOG_INFO << "Number of tets: " << indices.size() / 4;
	LOG_INFO << "Number of vertices: " << width*height*depth;

}

void TW_CALL setTimeStep(const void *value, void *clientData)
{
	const Real val = *(const Real *)(value);
	TimeManager::getCurrent()->setTimeStepSize(val);
}

void TW_CALL getTimeStep(void *value, void *clientData)
{
	*(Real *)(value) = TimeManager::getCurrent()->getTimeStepSize();
}

void TW_CALL setStiffness(const void *value, void *clientData)
{
	const Real val = *(const Real *)(value);
	((SimulationModel*) clientData)->setSolidStiffness(val);
}

void TW_CALL getStiffness(void *value, void *clientData)
{
	*(Real *)(value) = ((SimulationModel*)clientData)->getSolidStiffness();
}

void TW_CALL setPoissonRatio(const void *value, void *clientData)
{
	const Real val = *(const Real *)(value);
	((SimulationModel*)clientData)->setSolidPoissonRatio(val);
}

void TW_CALL getPoissonRatio(void *value, void *clientData)
{
	*(Real *)(value) = ((SimulationModel*)clientData)->getSolidPoissonRatio();
}

void TW_CALL setNormalizeStretch(const void *value, void *clientData)
{
	const bool val = *(const bool *)(value);
	((SimulationModel*)clientData)->setSolidNormalizeStretch(val);
}

void TW_CALL getNormalizeStretch(void *value, void *clientData)
{
	*(bool *)(value) = ((SimulationModel*)clientData)->getSolidNormalizeStretch();
}

void TW_CALL setNormalizeShear(const void *value, void *clientData)
{
	const bool val = *(const bool *)(value);
	((SimulationModel*)clientData)->setSolidNormalizeShear(val);
}

void TW_CALL getNormalizeShear(void *value, void *clientData)
{
	*(bool *)(value) = ((SimulationModel*)clientData)->getSolidNormalizeShear();
}

void TW_CALL setSimulationMethod(const void *value, void *clientData)
{
	const short val = *(const short *)(value);
	*((short*)clientData) = val;
	reset();
}

void TW_CALL getSimulationMethod(void *value, void *clientData)
{
	*(short *)(value) = *((short*)clientData);
}

void TW_CALL setVelocityUpdateMethod(const void *value, void *clientData)
{
	const short val = *(const short *)(value);
	((TimeStepController*)clientData)->setVelocityUpdateMethod((unsigned int)val);
}

void TW_CALL getVelocityUpdateMethod(void *value, void *clientData)
{
	*(short *)(value) = (short)((TimeStepController*)clientData)->getVelocityUpdateMethod();
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_COTD_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_COTD_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-trigonometric
    Function object implementing cotd capabilities

    cotangent of input in degree.

    @par Semantic:

    For every parameter of floating type T

    @code
    T r = cotd(x);
    @endcode

    is similar to:

    @code
    T r = cot(inrad(x));
    @endcode

    As most other trigonometric function cotd can be called with a second optional parameter
    which is a tag on speed and accuracy (see @ref cos for further details)

    @see cos, sin, tan, cot, cotpi

  **/
  const boost::dispatch::functor<tag::cotd_> cotd = {};
} }
#endif

#include <boost/simd/function/scalar/cotd.hpp>
#include <boost/simd/function/simd/cotd.hpp>

#endif


//  cycle_count.cpp  ----------------------------------------------------------//

//  Copyright 2008 Howard Hinnant
//  Copyright 2008 Beman Dawes
//  Copyright 2009 Vicente J. Botet Escriba

//  Distributed under the Boost Software License, Version 1.0.
//  See http://www.boost.org/LICENSE_1_0.txt

/*
This code was extracted by Vicente J. Botet Escriba from Beman Dawes time2_demo.cpp which
was derived by Beman Dawes from Howard Hinnant's time2_demo prototype.
Many thanks to Howard for making his code available under the Boost license.
The original code was modified to conform to Boost conventions and to section
20.9 Time utilities [time] of the C++ committee's working paper N2798.
See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf.

time2_demo contained this comment:

    Much thanks to Andrei Alexandrescu,
                   Walter Brown,
                   Peter Dimov,
                   Jeff Garland,
                   Terry Golubiewski,
                   Daniel Krugler,
                   Anthony Williams.
*/

#include <boost/chrono/chrono.hpp>
#include <boost/type_traits.hpp>

#include <iostream>

using namespace boost::chrono;

template <long long speed>
struct cycle_count
{
    typedef typename boost::ratio_multiply<boost::ratio<speed>, boost::mega>::type frequency;  // Mhz
    typedef typename boost::ratio_divide<boost::ratio<1>, frequency>::type period;
    typedef long long rep;
    typedef boost::chrono::duration<rep, period> duration;
    typedef boost::chrono::time_point<cycle_count> time_point;

    static time_point now()
    {
        static long long tick = 0;
        // return exact cycle count
        return time_point(duration(++tick));  // fake access to clock cycle count
    }
};

template <long long speed>
struct approx_cycle_count
{
    static const long long frequency = speed * 1000000;  // MHz
    typedef nanoseconds duration;
    typedef duration::rep rep;
    typedef duration::period period;
    static const long long nanosec_per_sec = period::den;
    typedef boost::chrono::time_point<approx_cycle_count> time_point;

    static time_point now()
    {
        static long long tick = 0;
        // return cycle count as an approximate number of nanoseconds
        // compute as if nanoseconds is only duration in the std::lib
        return time_point(duration(++tick * nanosec_per_sec / frequency));
    }
};

void cycle_count_delay()
{
    {
    typedef cycle_count<400> clock;
    std::cout << "\nSimulated " << clock::frequency::num / boost::mega::num << "MHz clock which has a tick period of "
         << duration<double, boost::nano>(clock::duration(1)).count() << " nanoseconds\n";
    nanoseconds delayns(500);
    clock::duration delay = duration_cast<clock::duration>(delayns);
    std::cout << "delay = " << delayns.count() << " nanoseconds which is " << delay.count() << " cycles\n";
    clock::time_point start = clock::now();
    clock::time_point stop = start + delay;
    while (clock::now() < stop)  // no multiplies or divides in this loop
        ;
    clock::time_point end = clock::now();
    clock::duration elapsed = end - start;
    std::cout << "paused " << elapsed.count() << " cycles ";
    std::cout << "which is " << duration_cast<nanoseconds>(elapsed).count() << " nanoseconds\n";
    }
    {
    typedef approx_cycle_count<400> clock;
    std::cout << "\nSimulated " << clock::frequency / 1000000 << "MHz clock modeled with nanoseconds\n";
    clock::duration delay = nanoseconds(500);
    std::cout << "delay = " << delay.count() << " nanoseconds\n";
    clock::time_point start = clock::now();
    clock::time_point stop = start + delay;
    while (clock::now() < stop) // 1 multiplication and 1 division in this loop
        ;
    clock::time_point end = clock::now();
    clock::duration elapsed = end - start;
    std::cout << "paused " << elapsed.count() << " nanoseconds\n";
    }
    {
    typedef cycle_count<1500> clock;
    std::cout << "\nSimulated " << clock::frequency::num / boost::mega::num << "MHz clock which has a tick period of "
         << duration<double, boost::nano>(clock::duration(1)).count() << " nanoseconds\n";
    nanoseconds delayns(500);
    clock::duration delay = duration_cast<clock::duration>(delayns);
    std::cout << "delay = " << delayns.count() << " nanoseconds which is " << delay.count() << " cycles\n";
    clock::time_point start = clock::now();
    clock::time_point stop = start + delay;
    while (clock::now() < stop)  // no multiplies or divides in this loop
        ;
    clock::time_point end = clock::now();
    clock::duration elapsed = end - start;
    std::cout << "paused " << elapsed.count() << " cycles ";
    std::cout << "which is " << duration_cast<nanoseconds>(elapsed).count() << " nanoseconds\n";
    }
    {
    typedef approx_cycle_count<1500> clock;
    std::cout << "\nSimulated " << clock::frequency / 1000000 << "MHz clock modeled with nanoseconds\n";
    clock::duration delay = nanoseconds(500);
    std::cout << "delay = " << delay.count() << " nanoseconds\n";
    clock::time_point start = clock::now();
    clock::time_point stop = start + delay;
    while (clock::now() < stop) // 1 multiplication and 1 division in this loop
        ;
    clock::time_point end = clock::now();
    clock::duration elapsed = end - start;
    std::cout << "paused " << elapsed.count() << " nanoseconds\n";
    }
}

int main()
{
    cycle_count_delay();
    return 0;
}


#include <boost/algorithm/string.hpp>
#include <boost/multi_array.hpp>

# include "brain.hpp"

BrainModel::BrainModel() {

    /* default values of model constants */
    Cm      = 1.0;
    rho     = 0.107;
    D_white = 0.255;
    D_grey  = 0.051;
    dose    = 1.8;
    alpha   = 0.036;
    beta    = 0.0036;
    s = exp( -alpha*dose -beta*pow(dose,2) );
    t_radio = 15.0;

 /* define the initial position and size of the tumor */
# if defined TEST
    tumor_size = 5;
    tumor_position[0] = 15;
    tumor_position[1] = 15;
    tumor_position[2] = 15;
    fname = "diffusion_test.csv";
# else
    tumor_size = 30;
    tumor_position[0] = 30;
    tumor_position[1] = 93;
    tumor_position[2] = 30;
    fname = "diffusion.csv";
# endif
}

BrainModel::BrainModel(
                    const double Cm,
                    const double rho,
                    const double D_white,
                    const double D_grey,
                    const double alpha,
                    const double beta,
                    const double dose,
                    const int tumor_size,
                    const double t_radio) : BrainModel::BrainModel()
{

    this->Cm = Cm;
    this->rho = rho;
    this->D_white = D_white;
    this->D_grey = D_grey;
    this->dose = dose;
    this->alpha = alpha;
    this->beta = beta;
    this->s = exp( -alpha*dose -beta*pow(dose,2) );
    this->t_radio = t_radio;
    this->tumor_size = tumor_size;

}

BrainModel::~BrainModel() {
    delete this->diffusion;
}

/* read input diffusion coefficients and populates current brain model */
void BrainModel::read()
{

    std::ifstream f;
    std::string line;

    int nx = 0;
    int ny = 0;
    int nz = 0;
    std::vector<double> t_D;

    f.open(fname, std::ifstream::in);
    if( !f.is_open() ) {
        std::cout << " Error opening input file " << std::endl;
        std::cout << " The program will not work! " << std::endl;
        exit(EXIT_FAILURE);
    }

    std::getline(f, line);  /* read the heading */
    while( std::getline(f, line) ) {

        std::vector<std::string> result;
        boost::algorithm::split(result, line, boost::is_any_of(","));

        nx = max(nx, std::stoi(result[0]));
        ny = max(ny, std::stoi(result[1]));
        nz = max(nz, std::stoi(result[2]));
        int D_read = std::stoi(result[3]);

        if(D_read >= this->is_white.first && D_read <= this->is_white.second) {
            t_D.push_back(D_white);
        }
        else if(D_read >= this->is_grey.first && D_read < this->is_grey.second) {
            t_D.push_back(D_grey);
        }
        else {
            t_D.push_back(0.0);
        }

    }
    f.close();

    /* create diffusion coefficient matrix with the values read from input */
    int ind = 0;
    this->diffusion = new boost_array3d_t(boost::extents[nx][ny][nz]);
    const auto sup(this->diffusion->data() + this->diffusion->num_elements());
    for (auto it = this->diffusion->data(); it != sup; ++it) {
        *it = t_D[ind];
        ++ind;
    }

    return;
 }

 boost_array3d_t BrainModel::init_tumor(const int nx, const int ny, const int nz) {

    /* initial dimensions and diffusion coefficient value
     * for the tumor area */
    double init_value = Cm/2.0;
    int dim = tumor_size;
    double radius = dim/2.0;

    /* initial tumor positions */
    int i_start = tumor_position[0];
    int j_start = tumor_position[1];
    int k_start = tumor_position[2];

    boost_array3d_t coeffs(boost::extents[nx][ny][nz]);

    for(int i = 0; i < nx; i++) {
        for(int j = j_start - radius ;j <= j_start + radius; j++) {
            for(int k = k_start - radius;k <= k_start + radius; k++) {

                double ix = (double) i-i_start;
                double iy = (double) j-j_start;
                double iz = (double) k-k_start;

                if(i >= i_start - radius && i <= i_start + radius) {
                    if( sqrt( pow(ix,2) + pow(iy,2) + pow(iz,2) ) < radius )
                        coeffs[i][j][k] = init_value;
                }
            }
        }
    }

   return coeffs;
 }

 double BrainModel::norm_tumor(const int is, const int ie, boost_array3d_t& data) {

    int i,j,k;
    double init_value = Cm/2.0;
    int i_start = tumor_position[0];
    int j_start = tumor_position[1];
    int k_start = tumor_position[2];
    int dim     = tumor_size;
    double radius = dim/2.0;

    double norm = 0.0;

    for(i = is; i < ie; i++) {
        int i_loc = i - is;
        for(j = j_start - radius ;j <= j_start + radius; j++) {
            for(k = k_start - radius;k <= k_start + radius; k++) {

                double ix = (double) i-i_start;
                double iy = (double) j-j_start;
                double iz = (double) k-k_start;

	            if(i >= i_start - 2.0 && i <= i_start + 2.0) {

	                if( sqrt( pow(ix,2) + pow(iy,2) + pow(iz,2) ) < 2.0 )
	                    norm += pow(data[i_loc][j][k], 2);
                }
            }
        }
    }
    norm = sqrt(norm);
    return norm;

 }


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_DIVROUND_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_DIVROUND_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/hierarchy/simd.hpp>
//#include <boost/simd/sdk/meta/is_upgradable.hpp>
#include <boost/simd/function/simd/divides.hpp>
#include <boost/simd/function/simd/groups.hpp>
#include <boost/simd/function/simd/round.hpp>
#include <boost/simd/function/simd/split.hpp>
#include <boost/simd/function/simd/tofloat.hpp>
#include <boost/simd/function/simd/toints.hpp>
#include <boost/simd/function/simd/touints.hpp>

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;
   BOOST_DISPATCH_OVERLOAD(divround_
                             , (typename A0, typename X)
                             , bd::cpu_
                             , bs::pack_<bd::int_<A0>, X>
                             , bs::pack_<bd::int_<A0>, X>
                             )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        using ivtype = bd::upgrade_t<A0>;
        ivtype a0l, a0h, a1l, a1h;
        bs::split(a0, a0l, a0h );
        bs::split(a1, a1l, a1h );
        ivtype d0 = toints(divround(tofloat(a0l), tofloat(a1l)));
        ivtype d1 = toints(divround(tofloat(a0h), tofloat(a1h)));
        return groups(d0, d1);
      }
   };

   BOOST_DISPATCH_OVERLOAD(divround_
                             , (typename A0, typename X)
                             , bd::cpu_
                             , bs::pack_<bd::uint_<A0>, X>
                             , bs::pack_<bd::uint_<A0>, X>
                             )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        using ivtype = bd::upgrade_t<A0>;
        ivtype a0l, a0h, a1l, a1h;
        bs::split(a0, a0l, a0h );
        bs::split(a1, a1l, a1h );
        ivtype d0 = touints(divround(tofloat(a0l), tofloat(a1l)));
        ivtype d1 = touints(divround(tofloat(a0h), tofloat(a1h)));
        return groups(d0, d1);
      }
   };

   BOOST_DISPATCH_OVERLOAD(divround_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::pack_<bd::ints8_<A0>, X>
                          , bs::pack_<bd::ints8_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        using ivtype = bd::upgrade_t<A0>;
        ivtype a0l, a0h, a1l, a1h;
        bs::split(a0, a0l, a0h);
        bs::split(a1, a1l, a1h);
        ivtype d0 = divround(a0l, a1l);
        ivtype d1 = divround(a0h, a1h);
        return groups(d0, d1);
      }
   };

   BOOST_DISPATCH_OVERLOAD(divround_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::pack_<bd::floating_<A0>, X>
                          , bs::pack_<bd::floating_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        return simd::round(a0/a1);
      }
   };

} } }

#endif


#include <Eigen/Core>

#include <geos/geom.h>
#include <geos/opBuffer.h>
#include <geos/opDistance.h>

#include <Pita/Node.hpp>
#include <Pita/Context.hpp>
#include <Pita/Vectorizer.hpp>
#include <Pita/Printer.hpp>

namespace cgl
{
	bool ReadDoublePacked(double& output, const std::string& name, const PackedRecord& record)
	{
		const auto& values = record.values;

		auto it = values.find(name);
		if (it == values.end())
		{
			return false;
		}
		const PackedVal& value = it->second.value;
		if (!IsType<int>(value) && !IsType<double>(value))
		{
			return false;
		}
		output = IsType<int>(value) ? static_cast<double>(As<int>(value)) : As<double>(value);
		return true;
	}

	std::tuple<double, double> ReadVec2Packed(const PackedRecord& record)
	{
		const auto& values = record.values;

		auto itX = values.find("x");
		auto itY = values.find("y");

		Eigen::Vector2d xy(AsDouble(itX->second.value), AsDouble(itY->second.value));

		return std::tuple<double, double>(xy.x(), xy.y());
	}

	std::tuple<double, double> ReadVec2Packed(const PackedRecord& record, const TransformPacked& transform)
	{
		const auto& values = record.values;

		auto itX = values.find("x");
		auto itY = values.find("y");

		Eigen::Vector2d xy;
		xy << AsDouble(itX->second.value), AsDouble(itY->second.value);
		const auto xy_ = transform.product(xy);

		//return std::tuple<double, double>(AsDouble(itX->second.value), AsDouble(itY->second.value));
		return std::tuple<double, double>(xy_.x(), xy_.y());
	}

	Path ReadPathPacked(const PackedRecord& pathRecord)
	{
		Path resultPath;

		resultPath.cs = std::make_unique<gg::CoordinateArraySequence>();
		auto& cs = resultPath.cs;
		auto& distances = resultPath.distances;

		const TransformPacked transform(pathRecord);

		auto itLine = pathRecord.values.find("line");
		if (itLine == pathRecord.values.end())
			CGL_Error("不正な式です");

		const PackedVal lineVal = itLine->second.value;
		if (auto pointListOpt = AsOpt<PackedList>(lineVal))
		{
			for (const auto pointVal : pointListOpt.get().data)
			{
				if (auto posRecordOpt = AsOpt<PackedRecord>(pointVal.value))
				{
					const auto v = ReadVec2Packed(posRecordOpt.get());
					const double x = std::get<0>(v);
					const double y = std::get<1>(v);
					//cs->add(gg::Coordinate(x, y));
					Eigen::Vector2d xy(x, y);
					const auto xy_ = transform.product(xy);

					cs->add(gg::Coordinate(xy_.x(), xy_.y()));
				}
				else CGL_Error("不正な式です");
			}

			distances.push_back(0);
			for (int i = 1; i < cs->size(); ++i)
			{
				const double newDistance = cs->getAt(i - 1).distance(cs->getAt(i));
				distances.push_back(distances.back() + newDistance);
			}
		}
		else CGL_Error("不正な式です");

		return std::move(resultPath);
	}

	bool ReadPolygonPacked(Vector<Eigen::Vector2d>& output, const PackedList& vertices, const TransformPacked& transform)
	{
		output.clear();

		for (const auto& val: vertices.data)
		{
			const PackedVal& value = val.value;

			if (IsType<PackedRecord>(value))
			{
				double x = 0, y = 0;
				const PackedRecord& pos = As<PackedRecord>(value);
				if (!ReadDoublePacked(x, "x", pos) || !ReadDoublePacked(y, "y", pos))
				{
					return false;
				}
				Eigen::Vector2d v(x, y);
				output.push_back(transform.product(v));
			}
			else
			{
				return false;
			}
		}

		return true;
	}

	bool ReadColorPacked(Color& output, const PackedRecord& record, const TransformPacked& transform)
	{
		double r = 0, g = 0, b = 0;

		if (!(
			ReadDoublePacked(r, "r", record) &&
			ReadDoublePacked(g, "g", record) &&
			ReadDoublePacked(b, "b", record)
			))
		{
			return false;
		}

		output.r = r;
		output.g = g;
		output.b = b;

		return true;
	}
}


#include <iostream>
#include <string>
#include <vector>
#include <random>

#include "cxxopts.hpp"

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

#ifndef REAL
#define REAL double
#endif

#if defined(SOLVER_BACKEND_VEXCL)
#  include <amgcl/value_type/static_matrix.hpp>
#  include <amgcl/adapter/block_matrix.hpp>
#  include <amgcl/backend/vexcl.hpp>
#  include <amgcl/backend/vexcl_static_matrix.hpp>
   typedef amgcl::backend::vexcl<REAL> Backend;
#elif defined(SOLVER_BACKEND_VIENNACL)
#  include <amgcl/backend/viennacl.hpp>
   typedef amgcl::backend::viennacl< viennacl::compressed_matrix<REAL> > Backend;
#elif defined(SOLVER_BACKEND_CUDA)
#  include <amgcl/backend/cuda.hpp>
#  include <amgcl/relaxation/cusparse_ilu0.hpp>
   typedef amgcl::backend::cuda<REAL> Backend;
#elif defined(SOLVER_BACKEND_EIGEN)
#  include <amgcl/backend/eigen.hpp>
   typedef amgcl::backend::eigen<REAL> Backend;
#elif defined(SOLVER_BACKEND_BLAZE)
#  include <amgcl/backend/blaze.hpp>
   typedef amgcl::backend::blaze<REAL> Backend;
#else
#  ifndef SOLVER_BACKEND_BUILTIN
#    define SOLVER_BACKEND_BUILTIN
#  endif
#  include <amgcl/backend/builtin.hpp>
#  include <amgcl/value_type/static_matrix.hpp>
#  include <amgcl/adapter/block_matrix.hpp>
   typedef amgcl::backend::builtin<REAL> Backend;
#endif

#include <amgcl/relaxation/runtime.hpp>
#include <amgcl/coarsening/runtime.hpp>
#include <amgcl/coarsening/rigid_body_modes.hpp>
#include <amgcl/solver/runtime.hpp>
#include <amgcl/preconditioner/runtime.hpp>
#include <amgcl/make_solver.hpp>
#include <amgcl/amg.hpp>
#include <amgcl/adapter/crs_tuple.hpp>
#include <amgcl/adapter/reorder.hpp>
#include <amgcl/io/mm.hpp>

#include <amgcl/profiler.hpp>

#ifndef AMGCL_BLOCK_SIZES
#  define AMGCL_BLOCK_SIZES (3)(4)(6)(10)
#endif

namespace amgcl { profiler<> prof; }
using amgcl::prof;
using amgcl::precondition;

#ifdef SOLVER_BACKEND_BUILTIN
//---------------------------------------------------------------------------
template <int B>
std::tuple<size_t, REAL> block_solve(
        const boost::property_tree::ptree &prm,
        size_t rows,
        std::vector<ptrdiff_t> const &ptr,
        std::vector<ptrdiff_t> const &col,
        std::vector<REAL>    const &val,
        std::vector<REAL>    const &rhs,
        std::vector<REAL>          &x,
        bool reorder
        )
{
    typedef amgcl::static_matrix<REAL, B, B> value_type;
    typedef amgcl::static_matrix<REAL, B, 1> rhs_type;
    typedef amgcl::backend::builtin<value_type> BBackend;

    typedef amgcl::make_solver<
        amgcl::runtime::preconditioner<BBackend>,
        amgcl::runtime::solver::wrapper<BBackend>
        > Solver;

    auto As = std::tie(rows, ptr, col, val);
    auto Ab = amgcl::adapter::block_matrix<value_type>(As);

    std::tuple<size_t, REAL> info;

    if (reorder) {
        prof.tic("reorder");
        amgcl::adapter::reorder<> perm(Ab);
        prof.toc("reorder");

        prof.tic("setup");
        Solver solve(perm(Ab), prm);
        prof.toc("setup");

        std::cout << solve << std::endl;

        rhs_type const * fptr = reinterpret_cast<rhs_type const *>(&rhs[0]);
        rhs_type       * xptr = reinterpret_cast<rhs_type       *>(&x[0]);

        amgcl::backend::numa_vector<rhs_type> F(perm(amgcl::make_iterator_range(fptr, fptr + rows/B)));
        amgcl::backend::numa_vector<rhs_type> X(perm(amgcl::make_iterator_range(xptr, xptr + rows/B)));

        prof.tic("solve");
        info = solve(F, X);
        prof.toc("solve");

        perm.inverse(X, xptr);
    } else {
        prof.tic("setup");
        Solver solve(Ab, prm);
        prof.toc("setup");

        std::cout << solve << std::endl;

        rhs_type const * fptr = reinterpret_cast<rhs_type const *>(&rhs[0]);
        rhs_type       * xptr = reinterpret_cast<rhs_type       *>(&x[0]);

        amgcl::backend::numa_vector<rhs_type> F(fptr, fptr + rows/B);
        amgcl::backend::numa_vector<rhs_type> X(xptr, xptr + rows/B);

        prof.tic("solve");
        info = solve(F, X);
        prof.toc("solve");

        std::copy(X.data(), X.data() + X.size(), xptr);
    }

    return info;
}
#endif

#ifdef SOLVER_BACKEND_VEXCL
//---------------------------------------------------------------------------
template <int B>
std::tuple<size_t, REAL> block_solve(
        const boost::property_tree::ptree &prm,
        size_t rows,
        std::vector<ptrdiff_t> const &ptr,
        std::vector<ptrdiff_t> const &col,
        std::vector<REAL>    const &val,
        std::vector<REAL>    const &rhs,
        std::vector<REAL>          &x,
        bool reorder
        )
{
    typedef amgcl::static_matrix<REAL, B, B> value_type;
    typedef amgcl::static_matrix<REAL, B, 1> rhs_type;
    typedef amgcl::backend::vexcl<value_type> BBackend;

    typedef amgcl::make_solver<
        amgcl::runtime::preconditioner<BBackend>,
        amgcl::runtime::solver::wrapper<BBackend>
        > Solver;

    typename BBackend::params bprm;

    vex::Context ctx(vex::Filter::Env);
    std::cout << ctx << std::endl;
    bprm.q = ctx;
    bprm.fast_matrix_setup = prm.get("fast", true);

    vex::scoped_program_header header(ctx,
            amgcl::backend::vexcl_static_matrix_declaration<REAL,B>());

    auto As = std::tie(rows, ptr, col, val);
    auto Ab = amgcl::adapter::block_matrix<value_type>(As);

    std::tuple<size_t, REAL> info;

    if (reorder) {
        prof.tic("reorder");
        amgcl::adapter::reorder<> perm(Ab);
        prof.toc("reorder");

        prof.tic("setup");
        Solver solve(perm(Ab), prm, bprm);
        prof.toc("setup");

        std::cout << solve << std::endl;

        rhs_type const * fptr = reinterpret_cast<rhs_type const *>(&rhs[0]);
        rhs_type       * xptr = reinterpret_cast<rhs_type       *>(&x[0]);

        std::vector<rhs_type> tmp(rows / B);

        perm.forward(amgcl::make_iterator_range(fptr, fptr + rows/B), tmp);
        vex::vector<rhs_type> f_b(ctx, tmp);

        perm.forward(amgcl::make_iterator_range(xptr, xptr + rows/B), tmp);
        vex::vector<rhs_type> x_b(ctx, tmp);

        prof.tic("solve");
        info = solve(f_b, x_b);
        prof.toc("solve");

        vex::copy(x_b, tmp);
        perm.inverse(tmp, xptr);
    } else {
        prof.tic("setup");
        Solver solve(Ab, prm, bprm);
        prof.toc("setup");

        std::cout << solve << std::endl;

        rhs_type const * fptr = reinterpret_cast<rhs_type const *>(&rhs[0]);
        rhs_type       * xptr = reinterpret_cast<rhs_type       *>(&x[0]);

        vex::vector<rhs_type> f_b(ctx, rows/B, fptr);
        vex::vector<rhs_type> x_b(ctx, rows/B, xptr);

        prof.tic("solve");
        info = solve(f_b, x_b);
        prof.toc("solve");

        vex::copy(x_b.begin(), x_b.end(), xptr);
    }

    return info;
}
#endif

//---------------------------------------------------------------------------
std::tuple<size_t, REAL> scalar_solve(
        const boost::property_tree::ptree &prm,
        size_t rows,
        std::vector<ptrdiff_t> const &ptr,
        std::vector<ptrdiff_t> const &col,
        std::vector<REAL>    const &val,
        std::vector<REAL>    const &rhs,
        std::vector<REAL>          &x,
        bool reorder
        )
{
    Backend::params bprm;

#if defined(SOLVER_BACKEND_VEXCL)
    // https://vexcl.readthedocs.io/en/latest/initialize.html
    //vex::Context ctx(vex::Filter::Env);
    vex::Context ctx(vex::Filter::Position(2));
    std::cout << ctx << std::endl;
    bprm.q = ctx;
    //bprm.q = {ctx.queue(2)}; // RadeonVII
#elif defined(SOLVER_BACKEND_VIENNACL)
    std::cout
        << viennacl::ocl::current_device().name()
        << " (" << viennacl::ocl::current_device().vendor() << ")\n\n";
#elif defined(SOLVER_BACKEND_CUDA)
    cusparseCreate(&bprm.cusparse_handle);
    {
        int dev;
        cudaGetDevice(&dev);

        cudaDeviceProp prop;
        cudaGetDeviceProperties(&prop, dev);
        std::cout << prop.name << std::endl << std::endl;
    }
#endif

    typedef amgcl::make_solver<
        amgcl::runtime::preconditioner<Backend>,
        amgcl::runtime::solver::wrapper<Backend>
        > Solver;

    std::tuple<size_t, REAL> info;

    if (reorder) {
        prof.tic("reorder");
        amgcl::adapter::reorder<> perm(std::tie(rows, ptr, col, val));
        prof.toc("reorder");

        prof.tic("setup");
        Solver solve(perm(std::tie(rows, ptr, col, val)), prm, bprm);
        prof.toc("setup");

        std::cout << solve << std::endl;

        std::vector<REAL> tmp(rows);

        perm.forward(rhs, tmp);
        auto f_b = Backend::copy_vector(tmp, bprm);

        perm.forward(x, tmp);
        auto x_b = Backend::copy_vector(tmp, bprm);

        prof.tic("solve");
        info = solve(*f_b, *x_b);
        prof.toc("solve");

#if defined(SOLVER_BACKEND_VEXCL)
        vex::copy(*x_b, tmp);
#elif defined(SOLVER_BACKEND_VIENNACL)
        viennacl::fast_copy(*x_b, tmp);
#elif defined(SOLVER_BACKEND_CUDA)
        thrust::copy(x_b->begin(), x_b->end(), tmp.begin());
#else
        std::copy(&(*x_b)[0], &(*x_b)[0] + rows, &tmp[0]);
#endif

        perm.inverse(tmp, x);
    } else {
        prof.tic("setup");
        Solver solve(std::tie(rows, ptr, col, val), prm, bprm);
        prof.toc("setup");

        std::cout << solve << std::endl;

        auto f_b = Backend::copy_vector(rhs, bprm);
        auto x_b = Backend::copy_vector(x,   bprm);

        prof.tic("solve");
        info = solve(*f_b, *x_b);
        prof.toc("solve");

#if defined(SOLVER_BACKEND_VEXCL)
        vex::copy(*x_b, x);
#elif defined(SOLVER_BACKEND_VIENNACL)
        viennacl::fast_copy(*x_b, x);
#elif defined(SOLVER_BACKEND_CUDA)
        thrust::copy(x_b->begin(), x_b->end(), x.begin());
#else
        std::copy(&(*x_b)[0], &(*x_b)[0] + rows, &x[0]);
#endif
    }

    return info;
}

#define AMGCL_CALL_BLOCK_SOLVER(z, data, B)                                    \
  case B:                                                                      \
    return block_solve<B>(prm, rows, ptr, col, val, rhs, x, reorder);

//---------------------------------------------------------------------------
std::tuple<size_t, REAL> solve(
        const boost::property_tree::ptree &prm,
        size_t rows,
        std::vector<ptrdiff_t> const &ptr,
        std::vector<ptrdiff_t> const &col,
        std::vector<REAL>    const &val,
        std::vector<REAL>    const &rhs,
        std::vector<REAL>          &x,
        int block_size,
        bool reorder
        )
{
    switch (block_size) {
        case 1:
            return scalar_solve(prm, rows, ptr, col, val, rhs, x, reorder);
#if defined(SOLVER_BACKEND_BUILTIN) || defined(SOLVER_BACKEND_VEXCL)
        BOOST_PP_SEQ_FOR_EACH(AMGCL_CALL_BLOCK_SOLVER, ~, AMGCL_BLOCK_SIZES)
#endif
        default:
            precondition(false, "Unsupported block size");
            return std::make_tuple(0, 0.0);
    }
}

//---------------------------------------------------------------------------
int main(int argc, char *argv[]) {
    //namespace po = boost::program_options;
    namespace io = amgcl::io;

    using amgcl::prof;
    using std::vector;
    using std::string;

    //namespace po = boost::program_options;
    namespace io = amgcl::io;

    cxxopts::Options options("amgcl_block_matrices", "check AMG for different block sizes");
    options
        .allow_unrecognised_options()
        .add_options()
        ("A,matrix", "System matrix in the MatrixMarket format.",
         cxxopts::value<std::string>())
        ("b,block-size", "The block size of the system matrix. ",
         cxxopts::value<int>()->default_value("1"))

        (
         "p,prm",
         "Parameters specified as name=value pairs. "
         "May be provided multiple times. Examples:\n"
         "  -p solver.tol=1e-3\n"
         "  -p precond.coarse_enough=300",
         cxxopts::value<vector<string>>())

        ("h,help", "Print help")
        ;

    auto vm = options.parse(argc, argv);

    if (vm.count("help")) {
        std::cout << options.help() << std::endl;
        return 0;
    }

    for (int i = 0; i < argc; ++i) {
        if (i) std::cout << " ";
        std::cout << argv[i];
    }
    std::cout << std::endl;

    boost::property_tree::ptree prm;
    if (vm.count("prm-file")) {
        read_json(vm["prm-file"].as<string>(), prm);
    }

    if (vm.count("prm")) {
        for(const string &v : vm["prm"].as<vector<string>>()) {
            amgcl::put(prm, v);
        }
    }

    // see [Question: How to retrieve the initial residual from the linear solvers?](https://github.com/ddemidov/amgcl/issues/89)
    auto set_prm_defaults = [&prm](string const& param, auto const& default_val) {
        prm.put(param, prm.get(param, default_val));
    };
    set_prm_defaults("solver.type",     "cg"); // "bicgstab"
    set_prm_defaults("solver.tol",      1e-4);
    set_prm_defaults("solver.maxiter",  5000);
    set_prm_defaults("solver.verbose",  false);
    set_prm_defaults("precond.coarsening.type",  "smoothed_aggregation");
    set_prm_defaults("precond.relax.type",       "damped_jacobi"); // ilu0
    set_prm_defaults("precond.allow_rebuild", true);

    size_t block_size = 1;

    if (vm.count("block-size")) {
        block_size = vm["block-size"].as<int>();
    }

    size_t rows, nv = 0;
    vector<ptrdiff_t> ptr, col;
    vector<REAL> val;

    if (vm.count("matrix")) {
        string Afile  = vm["matrix"].as<string>();
        auto [r, cols] = io::mm_reader(Afile)(ptr, col, val);

        rows = r;
        std::cout << "rows: " << rows << std::endl;
        std::cout << "cols: " << cols << std::endl;
    }

    vector<REAL> rhs(rows, 0.0), x(rows, 0.0);

    // Set RHS := Ax where x = 1
    for (size_t i = 0; i < rows; ++i) {
        REAL s = 0;
        for (ptrdiff_t j = ptr[i], e = ptr[i+1]; j < e; ++j) {
            s += val[j];
        }
        rhs[i] = s;
    }

    auto [iters, error] = solve(
            prm, rows, ptr, col, val, rhs, x,
            block_size, false /* reorder */);

    std::cout << "Iterations: " << iters << std::endl
              << "Error:      " << error << std::endl
              << prof << std::endl;
}


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_POW2_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_POW2_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-exponential
    This function object returns \f$ x 2^y\f$.  (the result is undefined on overflow and
    the function asserts for invalid second parameter )

    @par Header <boost/simd/function/pow2.hpp>

    @par Note

   The first parameter default to @c One.

    @par Example:

      @snippet pow2.cpp pow2

    @par Possible output:

      @snippet pow2.txt pow2

  **/
  ///@{
  IEEEValue pow2(IEEEValue const& x, Value const& y);
  IntegerValue pow2(IntegerValue const& x, IntegerValue const& y);
  ///@}
} }
#endif

#include <boost/simd/function/scalar/pow2.hpp>
#include <boost/simd/function/simd/pow2.hpp>

#endif


/* Copyright (C) 2012-2017 IBM Corp.
 * This program is Licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. See accompanying LICENSE file.
 */
/* matmul1D.cpp - Data-movement operations on arrays of slots
 */
#include <algorithm>
#include <NTL/BasicThreadPool.h>
#include "EncryptedArray.h"
#include "matmul.h"

struct PtxtPtr {
  enum Type { ZERO=0, ZZX=1, DCRT=2 };
  zzX*       zp;
  DoubleCRT* dp;

  PtxtPtr(): zp(nullptr), dp(nullptr) {}
};

// A class that implements the basic (sparse) 1D matrix-vector functions
template<class type> class matmul1D_impl {
  PA_INJECT(type)
  const MatrixCacheType buildCache;
  std::unique_ptr<CachedzzxMatrix> zCache;
  std::unique_ptr<CachedDCRTMatrix> dCache;

  MatMul<type>& mat;
  const EncryptedArrayDerived<type>& ea;

public:
  matmul1D_impl(MatMulBase& _mat, long dim, MatrixCacheType tag)
    : buildCache(tag), mat(dynamic_cast< MatMul<type>& >(_mat)),
      ea(_mat.getEA().getDerived(type()))
  {
    if (buildCache==cachezzX) {
      zCache.reset(new CachedzzxMatrix(NTL::INIT_SIZE,ea.sizeOfDimension(dim)));
    } else if (buildCache==cacheDCRT) {
      dCache.reset(new CachedDCRTMatrix(NTL::INIT_SIZE,ea.sizeOfDimension(dim)));
    }
  }

  // Get the i'th diagonal along dimension dim, encoded as a
  // single constant. All blocks use the same transofmration.
  // Returns true if this is a zero diagonal, false otherwise
  bool processDiagonal1(zzX& cPoly, long dim, long i, long D, long rotAmt)
  {
    vector<RX> tmpDiag(D);
    bool zDiag = true; // is this a zero diagonal?
    long nzLast = -1;  // index of last non-zero entry
    RX entry;

    // Process the entries in this diagonal one at a time
    for (long j = 0; j < D; j++) { // process entry j
      long rotJ = (j+rotAmt) % D;  // need to rotate constant by rotAmt
      bool zEntry = mat.get(entry, mcMod(rotJ-i, D), rotJ); // entry [j-i mod D, j]
      assert(zEntry || deg(entry) < ea.getDegree());
      // get(...) returns true if the entry is empty, false otherwise

      if (!zEntry && IsZero(entry)) zEntry = true;// zero is an empty entry too

      if (!zEntry) {   // not a zero entry
        zDiag = false; // mark diagonal as non-empty

        // clear entries between last nonzero entry and this one
        for (long jj = nzLast+1; jj < j; jj++) clear(tmpDiag[jj]);
        tmpDiag[j] = entry;
        nzLast = j;
      }
    }
    if (zDiag) return true; // zero diagonal, nothing to do

    // clear trailing zero entries
    for (long jj = nzLast+1; jj < D; jj++) clear(tmpDiag[jj]);

    vector<RX> diag(ea.size());
    if (D==1) diag.assign(ea.size(), tmpDiag[0]); // dimension of size one
    else for (long j = 0; j < ea.size(); j++)
           diag[j] = tmpDiag[ ea.coordinate(dim,j) ];
           // rearrange the indexes based on the current dimension

    ea.encode(cPoly, diag);
    return false; // a nonzero diagonal
  }

  // Get the i'th diagonal along dimension dim, encoded as a
  // single constant. Different blocks use different transofmrations.
  // Returns true if this is a zero diagonal, false otherwise
  bool processDiagonal2(zzX& poly, long dim, long idx, long D, long rotAmt)
  {
    bool zDiag = true; // is this a zero diagonal?
    long nzLast = -1;  // index of last non-zero entry
    RX entry;

    // Process the entries in this diagonal one at a time
    long blockIdx, innerIdx;
    vector<RX> diag(ea.size());
    for (long j=0; j<ea.size(); j++) {
      if (D==1) {
	blockIdx=j; innerIdx = 0;
      } else {
	std::tie(blockIdx, innerIdx) // std::pair<long,long> idxes
	  = ea.getContext().zMStar.breakIndexByDim(j, dim);
	//	blockIdx = idxes.first;  // which transformation
	//	innerIdx = idxes.second; // index along diemnssion dim
        innerIdx = (innerIdx+rotAmt) % D;  // need to rotate constant by rotAmt
      }
      // process entry j
      bool zEntry=mat.multiGet(entry,mcMod(innerIdx-idx,D),innerIdx,blockIdx);
      // entry [i,j-i mod D] in the block corresponding to blockIdx
      // multiGet(...) returns true if the entry is empty, false otherwise

      // If non-zero, make sure the degree is not too large
      assert(zEntry || deg(entry) < ea.getDegree());

      if (!zEntry && IsZero(entry)) zEntry = true; // zero is an empty entry too

      if (!zEntry) {   // not a zero entry
	zDiag = false; // mark diagonal as non-empty

	// clear entries between last nonzero entry and this one
	for (long jj = nzLast+1; jj < j; jj++) clear(diag[jj]);
	nzLast = j;
	diag[j] = entry;
      }
    }
    if (zDiag) return true; // zero diagonal, nothing to do

    // clear trailing zero entries
    for (long jj = nzLast+1; jj < ea.size(); jj++) clear(diag[jj]);

    ea.encode(poly, diag);
    return false; // a nonzero diagonal
  }

  // Returns in vec all the constants needed for a single giant-step.
  // The return value is ZERO(=0) if all these constants are zero.
  // Otherwise it is ZZX(=1) if using zzX or DCRT(=2) if using DoubleCRT
  PtxtPtr::Type getConsts(std::vector<PtxtPtr>& vec, std::vector<zzX>& polys,
                          CachedzzxMatrix* zcp, CachedDCRTMatrix* dcp,
                          long dim, long jg, long D, bool oneTrans)
  {
    PtxtPtr::Type typ = PtxtPtr::ZERO;
    long g = polys.size(); // how many constants do we need
    vec.assign(g,PtxtPtr());

    if (dcp!=nullptr) {        // we have DCRT cache
      for (long i=0, idx=jg; i<g && idx<D; i++, idx++) {
        if ((vec[i].dp = (*dcp)[idx].get()) != nullptr)
          typ = PtxtPtr::DCRT;
      }
    } else if (zcp!=nullptr) { // we have ZZX cache
      for (long i=0, idx=jg; i<g && idx<D; i++, idx++) {
        if ((vec[i].zp = (*zcp)[idx].get()) != nullptr)
          typ = PtxtPtr::ZZX;
      }
    } else {                   // no cache, compute consts
      for (long i=0, idx=jg; i<g && idx<D; i++, idx++) {
        bool zero = oneTrans? processDiagonal1(polys[i], dim, idx, D, i)
                            : processDiagonal2(polys[i], dim, idx, D, i);
        if (!zero) {
          vec[i].zp = &(polys[i]); // if not a zero, point to it
          typ = PtxtPtr::ZZX;
        }
      }
    }
    return typ;
  }

  void multiply(Ctxt* ctxt, long dim, bool oneTransform)
  {
    assert(dim >= 0 && dim <= ea.dimension());
    RBak bak; bak.save(); ea.getTab().restoreContext(); // backup NTL modulus

    long D = (dim==ea.dimension())? 1 : ea.sizeOfDimension(dim);
    long g = mat.getGstep();
    if (g<1 || g>=D) g=1; // sanity check
    long dDivg = divc(D,g);

    // Process the diagonals in baby-step/giant-step ordering.
    //   sum_{i=0}^{d-1} const_i rot^i(X)
    //   = \sum_{i=0}^{g-1} \sum_{j=0}^{d/g -1} const_{i+g*j} rot^{i+g*j}(X)
    //   = \sum_{i=0}^{g-1} rot^i(sum_j rot^{-i}(const_{i+g*j}) rot^{g*j}(X))
    //
    // so for i=0..g-1 we let
    //    Y_i = sum_j rot^{-i}(const_{i+g*j}) rot^{g*j}(X)
    // then compute \sum_{i=0}^{g-1} rot^i(Y_i).
    //
    // Computing the Y_i's, we initialize an accumulator for each Y_i,
    // then compute the rotations X_j = rot^{g*j}(X), j=0,...,d/g-1.
    // Each X_j is multiplied by all the constants rot^{-i}(const_{i+g*j}),
    // i=0,...,g-1, and the i'th product is added to the accumulator for
    // the corresponding Y_i.

    long lastRotate = 0;
    std::vector<Ctxt> acc; // accumulators
    if (ctxt!=nullptr) {   // we need to do an actual multiplication
      ctxt->cleanUp();     // not sure, but this may be a good idea
      Ctxt tmp(ZeroCtxtLike, *ctxt);
      acc.resize(g, tmp);
    }

    // Check if we have the relevant constant in cache
    CachedzzxMatrix* zcp;
    CachedDCRTMatrix* dcp;
    mat.getCache(&zcp, &dcp);

    // Process the diagonals in giant-step/baby-step order
    std::vector<zzX> cpolys(g); // scratch space for encoding consts
    std::vector<PtxtPtr> ptrs;  // pointers to these constants
    for (long j = 0; j < dDivg; j++) { // giant steps
      long jg = j*g;            // beginning index of this giant step

      // get all the constants rot^{-i}(const_{i+g*j}) for this step
      PtxtPtr::Type ty = getConsts(ptrs, cpolys, zcp, dcp,
                                   dim, jg, D, oneTransform);

      if (ty==PtxtPtr::ZERO) continue; // all consts are zero

      // Store constants in cache and/or multiply/add them

      if (ctxt!=nullptr) {  // rotate by jg, multiply & add
        Ctxt shCtxt(*ctxt); // temporary to hold current rot^{g*j}(X)

        if (j>0) {
          shCtxt = *ctxt;
          ea.rotate1D(shCtxt, dim, jg); // rotate by i
	} // if j==0 we already have *shCtxt == *ctxt

        if (ty==PtxtPtr::DCRT) for (long i=0; i<min(g,D-jg); i++) {
            if (ptrs[i].dp!=nullptr) {
              Ctxt tmp(shCtxt);
              tmp.multByConstant(*(ptrs[i].dp));
              acc[i] += tmp;
            }
          }
        else /*ty==PtxtPtr::ZZX*/ for (long i=0; i<min(g,D-jg); i++) {
            if (ptrs[i].zp!=nullptr) {
              Ctxt tmp(shCtxt);
              tmp.multByConstant(*(ptrs[i].zp));
              acc[i] += tmp;
            }
          }
      }

      if (buildCache==cachezzX) for (long i=0; i<min(g,D-jg); i++) {
          if (ptrs[i].zp!=nullptr)
            (*zCache)[i+jg].reset(new zzX(*(ptrs[i].zp)));
        }
      else if (buildCache==cacheDCRT) for (long i=0; i<min(g,D-jg); i++) {
          if (ptrs[i].zp!=nullptr)
            (*dCache)[i+jg].reset(new DoubleCRT(*(ptrs[i].zp),ea.getContext()));
        }
      // end of giant-step loop
    }
    // Compute the result as \sum_{i=0}^{g-1} rho^i(Y_i)
    if (ctxt!=nullptr) {
      *ctxt = acc[0];
      for (long i = 1; i < g; i++) {
        ea.rotate1D(acc[i], dim, i);
        *ctxt += acc[i];
      }
    }
    // "install" the cache if needed
    if (buildCache == cachezzX)
      mat.installzzxcache(zCache);
    else if (buildCache == cacheDCRT)
      mat.installDCRTcache(dCache);
  } // end of multiply(...)
};

// Wrapper functions around the implemenmtation class
static void matmul1d(Ctxt* ctxt, MatMulBase& mat, long dim, bool oneTransform,
                     MatrixCacheType buildCache)
{
  MatMulLock locking(mat, buildCache);

  // If locking.getType()!=cacheEmpty then we really do need to
  // build the cache, and we also have the lock for it.

  if (locking.getType()==cacheEmpty && ctxt==nullptr) //  nothing to do
    return;

  switch (mat.getEA().getTag()) {
    case PA_GF2_tag: {
      matmul1D_impl<PA_GF2> M(mat, dim, locking.getType());
      M.multiply(ctxt, dim, oneTransform);
      break;
    }
    case PA_zz_p_tag: {
      matmul1D_impl<PA_zz_p> M(mat, dim, locking.getType());
      M.multiply(ctxt, dim, oneTransform);
      break;
    }
    default:
      throw std::logic_error("matmul1d: neither PA_GF2 nor PA_zz_p");
  }
}
void buildCache4MatMul1D(MatMulBase& mat, long dim,
                         MatrixCacheType buildCache)
{ matmul1d(nullptr, mat, dim, true, buildCache); }

void matMul1D(Ctxt& ctxt, MatMulBase& mat,long dim,
              MatrixCacheType buildCache)
{ matmul1d(&ctxt, mat, dim, true, buildCache); }

void buildCache4MatMulti1D(MatMulBase& mat,long dim,
                           MatrixCacheType buildCache)
{ matmul1d(nullptr, mat, dim, false, buildCache); }

void matMulti1D(Ctxt& ctxt,MatMulBase& mat,long dim,
                MatrixCacheType buildCache)
{ matmul1d(&ctxt, mat, dim, false, buildCache); }

/********************************************************************
 ********************************************************************/
// Applying matmul to plaintext, useful for debugging

template<class type>
class matmul1D_pa_impl {
public:
  PA_INJECT(type)

  static void multiply(NewPlaintextArray& pa, MatMul<type>& mat,
		       long dim, bool oneTrans)
  {
    const EncryptedArrayDerived<type>& ea = mat.getEA().getDerived(type());
    RBak bak; bak.save(); ea.getTab().restoreContext();

    long n = ea.size();
    long D = ea.sizeOfDimension(dim);

    vector< vector<RX> > data1(n/D);
    for (long k = 0; k < n/D; k++)
      data1[k].resize(D);

    // copy the data into a vector of 1D vectors
    vector<RX>& data = pa.getData<type>();
    for (long i = 0; i < n; i++) {
      long k,j;
      std::tie(k,j) = ea.getContext().zMStar.breakIndexByDim(i, dim);
      data1[k][j] = data[i];       // k= along dim, j = the rest of i
    }

    // multiply each one of the vectors by the same matrix
    for (long k = 0; k < n/D; k++) {
      for (long j = 0; j < D; j++) { // simple matrix-vector multiplication
	std::pair<long,long> p(k,j);
	long idx = ea.getContext().zMStar.assembleIndexByDim(p, dim);

	RX acc, val, tmp;
	acc = 0;
        for (long i = 0; i < D; i++) {
          bool zero = oneTrans? mat.get(val, i, j) : mat.multiGet(val,i,j,k);
          if (!zero) {
            NTL::mul(tmp, data1[k][i], val);
            NTL::add(acc, acc, tmp);
          }
        }
        rem(data[idx], acc, ea.getG()); // store the result in the data array
      }
    }
  }
};
// A wrapper around the implementation class
static void matmul1d(NewPlaintextArray& pa, MatMulBase& mat,
		     long dim, bool oneTrans)
{
  switch (mat.getEA().getTag()) {
    case PA_GF2_tag: {
      matmul1D_pa_impl<PA_GF2>::multiply(pa,
                      dynamic_cast< MatMul<PA_GF2>& >(mat), dim, oneTrans);
      return;
    }
    case PA_zz_p_tag: {
      matmul1D_pa_impl<PA_zz_p>::multiply(pa,
                      dynamic_cast<MatMul<PA_zz_p>&>(mat), dim, oneTrans);
      return;
    }
    default:
      throw std::logic_error("matMul1D: neither PA_GF2 nor PA_zz_p");
  }
}

void matMul1D(NewPlaintextArray& pa, MatMulBase& mat, long dim)
{
  matmul1d(pa, mat, dim, true);
}
void matMulti1D(NewPlaintextArray& pa, MatMulBase& mat, long dim)
{
  matmul1d(pa, mat, dim, false);
}


#include <wav2midi/audio_stream.hpp>
#include <wav2midi/fft/fft.hpp>
#include <wav2midi/fft/window.hpp>
#include <wav2midi/scale.hpp>
#include <wav2midi/gnuplot.hpp>
#include <iostream>
#include <fstream>
#include <cstdio>
#include <boost/format.hpp>

int main(int argc, char * argv[]) {
    std::string path = argc == 1 ? "/dev/stdin" : argv[1];
    std::cout << "Read from " << path << std::endl;;

    wav2midi::gnuplot wave_gnuplot;
    wave_gnuplot.command("set title '{/=16 Wave}'");
    wave_gnuplot.command("set tmargin 5");
    wave_gnuplot.command("set bmargin 5");
    wave_gnuplot.command("set rmargin 8");
    wave_gnuplot.command("set lmargin 8");
    wave_gnuplot.command("set xrange [0:0.046]");
    wave_gnuplot.command("set yrange [-0.1:0.1]");
    wave_gnuplot.open();

    wav2midi::gnuplot fft_gnuplot;
    fft_gnuplot.command("set title '{/=16 FFT}'");
    fft_gnuplot.command("set tmargin 5");
    fft_gnuplot.command("set bmargin 5");
    fft_gnuplot.command("set rmargin 8");
    fft_gnuplot.command("set lmargin 8");
    fft_gnuplot.command("set xrange [0:5000]");
    fft_gnuplot.command("set yrange [0:20]");
    fft_gnuplot.open();

    while (true) {
        wav2midi::audio_stream as(path);
        wav2midi::scale scale;

        as.read(std::pow(2, 12), [&wave_gnuplot, &fft_gnuplot, &scale](auto samplings) {
            {
                std::ofstream ofs("wave.dat");

                for (auto x = 0u; x < samplings.size(); ++x) {
                    auto t = x * 1.0 / (44.1 * 1000);
                    auto amp = samplings[x];
                    ofs << t << " " << amp << std::endl;
                }
                wave_gnuplot.command("plot 'wave.dat' using 1:2 with lines");
                wave_gnuplot.flush();
            }

            auto frequences = wav2midi::fft::fft(samplings, wav2midi::fft::window::hanning).execute();
            const auto n = frequences.size();
            const auto threshold_amp = 1;
            auto max_amp = 0.0;
            auto max_freq = 0.0;

            {
                std::ofstream ofs("fft.dat");

                auto k_f = [n](double f) -> uint32_t { return std::round(f / (44.1 * 1000) * n); };
                auto k_begin = k_f(scale[0].frequency());
                auto k_end = k_f(scale[87].frequency());

                for (auto k = k_begin; k < k_end; ++k) {
                    auto freq = 44.1 * 1000 * k / n;

                    auto amp = std::abs(frequences[k]);
                    ofs << freq << " " << amp << std::endl;
                    if (max_amp < amp && amp > threshold_amp) {
                        max_amp = amp;
                        max_freq = freq;
                    }
                    ofs << freq << " " << amp << std::endl;
                }
                std::cout << "========================" << std::endl;
            }
            auto item = scale.match(max_freq);
            auto label = (
                boost::format("%f [Hz]: %f => %s: %f [Hz]")
                % max_freq
                % max_amp
                % item.name()
                % item.frequency()
            ).str();
            auto gp_label1 = (
                boost::format("{/=16 %s}{/=12 : %.2f [Hz]}")
                % item.name()
                % item.frequency()
            ).str();
            auto gp_label2 = (
                boost::format("{/=12 %.2f [Hz]: %.2f}")
                % max_freq
                % max_amp
            ).str();
            std::cout << label << std::endl;
            fft_gnuplot.command("plot 'fft.dat' using 1:2 with lines");
            fft_gnuplot.command("set label 1 at screen 0.05, 0.05 '%s'", gp_label1);
            fft_gnuplot.command("set label 2 at screen 0.65, 0.05 '%s'", gp_label2);
            fft_gnuplot.flush();

            return false;
        });

        if (path == "/dev/stdin") {
            break;
        }
        else {
            std::cout << "=====================================================================" << std::endl;
            std::cout << "You can finish this program when you entered 'q'; otherwise you can retry this program." << std::endl;
            std::cout << ">> ";
            std::string command;
            std::cin >> command;
            if (command == "q") break;
        }
    }

    return 0;
}


#include "hydrology.h"
#include "../basic.h"

#include <random>

#include <boost/log/trivial.hpp>
#include <boost/random.hpp>

namespace WorldEngine
{

static void Droplet(World& world, uint32_t x, uint32_t y, float q);
static void WatermapExecute(World& world, uint32_t numSamples, uint32_t seed);

static const uint32_t NUM_SAMPLES = 20000u;

void WatermapSimulation(World& world, uint32_t seed)
{
   BOOST_LOG_TRIVIAL(info) << "Watermap simulation start";

   const WaterMapArrayType& watermap = world.GetWaterMapData();
   const OceanArrayType&    ocean    = world.GetOceanData();

   WatermapExecute(world, NUM_SAMPLES, seed);

   world.SetThreshold(WaterThreshold::Creek,
                      FindThresholdF(watermap, 0.05f, &ocean));
   world.SetThreshold(WaterThreshold::River,
                      FindThresholdF(watermap, 0.02f, &ocean));
   world.SetThreshold(WaterThreshold::MainRiver,
                      FindThresholdF(watermap, 0.007f, &ocean));

   BOOST_LOG_TRIVIAL(info) << "Watermap simulation finish";
}

static void Droplet(World& world, uint32_t x, uint32_t y, float q)
{
   if (q < 0)
   {
      return;
   }

   const ElevationArrayType& elevation = world.GetElevationData();
   WaterMapArrayType&        watermap  = world.GetWaterMapData();

   std::vector<std::pair<uint32_t, uint32_t>>            tilesAround;
   std::vector<std::tuple<uint32_t, uint32_t, uint32_t>> lowers;

   float    posElev   = elevation[y][x] + watermap[y][x];
   float    minLower  = std::numeric_limits<float>::max();
   uint32_t totLowers = 0;

   world.GetTilesAround(tilesAround, x, y);

   for (const auto& p : tilesAround)
   {
      uint32_t px = p.first;
      uint32_t py = p.second;
      float    e  = elevation[py][px] + watermap[py][px];

      if (e < posElev)
      {
         uint32_t dq = static_cast<uint32_t>(posElev - e) << 2;
         if (e < minLower)
         {
            minLower = e;
            if (dq == 0)
            {
               dq = 1;
            }
         }
         lowers.push_back({dq, px, py});
         totLowers += dq;
      }
   }

   if (!lowers.empty())
   {
      float f = q / static_cast<float>(totLowers);

      for (const auto& t : lowers)
      {
         uint32_t s;
         uint32_t px;
         uint32_t py;

         std::tie(s, px, py) = t;

         if (!world.IsOcean(px, py))
         {
            float ql    = f * s;
            bool  going = ql > 0.05f;

            watermap[py][px] += ql;

            if (going)
            {
               Droplet(world, px, py, ql);
            }
         }
      }
   }
   else
   {
      watermap[y][x] += q;
   }
}

static void WatermapExecute(World& world, uint32_t numSamples, uint32_t seed)
{
   BOOST_LOG_TRIVIAL(debug) << "Seed: " << seed;

   std::mt19937                                      generator(seed);
   boost::random::uniform_int_distribution<uint32_t> distribution;

   const uint32_t width  = world.width();
   const uint32_t height = world.height();

   const PrecipitationArrayType& precipitations = world.GetPrecipitationData();
   WaterMapArrayType&            watermap       = world.GetWaterMapData();
   watermap.resize(boost::extents[height][width]);

   std::fill(watermap.data(), watermap.data() + watermap.num_elements(), 0.0f);

   /*
    * This indirectly calls the global RNG. We want different implementations of
    * watermap and internally called functions (especially RandomLand) to show
    * the same RNG behavior and not contaminate the state of the global RNG
    * should anyone else happen to rely on it.
    */

   std::vector<Point> landSamples;

   world.GetRandomLand(landSamples, numSamples, distribution(generator));

   for (uint32_t i = 0; i < landSamples.size(); i++)
   {
      uint32_t x = landSamples[i].first;
      uint32_t y = landSamples[i].second;
      float    q = precipitations[y][x];

      if (q > 0)
      {
         Droplet(world, x, y, q);
      }
   }
}

} // namespace WorldEngine


#include <algorithm>
#include <boost/optional.hpp>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iostream>
#include <limits>
#include <list>
#include <numeric>
#include <queue>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

#define REP(i, n) for(int i = 0, i##_MACRO = (n); i < i##_MACRO; i++)
#define RANGE(i, a, b) for(int i = (a), i##_MACRO = (b); i < i##_MACRO; i++)
#define EACH(e, a) for(auto&& e : a)
#define ALL(a) (a).begin(), (a).end()
#define AALL(a, n) (a), ((a) + (n))
#define FILL(a, n) memset((a), n, sizeof(a))
#define FILLZ(a) FILL(a, 0)
#define MODNUM (static_cast<int>(1e9 + 7))
#define MOD(x) ((x) % MODNUM)

using namespace std;

using ll = long long;
using VI = vector<int>;
using VI2D = vector<vector<int>>;

const int INF = 2e9;
const double EPS = 1e-10;
const double PI = acos(-1.0);

const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, -1, 0, 1};

template <typename T>
int sign(T x) {
	return x < 0 ? -1 : x > 0 ? 1 : 0;
}

template <>
int sign(double x) {
	return x < -EPS ? -1 : x > EPS ? 1 : 0;
}

template <typename T>
T square(T x) {
	return x * x;
}

inline int toInt(string s) {
	int v;
	istringstream sin(s);
	sin >> v;
	return v;
}

void chmax(int& m, int n) {
	m = max(m, n);
}

int bfs(const VI2D& maze, int h, int w, pair<int, int> s) {
	VI2D distances(h, VI(w, INF));
	distances[s.first][s.second] = 0;
	int max_distance = 0;
	queue<pair<int, int>> que;
	que.push(s);
	while(!que.empty()) {
		auto p = que.front();
		que.pop();
		if(maze[p.first][p.second]) {
			continue;
		};
		int d = distances[p.first][p.second];
		chmax(max_distance, d);
		REP(i, 4) {
			if(distances[p.first + dy[i]][p.second + dx[i]] == INF) {
				distances[p.first + dy[i]][p.second + dx[i]] = d + 1;
				que.push(pair<int, int>(p.first + dy[i], p.second + dx[i]));
			}
		}
	}
	return max_distance;
}

int main() {
	int h, w;
	scanf("%d %d\n", &h, &w);
	VI2D maze(h + 2, VI(w + 2, 1));
	REP(i, h) {
		REP(j, w) {
			char c = getchar();
			if(c == '.') {
				maze[i + 1][j + 1] = 0;
			}
		}
		getchar();
	}

	int result = 0;
	REP(i, h) {
		REP(j, w) {
			chmax(result,
				  bfs(maze, h + 2, w + 2, pair<int, int>(i + 1, j + 1)));
		}
	}
	printf("%d\n", result);
	return 0;
}