#include "f1_datalogger/post_processing/post_processing_utils.h"
#include <google/protobuf/util/json_util.h>
#include "f1_datalogger/alglib/interpolation.h"
#include <sstream>
#include <fstream>
#include <iostream>
#include <Eigen/Geometry>
#include "f1_datalogger/controllers/kdtree_eigen.h"
#if defined(__GNUC__) && (__GNUC__ < 8)
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#else
#include <filesystem>
namespace fs = std::filesystem;
#endif
namespace deepf1
{
namespace post_processing
{
	PostProcessingUtils::PostProcessingUtils()
	{
	}
	PostProcessingUtils::~PostProcessingUtils()
	{
	}
	void get_all(const fs::path& root, const std::string& ext, std::vector<fs::path>& ret)
	{
		if (!fs::exists(root) || !fs::is_directory(root)) return;

		fs::recursive_directory_iterator it(root);
		fs::recursive_directory_iterator endit;

		while (it != endit)
		{
			if (fs::is_regular_file(*it) && it->path().extension() == ext) ret.push_back(it->path().filename());
			++it;
		}

	}
	bool udpComp(const deepf1::protobuf::TimestampedUDPData& a, const deepf1::protobuf::TimestampedUDPData& b)
	{
		return a.timestamp() < b.timestamp();
	}
	bool imageComp(const deepf1::protobuf::TimestampedImage& a, const deepf1::protobuf::TimestampedImage& b)
	{
		return a.timestamp() < b.timestamp();
	}
	unsigned int closestValueHelper(const std::vector<deepf1::protobuf::TimestampedUDPData>& sorted_data,
	 google::protobuf::uint64 search, unsigned int left, unsigned int right)
	{
		if(left == right)
		{
			return left;

		}
		else if((right-left)==1)
		{
			if((search - sorted_data.at(left).timestamp()) < (sorted_data.at(right).timestamp() - search))
			{
				return left;
			}
			else
			{
				return right;
			}
		}
		else
		{
			unsigned int middle = (left + right)/2;
			google::protobuf::uint64 query_val = sorted_data.at(middle).timestamp();
			if(query_val == search)
			{
				return middle;
			}
			else if(query_val < search)
			{
				return closestValueHelper(sorted_data, search, middle, right);
			}
			else
			{
				return closestValueHelper(sorted_data, search, left, middle);
			}
		}
	}
	Eigen::MatrixXd PostProcessingUtils::readTrackFile(const std::string& trackfile)
	{
		Eigen::MatrixXd rtnMat;
		Eigen::Matrix<double,1,4> asdf = { 1.0, 2.0, 3.0, 4.0 };
		std::vector< std::pair< double, Eigen::Vector3d> > rtn;
		std::ifstream file;
		file.open(trackfile);
		std::string header;
		std::string labels;
		std::getline(file, header);
		std::cout << header << std::endl;
		std::getline(file, labels);
		std::cout << labels << std::endl;
		std::string line;

		while (true)
		{
			std::getline(file, line);
			if (file.eof())
			{
				break;
			}
			std::stringstream ss(line);
			std::vector<double> vec;
			while (ss.good())
			{
				std::string substr;
				std::getline(ss, substr, ',');
				vec.push_back(std::atof(substr.c_str()));
			}
			Eigen::Vector3d p(vec[1], vec[3], vec[2]);
			rtn.push_back(std::make_pair(vec[0], p));
			//std::cout <<std::endl;
			//std::cout << eigenvec <<std::endl;
			//std::cout << std::endl;
		}

		std::vector< std::pair< double, Eigen::Vector3d > > interpolated_points;
		unsigned int max_index = rtn.size() - 3;
		double dt = (1.0 / 16.0);
		std::vector<double> T = { 0.0, (1.0 / 3.0), (2.0 / 3.0), 1.0 };
		alglib::real_1d_array t_alglib;
		t_alglib.attach_to_ptr(4, &T[0]);
		for (unsigned int i = 0; i < max_index; i += 3)
		{
			double x0 = rtn.at(i).first;
			double x1 = rtn.at((size_t)i + 1).first;
			double x2 = rtn.at((size_t)i + 2).first;
			double x3 = rtn.at((size_t)i + 3).first;
			std::vector<double> X = { x0, x1, x2, x3 };
			alglib::real_1d_array x_alglib;
			x_alglib.attach_to_ptr(4, &X[0]);
			alglib::spline1dinterpolant x_interpolant;
			alglib::spline1dbuildcubic(t_alglib, x_alglib, x_interpolant);
			double deltax = x3 - x0;
			Eigen::Vector3d P0 = rtn.at(i).second;
			Eigen::Vector3d P1 = rtn.at((size_t)i + 1).second;
			Eigen::Vector3d P2 = rtn.at((size_t)i + 2).second;
			Eigen::Vector3d P3 = rtn.at((size_t)i + 3).second;
			for (double t = dt; t < 1.0; t += dt)
			{
				Eigen::Vector3d Pinterp = std::pow(1 - t, 3)*P0 + 3 * t*std::pow(1 - t, 2)*P1 +
					3 * std::pow(t, 2)*(1 - t)*P2 + std::pow(t, 3)*P3;
				double xinterp = alglib::spline1dcalc(x_interpolant, t);
				interpolated_points.push_back(std::make_pair(xinterp, Pinterp));
			}
		}
		rtn.insert(rtn.end(), interpolated_points.begin(), interpolated_points.end());
		std::sort(rtn.begin(), rtn.end(),
			[](const std::pair< double, Eigen::Vector3d>& a, const std::pair< double, Eigen::Vector3d >& b)
		{ return a.first < b.first; });
		rtnMat.resize(4, rtn.size());
		unsigned int idx = 0;

		std::for_each(rtn.begin(), rtn.end(), [&rtnMat, &idx](const std::pair< double, Eigen::Vector3d >& pair)
		{
			rtnMat(0, idx) = pair.first;
			rtnMat(Eigen::seqN(1, Eigen::last, 1), idx) = pair.second;
			idx++;
		});
		return rtnMat;
	}
	std::pair<deepf1::protobuf::TimestampedUDPData, unsigned int> PostProcessingUtils::closestValue(const std::vector<deepf1::protobuf::TimestampedUDPData>& sorted_data, google::protobuf::uint64 search)
	{
		unsigned int index = closestValueHelper(sorted_data, search, 0, sorted_data.size()-1);

		return std::pair<deepf1::protobuf::TimestampedUDPData, unsigned int>(sorted_data.at(index), index);
	}
	std::vector<float> interp(const std::vector<deepf1::protobuf::TimestampedUDPData>& udp_data, unsigned int closest_index, unsigned int interpolation_order, google::protobuf::uint64 image_timestamp)
	{
		std::vector<float> rtn(3);

		alglib::barycentricinterpolant steering_interp, brake_interp, throttle_interp;
		alglib::real_1d_array timestamps, steering, brake, throttle;
		timestamps.setlength(interpolation_order);
		steering.setlength(interpolation_order);
		brake.setlength(interpolation_order);
		throttle.setlength(interpolation_order);
		unsigned int lower_bound = closest_index - interpolation_order / 2;
		unsigned int upper_bound = closest_index + interpolation_order / 2 ;
		unsigned int idx = 0;
//		printf("Building alglib vectors at index %d. \n", closest_index);
		for (unsigned int i = lower_bound; i <= upper_bound; i++)
		{
			timestamps(idx) = (double)udp_data.at(i).timestamp();
			steering(idx) = (double)udp_data.at(i).udp_packet().m_steer();
			brake(idx) = (double)udp_data.at(i).udp_packet().m_brake();
			throttle(idx) = (double)udp_data.at(i).udp_packet().m_throttle();
			idx++;
		}
	//	printf("Built alglib vectors. \n");
		alglib::polynomialbuild(timestamps, steering, interpolation_order, steering_interp);
		alglib::polynomialbuild(timestamps, brake, interpolation_order, brake_interp);
		alglib::polynomialbuild(timestamps, throttle, interpolation_order, throttle_interp);
	//	printf("Built alglib models. \n");

		double t = (double)image_timestamp;
		rtn[0] = (float)alglib::barycentriccalc( steering_interp,  t);
		rtn[1] = (float)alglib::barycentriccalc( throttle_interp, t);
		rtn[2] = (float)alglib::barycentriccalc( brake_interp,  t);

		return rtn;
	}
	std::vector<deepf1::protobuf::LabeledImage> PostProcessingUtils::labelImages(std::vector<deepf1::protobuf::TimestampedUDPData>& udp_data, std::vector<deepf1::protobuf::TimestampedImage>& image_data, unsigned int interpolation_order)
	{
		printf("Labeling points.\n");
		std::vector<deepf1::protobuf::LabeledImage> rtn;
		std::sort(udp_data.begin(), udp_data.end(), &udpComp);
		std::sort(image_data.begin(), image_data.end(), &imageComp);

		rtn.reserve(image_data.size());
		for(unsigned int i = 0; i < image_data.size(); i ++)
		{
			deepf1::protobuf::TimestampedImage image_point = image_data.at(i);

			//printf("Processing image with filename: %s", image_point.image_file().c_str());

			std::pair<deepf1::protobuf::TimestampedUDPData, unsigned int> pair = closestValue(udp_data, image_point.timestamp());
			deepf1::protobuf::TimestampedUDPData closest_packet = udp_data.at(pair.second);
			if ( pair.second < interpolation_order || pair.second >(udp_data.size() - interpolation_order) )
				continue;

		//	printf("Image file %s with index #%u and timestamp %ld is closest to udp index %u with timestamp %ld. Delta=%ld\n",
			//	image_point.image_file().c_str(), i, image_point.timestamp(), pair.second, closest_packet.logger_time(), closest_packet.logger_time()-image_point.timestamp());
			deepf1::protobuf::LabeledImage im;

		//	printf("Interpolating on order %u \n", interpolation_order);
			std::vector<float> interp_results = interp(udp_data, pair.second, interpolation_order, image_point.timestamp());
			im.mutable_label()->set_steering(interp_results[0]);
			im.mutable_label()->set_throttle(interp_results[1]);
			im.mutable_label()->set_brake(interp_results[2]);
			im.set_image_file(std::string(image_point.image_file()));
		//	printf("Done interpolating \n");
			/*
			std::string json;
			google::protobuf::util::MessageToJsonString(rtn.at(i), &json);
			std::cout << "Labeled image: " << json << std::endl;
			*/
			rtn.push_back(im);
		}
	//	rtn.shrink_to_fit();

		return rtn;
	}
	std::vector<deepf1::protobuf::TimestampedUDPData> PostProcessingUtils::parseUDPDirectory(const std::string& directory)
	{
		std::vector<deepf1::protobuf::TimestampedUDPData> rtn;

		std::vector<fs::path> paths;
		fs::path udp_dir(directory);
		get_all(udp_dir, ".json", paths);
		for (fs::path path : paths)
		{
			fs::path current_path = udp_dir / path;

			std::ifstream stream_in(current_path.string());
			std::stringstream buffer;
			buffer << stream_in.rdbuf();
			deepf1::protobuf::TimestampedUDPData data_in;
			google::protobuf::util::Status st = google::protobuf::util::JsonStringToMessage(buffer.str(), &data_in);
			bool success = st == google::protobuf::util::Status::OK;
			stream_in.close();
			if (!success || data_in.timestamp()==0)
			{

				std::cerr << "FOUND EMPTY UDP PACKET" << std::endl;
				continue;
			}
			else
			{
				//std::printf("Got a udp packet with Steering: %f, Throttle: %f, Brake: %f\n", data_in.udp_packet().m_steer(), data_in.udp_packet().m_throttle(), data_in.udp_packet().m_brake());
			}

			//std::string json;
			//google::protobuf::util::MessageToJsonString(data_in, &json);
			//std::cout << "Got data: " << std::endl << json << std::endl;
			rtn.push_back(data_in);
		}

		return rtn;
	}
	std::vector<deepf1::protobuf::TimestampedImage> PostProcessingUtils::parseImageDirectory(const std::string& directory)
	{
		std::vector<deepf1::protobuf::TimestampedImage> rtn;

		std::vector<fs::path> paths;
		fs::path image_dir(directory);
		get_all(image_dir, ".pb", paths);
		std::ifstream stream_in;
		for (fs::path path : paths)
		{
			fs::path current_path = image_dir / path;
			//		std::cout << "Loading file: " << current_path.string() << std::endl;
			stream_in.open(current_path.string().c_str());
			deepf1::protobuf::TimestampedImage data_in;
			bool success = data_in.ParseFromIstream(&stream_in);
			stream_in.close();
			if (!success || data_in.image_file().empty())
			{
				std::cout << "FOUND EMPTY IMAGE FILENAME" << std::endl;
				continue;
			}
			/*
			std::string json;
			google::protobuf::util::MessageToJsonString(data_in, &json);
			std::cout << "Got data: " << std::endl << json << std::endl;
			*/
			rtn.push_back(data_in);
		}

		return rtn;

	}
}
}


/**
 * @file utils.cpp
 * @brief This is a utility class for stomp
 *
 * @author Jorge Nicho
 * @date March 7, 2016
 * @version TODO
 * @bug No known bugs
 *
 * @copyright Copyright (c) 2016, Southwest Research Institute
 *
 * @par License
 * Software License Agreement (Apache License)
 * @par
 * 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
 * @par
 * 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.
 */
#include <stomp_core/utils.h>
#include <cmath>
#include <iostream>
#include <Eigen/Dense>

namespace stomp_core
{

void generateFiniteDifferenceMatrix(int num_time_steps,
                                             DerivativeOrders::DerivativeOrder order,
                                             double dt, Eigen::MatrixXd& diff_matrix)
{

  diff_matrix = Eigen::MatrixXd::Zero(num_time_steps, num_time_steps);
  double multiplier = 1.0/pow(dt,(int)order);
  for (int i=0; i<num_time_steps; ++i)
  {
    for (int j=-FINITE_DIFF_RULE_LENGTH/2; j<=FINITE_DIFF_RULE_LENGTH/2; ++j)
    {
      int index = i+j;
      if (index < 0)
      {
        index = 0;
        continue;
      }
      if (index >= num_time_steps)
      {
        index = num_time_steps-1;
        continue;
      }

      diff_matrix(i,index) = multiplier * FINITE_CENTRAL_DIFF_COEFFS[order][j+FINITE_DIFF_RULE_LENGTH/2];
    }
  }
}

void generateSmoothingMatrix(int num_timesteps,double dt, Eigen::MatrixXd& projection_matrix_M)
{
  using namespace Eigen;

  // generate augmented finite differencing matrix
  int start_index_padded = FINITE_DIFF_RULE_LENGTH-1;
  int num_timesteps_padded = num_timesteps + 2*(FINITE_DIFF_RULE_LENGTH-1);
  MatrixXd finite_diff_matrix_A_padded;
  generateFiniteDifferenceMatrix(num_timesteps_padded,DerivativeOrders::STOMP_ACCELERATION,
                                 dt,finite_diff_matrix_A_padded);

  /* computing control cost matrix (R = A_transpose * A):
   * Note: Original code multiplies the A product by the time interval.  However this is not
   * what was described in the literature
   */
  MatrixXd control_cost_matrix_R_padded = dt*finite_diff_matrix_A_padded.transpose() * finite_diff_matrix_A_padded;
  MatrixXd control_cost_matrix_R = control_cost_matrix_R_padded.block(
      start_index_padded,start_index_padded,num_timesteps,num_timesteps);
  MatrixXd inv_control_cost_matrix_R = control_cost_matrix_R.fullPivLu().inverse();

  // computing projection matrix M
  projection_matrix_M = inv_control_cost_matrix_R;
  double max = 0;
  for(auto t = 0u; t < num_timesteps; t++)
  {
    max = projection_matrix_M(t,t);
    projection_matrix_M.col(t)*= (1.0/(num_timesteps*max)); // scaling such that the maximum value is 1/num_timesteps
  }
}

void differentiate(const Eigen::VectorXd& parameters, DerivativeOrders::DerivativeOrder order,
                          double dt, Eigen::VectorXd& derivatives )
{

  using namespace Eigen;

  derivatives = Eigen::VectorXd::Zero(parameters.size());

  // coefficient arrays
  VectorXd central_coeffs = VectorXd::Map(&FINITE_CENTRAL_DIFF_COEFFS[order][0],FINITE_DIFF_RULE_LENGTH);
  VectorXd forward_coeffs = VectorXd::Map(&FINITE_FORWARD_DIFF_COEFFS[order][0],FINITE_DIFF_RULE_LENGTH);
  VectorXd backward_coeffs = forward_coeffs.reverse();

  // check order eveness
  if(order %2 != 0)
  {
    backward_coeffs*= -1.0;
  }

  // creating difference matrix
  int rule_length = FINITE_DIFF_RULE_LENGTH;
  int size = parameters.size();
  int skip = FINITE_DIFF_RULE_LENGTH/2;
  Eigen::MatrixXd A = Eigen::MatrixXd::Zero(size,size);
  int start_ind, end_ind;
  for(auto i = 0; i < size; i++)
  {
    if(i < skip)
    {
      start_ind = i;
      A.row(i).segment(start_ind,rule_length) = forward_coeffs;
    }
    else if(i >= skip && i < size - skip)
    {
      start_ind = i - skip;
      A.row(i).segment(start_ind,rule_length) = central_coeffs;
    }
    else
    {
      start_ind = i - rule_length+1;
      A.row(i).segment(start_ind,rule_length) = backward_coeffs;
    }
  }

  derivatives = A*parameters/std::pow(dt,2);
}

void toVector(const Eigen::MatrixXd& m,std::vector<Eigen::VectorXd>& v)
{
  v.resize(m.rows(),Eigen::VectorXd::Zero(m.cols()));
  for(auto d = 0u; d < m.rows();d++)
  {
    v[d] = m.row(d);
  }
}

std::string toString(const std::vector<Eigen::VectorXd>& data)
{
  Eigen::IOFormat clean_format(4, 0, ", ", "\n", "[", "]");
  Eigen::MatrixXd m = Eigen::MatrixXd::Zero(data.size(),data.front().size());
  std::stringstream ss;
  for(auto d = 0u; d < data.size(); d++)
  {
    m.row(d) = data[d].transpose();
  }

  ss<<m.format(clean_format);
  return ss.str();
}

std::string toString(const Eigen::MatrixXd& data)
{
  Eigen::IOFormat clean_format(4, 0, ", ", "\n", "[", "]");
  std::stringstream ss;
  ss<<data.format(clean_format);
  return ss.str();
}

std::string toString(const Eigen::VectorXd& data)
{
  Eigen::IOFormat clean_format(4, 0, ", ", "\n", "[", "]");
  std::stringstream ss;
  ss<<data.transpose().format(clean_format);
  return ss.str();
}

}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "main.h"

#include <Eigen/CXX11/Tensor>

using Eigen::Tensor;

template<int DataLayout>
static void test_simple_chip()
{
  Tensor<float, 5, DataLayout> tensor(2,3,5,7,11);
  tensor.setRandom();

  Tensor<float, 4, DataLayout> chip1;
  chip1 = tensor.template chip<0>(1);

  VERIFY_IS_EQUAL(chip1.dimension(0), 3);
  VERIFY_IS_EQUAL(chip1.dimension(1), 5);
  VERIFY_IS_EQUAL(chip1.dimension(2), 7);
  VERIFY_IS_EQUAL(chip1.dimension(3), 11);

  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 5; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          VERIFY_IS_EQUAL(chip1(i,j,k,l), tensor(1,i,j,k,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip2 = tensor.template chip<1>(1);
  VERIFY_IS_EQUAL(chip2.dimension(0), 2);
  VERIFY_IS_EQUAL(chip2.dimension(1), 5);
  VERIFY_IS_EQUAL(chip2.dimension(2), 7);
  VERIFY_IS_EQUAL(chip2.dimension(3), 11);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          VERIFY_IS_EQUAL(chip2(i,j,k,l), tensor(i,1,j,k,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip3 = tensor.template chip<2>(2);
  VERIFY_IS_EQUAL(chip3.dimension(0), 2);
  VERIFY_IS_EQUAL(chip3.dimension(1), 3);
  VERIFY_IS_EQUAL(chip3.dimension(2), 7);
  VERIFY_IS_EQUAL(chip3.dimension(3), 11);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          VERIFY_IS_EQUAL(chip3(i,j,k,l), tensor(i,j,2,k,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip4(tensor.template chip<3>(5));
  VERIFY_IS_EQUAL(chip4.dimension(0), 2);
  VERIFY_IS_EQUAL(chip4.dimension(1), 3);
  VERIFY_IS_EQUAL(chip4.dimension(2), 5);
  VERIFY_IS_EQUAL(chip4.dimension(3), 11);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          VERIFY_IS_EQUAL(chip4(i,j,k,l), tensor(i,j,k,5,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip5(tensor.template chip<4>(7));
  VERIFY_IS_EQUAL(chip5.dimension(0), 2);
  VERIFY_IS_EQUAL(chip5.dimension(1), 3);
  VERIFY_IS_EQUAL(chip5.dimension(2), 5);
  VERIFY_IS_EQUAL(chip5.dimension(3), 7);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          VERIFY_IS_EQUAL(chip5(i,j,k,l), tensor(i,j,k,l,7));
        }
      }
    }
  }
}

template<int DataLayout>
static void test_dynamic_chip()
{
  Tensor<float, 5, DataLayout> tensor(2,3,5,7,11);
  tensor.setRandom();

  Tensor<float, 4, DataLayout> chip1;
  chip1 = tensor.chip(1, 0);
  VERIFY_IS_EQUAL(chip1.dimension(0), 3);
  VERIFY_IS_EQUAL(chip1.dimension(1), 5);
  VERIFY_IS_EQUAL(chip1.dimension(2), 7);
  VERIFY_IS_EQUAL(chip1.dimension(3), 11);
  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 5; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          VERIFY_IS_EQUAL(chip1(i,j,k,l), tensor(1,i,j,k,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip2 = tensor.chip(1, 1);
  VERIFY_IS_EQUAL(chip2.dimension(0), 2);
  VERIFY_IS_EQUAL(chip2.dimension(1), 5);
  VERIFY_IS_EQUAL(chip2.dimension(2), 7);
  VERIFY_IS_EQUAL(chip2.dimension(3), 11);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          VERIFY_IS_EQUAL(chip2(i,j,k,l), tensor(i,1,j,k,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip3 = tensor.chip(2, 2);
  VERIFY_IS_EQUAL(chip3.dimension(0), 2);
  VERIFY_IS_EQUAL(chip3.dimension(1), 3);
  VERIFY_IS_EQUAL(chip3.dimension(2), 7);
  VERIFY_IS_EQUAL(chip3.dimension(3), 11);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          VERIFY_IS_EQUAL(chip3(i,j,k,l), tensor(i,j,2,k,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip4(tensor.chip(5, 3));
  VERIFY_IS_EQUAL(chip4.dimension(0), 2);
  VERIFY_IS_EQUAL(chip4.dimension(1), 3);
  VERIFY_IS_EQUAL(chip4.dimension(2), 5);
  VERIFY_IS_EQUAL(chip4.dimension(3), 11);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          VERIFY_IS_EQUAL(chip4(i,j,k,l), tensor(i,j,k,5,l));
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> chip5(tensor.chip(7, 4));
  VERIFY_IS_EQUAL(chip5.dimension(0), 2);
  VERIFY_IS_EQUAL(chip5.dimension(1), 3);
  VERIFY_IS_EQUAL(chip5.dimension(2), 5);
  VERIFY_IS_EQUAL(chip5.dimension(3), 7);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          VERIFY_IS_EQUAL(chip5(i,j,k,l), tensor(i,j,k,l,7));
        }
      }
    }
  }
}

template<int DataLayout>
static void test_chip_in_expr() {
  Tensor<float, 5, DataLayout> input1(2,3,5,7,11);
  input1.setRandom();
  Tensor<float, 4, DataLayout> input2(3,5,7,11);
  input2.setRandom();

  Tensor<float, 4, DataLayout> result = input1.template chip<0>(0) + input2;
  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 5; ++j) {
      for (int k = 0; k < 7; ++k) {
        for (int l = 0; l < 11; ++l) {
          float expected = input1(0,i,j,k,l) + input2(i,j,k,l);
          VERIFY_IS_EQUAL(result(i,j,k,l), expected);
        }
      }
    }
  }

  Tensor<float, 3, DataLayout> input3(3,7,11);
  input3.setRandom();
  Tensor<float, 3, DataLayout> result2 = input1.template chip<0>(0).template chip<1>(2) + input3;
  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 7; ++j) {
      for (int k = 0; k < 11; ++k) {
        float expected = input1(0,i,2,j,k) + input3(i,j,k);
        VERIFY_IS_EQUAL(result2(i,j,k), expected);
      }
    }
  }
}

template<int DataLayout>
static void test_chip_as_lvalue()
{
  Tensor<float, 5, DataLayout> input1(2,3,5,7,11);
  input1.setRandom();

  Tensor<float, 4, DataLayout> input2(3,5,7,11);
  input2.setRandom();
  Tensor<float, 5, DataLayout> tensor = input1;
  tensor.template chip<0>(1) = input2;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          for (int m = 0; m < 11; ++m) {
            if (i != 1) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input2(j,k,l,m));
            }
          }
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> input3(2,5,7,11);
  input3.setRandom();
  tensor = input1;
  tensor.template chip<1>(1) = input3;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          for (int m = 0; m < 11; ++m) {
            if (j != 1) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input3(i,k,l,m));
            }
          }
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> input4(2,3,7,11);
  input4.setRandom();
  tensor = input1;
  tensor.template chip<2>(3) = input4;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          for (int m = 0; m < 11; ++m) {
            if (k != 3) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input4(i,j,l,m));
            }
          }
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> input5(2,3,5,11);
  input5.setRandom();
  tensor = input1;
  tensor.template chip<3>(4) = input5;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          for (int m = 0; m < 11; ++m) {
            if (l != 4) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input5(i,j,k,m));
            }
          }
        }
      }
    }
  }

  Tensor<float, 4, DataLayout> input6(2,3,5,7);
  input6.setRandom();
  tensor = input1;
  tensor.template chip<4>(5) = input6;
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          for (int m = 0; m < 11; ++m) {
            if (m != 5) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input6(i,j,k,l));
            }
          }
        }
      }
    }
  }

  Tensor<float, 5, DataLayout> input7(2,3,5,7,11);
  input7.setRandom();
  tensor = input1;
  tensor.chip(0, 0) = input7.chip(0, 0);
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          for (int m = 0; m < 11; ++m) {
            if (i != 0) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input7(i,j,k,l,m));
            }
          }
        }
      }
    }
  }
}

static void test_chip_raw_data_col_major()
{
  Tensor<float, 5, ColMajor> tensor(2,3,5,7,11);
  tensor.setRandom();

  typedef TensorEvaluator<decltype(tensor.chip<4>(3)), DefaultDevice> Evaluator4;
  auto chip = Evaluator4(tensor.chip<4>(3), DefaultDevice());
  for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
      for (int k = 0; k < 5; ++k) {
        for (int l = 0; l < 7; ++l) {
          int chip_index = i + 2 * (j + 3 * (k + 5 * l));
          VERIFY_IS_EQUAL(chip.data()[chip_index], tensor(i,j,k,l,3));
        }
      }
    }
  }

  typedef TensorEvaluator<decltype(tensor.chip<0>(0)), DefaultDevice> Evaluator0;
  auto chip0 = Evaluator0(tensor.chip<0>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip0.data(), static_cast<float*>(0));

  typedef TensorEvaluator<decltype(tensor.chip<1>(0)), DefaultDevice> Evaluator1;
  auto chip1 = Evaluator1(tensor.chip<1>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip1.data(), static_cast<float*>(0));

  typedef TensorEvaluator<decltype(tensor.chip<2>(0)), DefaultDevice> Evaluator2;
  auto chip2 = Evaluator2(tensor.chip<2>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip2.data(), static_cast<float*>(0));

  typedef TensorEvaluator<decltype(tensor.chip<3>(0)), DefaultDevice> Evaluator3;
  auto chip3 = Evaluator3(tensor.chip<3>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip3.data(), static_cast<float*>(0));
}

static void test_chip_raw_data_row_major()
{
  Tensor<float, 5, RowMajor> tensor(11,7,5,3,2);
  tensor.setRandom();

  typedef TensorEvaluator<decltype(tensor.chip<0>(3)), DefaultDevice> Evaluator0;
  auto chip = Evaluator0(tensor.chip<0>(3), DefaultDevice());
  for (int i = 0; i < 7; ++i) {
    for (int j = 0; j < 5; ++j) {
      for (int k = 0; k < 3; ++k) {
        for (int l = 0; l < 2; ++l) {
          int chip_index = l + 2 * (k + 3 * (j + 5 * i));
          VERIFY_IS_EQUAL(chip.data()[chip_index], tensor(3,i,j,k,l));
        }
      }
    }
  }

  typedef TensorEvaluator<decltype(tensor.chip<1>(0)), DefaultDevice> Evaluator1;
  auto chip1 = Evaluator1(tensor.chip<1>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip1.data(), static_cast<float*>(0));

  typedef TensorEvaluator<decltype(tensor.chip<2>(0)), DefaultDevice> Evaluator2;
  auto chip2 = Evaluator2(tensor.chip<2>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip2.data(), static_cast<float*>(0));

  typedef TensorEvaluator<decltype(tensor.chip<3>(0)), DefaultDevice> Evaluator3;
  auto chip3 = Evaluator3(tensor.chip<3>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip3.data(), static_cast<float*>(0));

  typedef TensorEvaluator<decltype(tensor.chip<4>(0)), DefaultDevice> Evaluator4;
  auto chip4 = Evaluator4(tensor.chip<4>(0), DefaultDevice());
  VERIFY_IS_EQUAL(chip4.data(), static_cast<float*>(0));
}

void test_cxx11_tensor_chipping()
{
  CALL_SUBTEST(test_simple_chip<ColMajor>());
  CALL_SUBTEST(test_simple_chip<RowMajor>());
  CALL_SUBTEST(test_dynamic_chip<ColMajor>());
  CALL_SUBTEST(test_dynamic_chip<RowMajor>());
  CALL_SUBTEST(test_chip_in_expr<ColMajor>());
  CALL_SUBTEST(test_chip_in_expr<RowMajor>());
  CALL_SUBTEST(test_chip_as_lvalue<ColMajor>());
  CALL_SUBTEST(test_chip_as_lvalue<RowMajor>());
  CALL_SUBTEST(test_chip_raw_data_col_major());
  CALL_SUBTEST(test_chip_raw_data_row_major());
}


#ifndef EXPRBASE_HPP
#define EXPRBASE_HPP

#include "gentree.hpp"
#include <experimental/optional>
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <cmath>
#include <typeinfo>
#include <typeindex>
#include "scalartype.hpp"

/*
#include <experimental/any>

using any = std::experimental::any;

template<typename T>
auto MYany_cast(const any &a) {
	     return std::experimental::any_cast<T>(a);
}
template<typename T>
auto MYany_cast(any &a) {
	     return std::experimental::any_cast<T>(a);
}

#include <boost/spirit/home/support/detail/hold_any.hpp>

using any = boost::spirit::hold_any;

template<typename T>
auto MYany_cast(const any &a) {
	return boost::spirit::any_cast<T>(a);
}
template<typename T>
auto MYany_cast(any &a) {
	return boost::spirit::any_cast<T>(a);
}
*/

#include <boost/any.hpp>

using any = boost::any;

template<typename T>
auto MYany_cast(const any &a) {
	return boost::any_cast<T>(a);
}
template<typename T>
auto MYany_cast(any &a) {
	return boost::any_cast<T>(a);
}

struct noexprT {};

struct constval {
	any v;
	constval(any val) : v{std::move(val)} {}
	template<typename T>
	constval(T val) : v{std::move(val)} {}
};

struct varinfo {
	std::string name;
	const std::type_info &t;

	varinfo(const std::string &n, const std::type_info &ti) : name(n), t(ti) {}
};

struct var : public std::shared_ptr<varinfo> {
	var(const varinfo &vi) :
		std::shared_ptr<varinfo>(std::make_shared<varinfo>(vi)) {}
	var(varinfo &&vi) :
		std::shared_ptr<varinfo>(std::make_shared<varinfo>(std::move(vi))) {}
};

struct internalT {
	any v;
	internalT(any val) : v{std::move(val)} {}
	template<typename T>
	internalT(T val) : v{std::move(val)} {}
};

//typedef boost::variant<noexprT,constval,var,internalT> leaf;
typedef any leaf;
struct opinfo;

typedef std::shared_ptr<opinfo> op;
using expr = gentree<leaf,op>;

any eval(const expr &e);

struct opinfo {
	// perhaps narg isn't needed (narg=0 => variable)
	std::size_t narg;

	// below is for conversion to human-read string
	std::string name,treename;
	bool infix;
	bool leftassoc;
	int prec;

	opinfo(std::size_t na, const std::string &n, bool inf, bool la, int pr)
		: narg(na), name(n), treename(n),
		  infix(inf), leftassoc(la), prec(pr) {}

	opinfo(std::size_t na, const std::string &n, const std::string &tn,
				bool inf, bool la, int pr)
		: narg(na), name(n), treename(tn),
		  infix(inf), leftassoc(la), prec(pr) {}

	virtual any opeval() const { return {}; };
	virtual any opeval(const any &x1) const { return {}; };
	virtual any opeval(const any &x1, const any &x2) const { return {}; };
	virtual any opeval(const any &x1, const any &x2,
			const any &x3) const { return {}; };
	virtual any opeval(const std::vector<any> &x) const {
		switch(x.size()) {
			case 0: return opeval();
			case 1: return opeval(x[0]);
			case 2: return opeval(x[0],x[1]);
			case 3: return opeval(x[0],x[1],x[2]);
		}
	}

	virtual any eval(const std::vector<expr> &x) const {
		std::vector<any> v;
		v.reserve(x.size());
		for(auto &e : x)
			v.emplace_back(::eval(e));
		return opeval(v);
	}

	virtual bool caneval() const { return true; }
};

namespace std {
	template<> struct hash<var> {
		typedef var argument_type;
		typedef std::size_t result_type;
		result_type operator()(argument_type const &s) const {
			return std::hash<std::shared_ptr<varinfo>>{}(s);
		}
	};
	template<> struct hash<expr> {
		typedef expr argument_type;
		typedef std::size_t result_type;
		result_type operator()(argument_type const &s) const {
			return s.ptrhash();
		}
	};

	bool operator==(const expr &e1, const expr &e2) {
		return e1.sameptr(e2);
	}
	bool operator!=(const expr &e1, const expr &e2) {
		return !(e1==e2);
	}

}

expr newvar(const std::string &name, const std::type_info &ti) {
	return expr{var{varinfo(name,ti)}};
}

template<typename T>
expr newvar(const std::string &name) {
	return expr{var{varinfo(name,typeid(T))}};
}

std::size_t globalvarnum__ = 0;

expr newvar(const std::type_info &ti) {
	return newvar(std::string("__")+std::to_string(++globalvarnum__),ti);
}

template<typename T>
expr newvar() {
	return newvar(typeid(T));
}

template<typename T>
expr newconst(const T &v) {
	constval t{v};
	return expr{t};
}

any getconstany(const expr &e) {
	//return boost:get<constval>(e.asleaf()).v;
	return MYany_cast<constval>(e.asleaf()).v;
}

template<typename T>
T getconst(const expr &e) {
	return MYany_cast<T>(getconstany(e));
}

var getvar(const expr &e) {
	//return boost:get<var>(e.asleaf());
	return MYany_cast<var>(e.asleaf());
}

const std::type_info &getvartype(const expr &e) {
	//return (boost:get<var>(e.asleaf()))->t;
	return (MYany_cast<var>(e.asleaf()))->t;
}

template<typename T>
bool isop(const expr &e) {
	if (e.isleaf()) return false;
	auto p = std::dynamic_pointer_cast<T>(e.asnode());
	return (bool)p;
}

bool isop(const expr &e, op o) {
	return !e.isleaf() && e.asnode()==o;
}

bool isconst(const expr &e) {
	return e.isleaf() && e.asleaf().type()==typeid(constval);
}

bool isvar(const expr &e) {
	return e.isleaf() && e.asleaf().type()==typeid(var);
}

using vset = std::unordered_set<var>;
using vmap = std::unordered_map<var,var>;
//using vkmap = std::unordered_map<var,any>;

struct scopeinfo;// : public opinfo;

// do not call!
bool isconstexpr1(const expr &e,
		const optional<vset> &vars,
		vset &exceptvars) {
	if (e.isleaf()) {
		if (isconst(e)) return true;
		if (!isvar(e)) return false;
		var v = getvar(e);
		return (vars && vars->find(v)==vars->end())
				|| exceptvars.find(v)!=exceptvars.end();
	} else {
		auto n = std::dynamic_pointer_cast<scopeinfo>(e.asnode());
		if (n) {
			auto v = getvar(e.children()[0]);
			exceptvars.emplace(v);
		}
		for(auto &c : e.children())
			// no need to undo changes to exceptvars, because
			// we will return false all the way back to the top
			if (!isconstexpr1(c,vars,exceptvars)) return false;
		if (n) {
			auto v = getvar(e.children()[0]);
			exceptvars.erase(v);
		}
		return true;
	}
}

// vars == empty => "all vars"
bool isconstexpr(const expr &e, const optional<vset> &vars = {}) {
	vset ex;
	return isconstexpr1(e,vars,ex);
}

bool isconstexpr(const expr &e, const var &v) {
    vset ex;
    optional<vset> vars{in_place,{v}};
    return isconstexpr1(e,vars,ex);
}

bool isconstexprexcept(const expr &e, const var &v) {
    vset ex{v};
    optional<vset> vars{};
    return isconstexpr1(e,vars,ex);
}

// do not call!
bool isnonconstexpr1(const expr &e,
		const optional<vset> &vars,
		vset &exceptvars) {
	if (e.isleaf()) {
		if (isconst(e)) return false;
		if (!isvar(e)) return false; // (not sure what to place here...
						// ... only reason this isn't !isconstexpr
		var v = getvar(e);
		return (!vars || vars->find(v)!=vars->end())
				&& exceptvars.find(v)==exceptvars.end();
	} else {
		auto n = std::dynamic_pointer_cast<scopeinfo>(e.asnode());
		if (n) {
			auto v = getvar(e.children()[0]);
			exceptvars.emplace(v);
		}
		for(auto &c : e.children())
			// no need to undo changes to exceptvars, because
			// we will return true all the way back to the top
			if (isnonconstexpr1(c,vars,exceptvars)) return true;
		if (n) {
			auto v = getvar(e.children()[0]);
			exceptvars.erase(v);
		}
		return false;
	}
}

// vars == empty => "all vars"
bool isnonconstexpr(const expr &e,
		const optional<vset> &vars = {}) {
	vset ex;
	return isnonconstexpr1(e,vars,ex);
}

bool operator==(const leaf &a, const leaf &b) {
     std::unordered_map<std::type_index,
		std::function<bool(const any &, const any &)>>
          eqcmplookupconst =
           {
             {typeid(scalarreal),[](const any &a, const any &b)
		    	{ return MYany_cast<scalarreal>(a) == MYany_cast<scalarreal>(b); }},
             {typeid(double),[](const any &a, const any &b)
		    	{ return MYany_cast<double>(a) == MYany_cast<double>(b); }},
             {typeid(long double),[](const any &a, const any &b)
		    	{ return MYany_cast<long double>(a) == MYany_cast<long double>(b); }},
             {typeid(float),[](const any &a, const any &b)
		    	{ return MYany_cast<float>(a) == MYany_cast<float>(b); }},
             {typeid(int),[](const any &a, const any &b)
		    	{ return MYany_cast<int>(a) == MYany_cast<int>(b); }},
             {typeid(unsigned int),[](const any &a, const any &b)
		    	{ return MYany_cast<unsigned int>(a) == MYany_cast<unsigned int>(b); }},
             {typeid(unsigned long),[](const any &a, const any &b)
		    	{ return MYany_cast<unsigned long>(a) == MYany_cast<unsigned long>(b); }},
             {typeid(unsigned long long),[](const any &a, const any &b)
		    	{ return MYany_cast<unsigned long long>(a) == MYany_cast<unsigned long long>(b); }},
		};

     std::unordered_map<std::type_index,std::function<bool(const any &, const any &)>>
          eqcmplookupleaf =
           {
			 {typeid(constval),[eqcmplookupconst](const any &a, const any &b)
				 { constval aa = MYany_cast<constval>(a);
					 constval bb = MYany_cast<constval>(b);
					 return aa.v.type()==bb.v.type()
						 && eqcmplookupconst.at(aa.v.type())(aa.v,bb.v);
				 }},
		   {typeid(var),[](const any &a, const any &b)
		     { return MYany_cast<var>(a) == MYany_cast<var>(b); }},
		   {typeid(noexprT),[](const any &a, const any &b)
		     { return true; }},
		   /*
		   {typeid(placeholder),[](const any &a, const any &b)
		     { return MYany_cast<placeholder>(a).num
				== MYany_cast<placeholder>(b).num; }},
				*/
		   {typeid(op),[](const any &a, const any &b)
		     { return MYany_cast<op>(a) == MYany_cast<op>(b); }},
            };

     return a.type()==b.type() && eqcmplookupleaf.at(a.type())(a,b);
}

template<typename OP, typename T1, typename T2>
struct binopinfo : public opinfo {
	OP op;
	binopinfo(const std::string &opname, bool inf, bool la, int pr)
		: opinfo(2,opname,inf,la,pr), op{} {}

	virtual any opeval(const any &x1, const any &x2) const {
		return {op(MYany_cast<T1>(x1),MYany_cast<T2>(x2))};
	}
};

template<typename OP, typename T1>
struct uniopinfo : public opinfo {
	OP op;
	uniopinfo(const std::string &opname, bool inf, bool la, int pr)
		: opinfo(1,opname,inf,la,pr), op{} {}

	virtual any opeval(const any &x1) const {
		return {op(MYany_cast<T1>(x1))};
	}
};

struct opchain : public opinfo {
	op baseop;
	opchain(op bop) : baseop(bop),
	opinfo(0,bop->name,bop->name+"C",bop->infix,bop->leftassoc,bop->prec) {}

	virtual any opeval(const any &x1) const {
		return x1;
	}
	virtual any opeval(const any &x1, const any &x2) const {
		return baseop->opeval(x1,x2);
	}
	virtual any opeval(const any &x1, const any &x2, const any &x3) const {
		if (leftassoc) return baseop->opeval(baseop->opeval(x1,x2),x3);
		else baseop->opeval(x1,baseop->opeval(x2,x3));
	}
	virtual any opeval(const std::vector<any> &x) const {
		if (leftassoc) return lefteval(*(x.begin()),x.begin()+1,x.end());
		else return righteval(x.begin(),x.end());
	}

	template<typename I>
		any lefteval(const any x, const I &b, const I &e) const {
			if (b==e) return x;
			return lefteval(baseop->opeval(x,*b),b+1,e);
		}

	template<typename I>
		any righteval(const I &b, const I &e) const {
			if (b+1==e) return *b;
			return baseop->opeval(*b,righteval(b+1,e));
		}
};

// abstract, for operations (like sum, deriv, integral)
//  that introduce a local variable
//  This just flags that the first child is
//    the new local variable
struct scopeinfo : public opinfo {
	using opinfo::opinfo;

	virtual bool caneval(const std::vector<expr> &x) const { return false; }
	virtual any eval(const std::vector<expr> &x) const { return {}; }

};

any eval(const expr &e) {
	if (e.isleaf()) {
		if (isconst(e)) return MYany_cast<constval>(e.asleaf()).v;
		return {};
	} else return e.asnode()->eval(e.children());
}

expr substitute(const expr &e, const expr &x, const expr &v);

struct evalatinfo : public scopeinfo {
	// 3 children: v, e, val
	// represents evaluating e where variable v is replaced by val
	evalatinfo(): scopeinfo(3,"eval",false,false,5) {}

	virtual bool caneval(const std::vector<expr> &x) const {
		return isconst(x[1]) || (isconst(x[2]) && isvar(x[0])
			&& isconstexprexcept(x[1],getvar(x[0])));
	}

	virtual any eval(const std::vector<expr> &x) const {
		return ::eval(substitute(x[1],x[0],x[2]));
	}
};

const op evalatop  = std::make_shared<evalatinfo>();

const expr noexpr {noexprT{}};

expr evalat(const expr &e, const expr &localx, const expr &x) {
	return {evalatop,localx,e,x};
}

#endif


// Naive Fibonacci calculation

#include <active/shared.hpp>
#include <active/promise.hpp>
#include <iostream>

#ifdef ACTIVE_USE_BOOST
#include <boost/make_shared.hpp>
#endif

typedef active::basic ao_type;

struct fib_result
{
	virtual void sub_result(int value)=0;
};

struct fib : public active::shared<fib, ao_type>, public fib_result
{
	void calculate(int value, active::platform::shared_ptr<fib_result> result)
	{
		active_fn([=]
		{
			if( value > 2 )
			{
				this->m_total=0;
				this->m_result = result;
				active::platform::make_shared<fib>()->calculate(value-1,shared_from_this());
				active::platform::make_shared<fib>()->calculate(value-2,shared_from_this());
			}
			else
			{
				result->sub_result(1);
			}
		});
	}

	void sub_result(int value)
	{
		active_fn([=]
		{
			if(this->m_total)
				this->m_result->sub_result(m_total+value);
			else
				this->m_total = value;
		});
	}

private:
	int m_total;
	active::platform::shared_ptr<fib_result> m_result;
};

struct display : public fib_result
{
	void sub_result(int i)
	{
		std::cout << "Result = " << i << std::endl;
	}
};

int main(int argc, char**argv)
{
	if( argc<2 ) { std::cout << "Usage: fib N\n"; return 1; }
	active::platform::make_shared<fib>()->calculate(atoi(argv[1]), active::platform::make_shared<display>());
	active::run();
}


/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 * Unit Tests for the methods of the TriPrism class
 */

#define BOOST_TEST_MODULE TriPrism
#include <boost/test/unit_test.hpp>
#include <boost/test/output_test_stream.hpp>
#include <stdexcept>

#include "TriPrism.h"
#include "Triangle3D.h"
#include "EuclideanPoint.h"

using namespace cupcfd::geometry::shapes;
namespace euc = cupcfd::geometry::euclidean;
namespace utf = boost::unit_test;

// === Constructor ===
// Test 1:
BOOST_AUTO_TEST_CASE(constructor_test1, * utf::tolerance(0.00001))
{
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tf(2.5, 5.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> bf(2.5, 5.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);

	// TriPrism<double> shape(tf, tlb, trb, bf, blb, brb);
	Triangle3D<double> triTop(tf, trb, tlb);
	Triangle3D<double> triBottom(bf, blb, brb);
	TriPrism<double> shape(triTop, triBottom);

	BOOST_TEST(shape.top.vertices[0].cmp[0] == 2.5);
	BOOST_TEST(shape.top.vertices[0].cmp[0] == 2.5);
	BOOST_TEST(shape.top.vertices[0].cmp[1] == 5.0);
	BOOST_TEST(shape.top.vertices[0].cmp[2] == 12.0);
	BOOST_TEST(shape.top.vertices[2].cmp[0] == 0.0);
	BOOST_TEST(shape.top.vertices[2].cmp[1] == 0.0);
	BOOST_TEST(shape.top.vertices[2].cmp[2] == 12.0);
	BOOST_TEST(shape.top.vertices[1].cmp[0] == 5.0);
	BOOST_TEST(shape.top.vertices[1].cmp[1] == 0.0);
	BOOST_TEST(shape.top.vertices[1].cmp[2] == 12.0);

	BOOST_TEST(shape.bottom.vertices[0].cmp[0] == 2.5);
	BOOST_TEST(shape.bottom.vertices[0].cmp[1] == 5.0);
	BOOST_TEST(shape.bottom.vertices[0].cmp[2] == 0.0);
	BOOST_TEST(shape.bottom.vertices[1].cmp[0] == 0.0);
	BOOST_TEST(shape.bottom.vertices[1].cmp[1] == 0.0);
	BOOST_TEST(shape.bottom.vertices[1].cmp[2] == 0.0);
	BOOST_TEST(shape.bottom.vertices[2].cmp[0] == 5.0);
	BOOST_TEST(shape.bottom.vertices[2].cmp[1] == 0.0);
	BOOST_TEST(shape.bottom.vertices[2].cmp[2] == 0.0);
}

// === getCentroid ===
// Test 1: Correctly compute centroid
BOOST_AUTO_TEST_CASE(getCentroid_test1, * utf::tolerance(0.00001))
{

}

// === getVolume ===
// Test 1: Correctly compute volume
BOOST_AUTO_TEST_CASE(getVolume_test1, * utf::tolerance(0.00001))
{

}

// Test 2: Correctly compute volume of oblique triprism
BOOST_AUTO_TEST_CASE(getVolume_test2, * utf::tolerance(0.00001))
{

}

// === isPointInside ===
// Test 1: Point Inside TriPrism
BOOST_AUTO_TEST_CASE(isPointInside_test1, * utf::tolerance(0.00001))
{

}

// Test 2: Point Outside TriPrism
BOOST_AUTO_TEST_CASE(isPointInside_test2, * utf::tolerance(0.00001))
{

}

// Test 3: Point On Point a
BOOST_AUTO_TEST_CASE(isPointInside_test3, * utf::tolerance(0.00001))
{

}

// Test 4: Point On Point b
BOOST_AUTO_TEST_CASE(isPointInside_test4, * utf::tolerance(0.00001))
{

}

// Test 5: Point On Point c
BOOST_AUTO_TEST_CASE(isPointInside_test5, * utf::tolerance(0.00001))
{

}

// Test 6: Point On Point d
BOOST_AUTO_TEST_CASE(isPointInside_test6, * utf::tolerance(0.00001))
{

}

// Test 7: Point On Point e
BOOST_AUTO_TEST_CASE(isPointInside_test7, * utf::tolerance(0.00001))
{

}

// Test 8: Point On Point f
BOOST_AUTO_TEST_CASE(isPointInside_test8, * utf::tolerance(0.00001))
{

}

// Test 9: Point On Top Face
BOOST_AUTO_TEST_CASE(isPointInside_test9, * utf::tolerance(0.00001))
{

}

// Test 10: Point On Bottom Face
BOOST_AUTO_TEST_CASE(isPointInside_test10, * utf::tolerance(0.00001))
{

}

// Test 11: Point Side Face 1
BOOST_AUTO_TEST_CASE(isPointInside_test11, * utf::tolerance(0.00001))
{

}

// Test 12: Point Side Face 2
BOOST_AUTO_TEST_CASE(isPointInside_test12, * utf::tolerance(0.00001))
{

}

// Test 13: Point Side Face 3
BOOST_AUTO_TEST_CASE(isPointInside_test13, * utf::tolerance(0.00001))
{

}

// Test 14: Point On Centroid
BOOST_AUTO_TEST_CASE(isPointInside_test14, * utf::tolerance(0.00001))
{

}

// === isPointOnEdge ===
// Test 1: Test point that is not on an edge
BOOST_AUTO_TEST_CASE(isPointOnEdge_test1, * utf::tolerance(0.00001))
{
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tf(2.5, 5.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> bf(2.5, 5.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);

	Triangle3D<double> triTop(tf, trb, tlb);
	Triangle3D<double> triBottom(bf, blb, brb);
	TriPrism<double> shape(triTop, triBottom);
	euc::EuclideanPoint<double, 3> point(1.24, 4.9, 12.0);

	bool onEdge = shape.isPointOnEdge(point);
	BOOST_CHECK_EQUAL(onEdge, false);
}

// Test 2: Test point that is on an edge
BOOST_AUTO_TEST_CASE(isPointOnEdge_test2, * utf::tolerance(0.00001))
{
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tf(2.5, 5.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> bf(2.5, 5.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);

	Triangle3D<double> triTop(tf, trb, tlb);
	Triangle3D<double> triBottom(bf, blb, brb);
	TriPrism<double> shape(triTop, triBottom);
	euc::EuclideanPoint<double, 3> point(4.78, 0.0, 12.0);

	bool onEdge = shape.isPointOnEdge(point);
	BOOST_CHECK_EQUAL(onEdge, true);
}

// === isPointOnVertex ===
// Test 1: Test point that is not on a vertex
BOOST_AUTO_TEST_CASE(isPointOnVertex_test1, * utf::tolerance(0.00001))
{
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tf(2.5, 5.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> bf(2.5, 5.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);

	Triangle3D<double> triTop(tf, trb, tlb);
	Triangle3D<double> triBottom(bf, blb, brb);
	TriPrism<double> shape(triTop, triBottom);
	euc::EuclideanPoint<double, 3> point(4.78, 0.0, 12.0);

	bool onEdge = shape.isPointOnVertex(point);
	BOOST_CHECK_EQUAL(onEdge, false);
}

// Test 2: Test point that is on a vertex
BOOST_AUTO_TEST_CASE(isPointOnVertex_test2, * utf::tolerance(0.00001))
{
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tf(2.5, 5.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> bf(2.5, 5.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	cupcfd::geometry::euclidean::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);

	Triangle3D<double> triTop(tf, trb, tlb);
	Triangle3D<double> triBottom(bf, blb, brb);
	TriPrism<double> shape(triTop, triBottom);
	euc::EuclideanPoint<double, 3> point(5.0, 0.0, 12.0);

	bool onEdge = shape.isPointOnVertex(point);
	BOOST_CHECK_EQUAL(onEdge, true);
}


#pragma once

// system includes ---------------------------------------------------------
#include <boost/assert.hpp>
#include <boost/lexical_cast.hpp>
#include <iomanip>
#include <iostream>
#include <limits>
#include <string>
// own includes ------------------------------------------------------------
#include "base/logger.hpp"
#include "ridgelet/ridgelet_cell_array.hpp"

class CG
{
 public:
  template <typename coeff_t, typename OPERATOR>
  bool solve(RidgeletCellArray<coeff_t> &x,
             const OPERATOR &A,
             const RidgeletCellArray<coeff_t> &b,
             const double reltol,
             const int maxit);

  template <typename DERIVED1, typename OPERATOR, typename DERIVED2>
  bool solve(Eigen::ArrayBase<DERIVED1> &x,
             const OPERATOR &A,
             const Eigen::ArrayBase<DERIVED2> &b,
             const double reltol,
             const int maxit);

  //@{
  /// status / log
  int iter() const { return iter_; }
  double relres() const { return relres_; }
  void set_log(bool log) { log_ = log; }
  void set_log_hist(bool t);
  //@}

 private:
  double relres_ = std::numeric_limits<double>::max();
  int iter_ = -1;
  bool log_ = false;
  bool log_hist_ = false;
};

template <typename coeff_t, typename OPERATOR>
bool
CG::solve(RidgeletCellArray<coeff_t> &x,
          const OPERATOR &A,
          const RidgeletCellArray<coeff_t> &b,
          const double reltol,
          const int maxit)
{
  typedef RidgeletCellArray<coeff_t> rca_t;
  typedef typename coeff_t::Scalar numeric_t;

  rca_t r(x.rf());
  r.resize(x);

  A.apply(r, x);
  r.sadd(-1, b, 1);

  double rho = r.norm();
  rho *= rho;
  if (rho < 1e-15) {
    relres_ = 0;
    iter_ = 0;
    if (log_)
      Logger::GetInstance() << (boost::lexical_cast<std::string>(iter_) + " " +
                                boost::lexical_cast<std::string>(relres_));
    return true;
  }

  rca_t p(x.rf());
  p.resize(x);

  rca_t Ap(x.rf());
  Ap.resize(x);

  p = r;
  for (iter_ = 1; iter_ <= maxit; ++iter_) {
    A.apply(Ap, p);
    // std::cout << "norm Ap: " << Ap.norm() << "\n";
    numeric_t alpha = rho / p.dot(Ap);
    x.sadd(1, p, alpha);
    r.sadd(1, Ap, -alpha);
    double old_rho = rho;
    double norm_r = r.norm();
    double norm_x = x.norm();
    //    std::cout << "norm_x: " << norm_x << "\n";
    // std::cout << "norm_r: " << norm_r << "\n";
    relres_ = norm_r / norm_x;
    if (log_ && log_hist_)
      Logger::GetInstance() << (boost::lexical_cast<std::string>(iter_) + " " +
                                boost::lexical_cast<std::string>(relres_));
    // std::cout << "CG::relres_ " << i << "\t" << std::setw(10) <<
    // std::setprecision(4) <<
    // std::scientific << relres_ << "\n";
    if (relres_ <= reltol) {
      // std::cout << "CG::Success after " << i << " iterations." << "\n";
      if (log_)
        Logger::GetInstance() << (boost::lexical_cast<std::string>(iter_) + " " +
                                  boost::lexical_cast<std::string>(relres_));
      return true;
    }

    rho = norm_r * norm_r;
    double beta = rho / old_rho;
    p.sadd(beta, r, 1);
  }
  return false;
}

template <typename DERIVED1, typename OPERATOR, typename DERIVED2>
bool
CG::solve(Eigen::ArrayBase<DERIVED1> &x,
          const OPERATOR &A,
          const Eigen::ArrayBase<DERIVED2> &b,
          const double reltol,
          const int maxit)
{
  BOOST_ASSERT_MSG(x.rows() == b.rows() && x.cols() == b.cols(), "dimension mismatch");

  static_assert(std::is_same<typename DERIVED1::Scalar, typename DERIVED2::Scalar>::value,
                "type mismatch");
  typedef typename DERIVED1::Scalar numeric_t;

  typedef Eigen::Array<numeric_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> array_t;

  array_t r(x.rows(), x.cols());

  A.apply(r, x.derived());
  r = b - r;
  double rho = std::sqrt(r.cwiseAbs2().sum());
  rho *= rho;
  if (rho < 1e-15) {
    relres_ = 0;
    iter_ = 0;
    if (log_)
      Logger::GetInstance() << (boost::lexical_cast<std::string>(iter_) + " " +
                                boost::lexical_cast<std::string>(relres_));
    return true;
  }

  array_t p(x.rows(), x.cols());
  array_t Ap(x.rows(), x.cols());

  p = r;
  for (iter_ = 1; iter_ <= maxit; ++iter_) {
    A.apply(Ap, p);
    // std::cout << "norm Ap: " << Ap.norm() << "\n";
    numeric_t alpha = rho / (p.conjugate() * Ap).sum();
    x += alpha * p;
    r -= alpha * Ap;
    double old_rho = rho;
    double norm_r = std::sqrt(r.cwiseAbs2().sum());
    double norm_x = std::sqrt(x.derived().cwiseAbs2().sum());
    //    std::cout << "norm_x: " << norm_x << "\n";
    // std::cout << "norm_r: " << norm_r << "\n";
    relres_ = norm_r / norm_x;
    if (log_ && log_hist_)
      Logger::GetInstance() << (boost::lexical_cast<std::string>(iter_) + " " +
                                boost::lexical_cast<std::string>(relres_));
    if (relres_ <= reltol) {
      if (log_)
        Logger::GetInstance() << (boost::lexical_cast<std::string>(iter_) + " " +
                                  boost::lexical_cast<std::string>(relres_));
      return true;
    }

    rho = norm_r * norm_r;
    double beta = rho / old_rho;
    p = beta * p + r;
  }
  return false;
}

void
CG::set_log_hist(bool t)
{
  if (t == true) log_ = true;
  log_hist_ = t;
}


#include <svgpp/parser/list_of_points.hpp>

#include <gtest/gtest.h>
#include <boost/assign/list_of.hpp>
#include <boost/tuple/tuple_io.hpp>

namespace
{

typedef std::vector<std::pair<double, double> > pair_list_t;

struct Context
{
  template<class Range>
  void set(svgpp::tag::attribute::points, Range const & r)
  {
    for(typename boost::range_const_iterator<Range>::type it = boost::begin(r); it != boost::end(r); ++it)
      values_.push_back(*it);
  }

  pair_list_t values_;
};

typedef std::pair<const char *, pair_list_t> valid_case_t;

using namespace boost::assign;

valid_case_t valid_tests[] = {
  valid_case_t("  ", pair_list_t() ),
  valid_case_t("1 2", list_of(std::make_pair(1.0, 2.0)).convert_to_container<pair_list_t>() ),
  valid_case_t("12.3 14.7,34.,176 77-67.3   \n2 ,  \n1 ,-3-4",
    list_of(std::make_pair(12.3, 14.7))
    (std::make_pair(34, 176))
    (std::make_pair(77, -67.3))
    (std::make_pair(2, 1))
    (std::make_pair(-3, -4)).convert_to_container<pair_list_t>()
    )
};

class list_of_points_valid : public ::testing::TestWithParam<valid_case_t> {
};

char const * const invalid_tests[] = {
  ",",
  "12 14-77 67",
  "12.3 14.7,",
  ",12.3 14.7",
  "12.3,,14.7"
};

class list_of_points_invalid : public ::testing::TestWithParam<const char *>
{};

}

TEST_P(list_of_points_valid, valid)
{
  Context context;
  EXPECT_TRUE(svgpp::value_parser<svgpp::tag::attribute::points>::parse(
    svgpp::tag::attribute::points(),
    context,
    std::string(GetParam().first),
    svgpp::tag::source::attribute()
  ));
  EXPECT_EQ(context.values_, GetParam().second);
}

INSTANTIATE_TEST_CASE_P(value_parser,
                        list_of_points_valid,
                        ::testing::ValuesIn(valid_tests));

class InvalidURange : public ::testing::TestWithParam<const char*> {
};

TEST_P(list_of_points_invalid, invalid)
{
  Context context;
  EXPECT_THROW(svgpp::value_parser<svgpp::tag::attribute::points>::parse(
    svgpp::tag::attribute::points(),
    context,
    std::string(GetParam()),
    svgpp::tag::source::attribute()
  ), std::exception);
}

INSTANTIATE_TEST_CASE_P(value_parser,
                        list_of_points_invalid,
                        ::testing::ValuesIn(invalid_tests));


/*
 *  graph_type.hpp
 *
 *
 *  Created by Andrea Bedini on 24/Nov/2011.
 *  Copyright (c) 2011-2014, Andrea Bedini <andrea.bedini@gmail.com>.
 *
 *  Distributed under the terms of the Modified BSD License.
 *  The full license is in the file COPYING, distributed as part of
 *  this software.
 *
 */

#ifndef GRAPH_TYPE_HPP
#define GRAPH_TYPE_HPP

#include <boost/graph/adjacency_list.hpp>

typedef boost::adjacency_list
  < boost::listS, boost::listS, boost::undirectedS
  , boost::property<boost::vertex_index_t, unsigned int>
  , boost::property<boost::edge_index_t, unsigned int>
  > graph_type;

#endif


/*
 [auto_generated]
 boost/numeric/odeint/integrate/integrate_n_steps.hpp

 [begin_description]
 Integration of n steps with constant time size. Adaptive and dense-output methods are fully supported.
 [end_description]

 Copyright 2009-2011 Karsten Ahnert
 Copyright 2009-2011 Mario Mulansky

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef OMPLEXT_BOOST_NUMERIC_ODEINT_INTEGRATE_INTEGRATE_N_STEPS_HPP_INCLUDED
#define OMPLEXT_BOOST_NUMERIC_ODEINT_INTEGRATE_INTEGRATE_N_STEPS_HPP_INCLUDED

#include <boost/type_traits/is_same.hpp>

#include <omplext_odeint/boost/numeric/odeint/stepper/stepper_categories.hpp>
#include <omplext_odeint/boost/numeric/odeint/integrate/null_observer.hpp>
#include <omplext_odeint/boost/numeric/odeint/integrate/detail/integrate_n_steps.hpp>

namespace boost {
namespace numeric {
namespace omplext_odeint {

/*
 * Integrates n steps
 *
 * the two overloads are needed in order to solve the forwarding problem
 */
template< class Stepper , class System , class State , class Time , class Observer>
Time integrate_n_steps(
        Stepper stepper , System system , State &start_state ,
        Time start_time , Time dt , size_t num_of_steps ,
        Observer observer )
{

    return detail::integrate_n_steps(
                stepper , system , start_state ,
                start_time , dt , num_of_steps ,
                observer , typename Stepper::stepper_category() );
}

/**
 * \brief Solves the forwarding problem, can be called with Boost.Range as start_state.
 */
template< class Stepper , class System , class State , class Time , class Observer >
Time integrate_n_steps(
        Stepper stepper , System system , const State &start_state ,
        Time start_time , Time dt , size_t num_of_steps ,
        Observer observer )
{
    return detail::integrate_n_steps(
                 stepper , system , start_state ,
                 start_time , dt , num_of_steps ,
                 observer , typename Stepper::stepper_category() );
}

/**
 * \brief The same function as above, but without observer calls.
 */
template< class Stepper , class System , class State , class Time >
Time integrate_n_steps(
        Stepper stepper , System system , State &start_state ,
        Time start_time , Time dt , size_t num_of_steps )
{
    return integrate_n_steps( stepper , system , start_state , start_time , dt , num_of_steps , null_observer() );
}

/**
 * \brief Solves the forwarding problem, can be called with Boost.Range as start_state.
 */
template< class Stepper , class System , class State , class Time >
Time integrate_n_steps(
        Stepper stepper , System system , const State &start_state ,
        Time start_time , Time dt , size_t num_of_steps )
{
    return integrate_n_steps( stepper , system , start_state , start_time , dt , num_of_steps , null_observer() );
}

/************* DOXYGEN *************/
    /**
     * \fn Time integrate_n_steps( Stepper stepper , System system , State &start_state , Time start_time , Time dt , size_t num_of_steps , Observer observer )
     * \brief Integrates the ODE with constant step size.
     *
     * This function is similar to integrate_const. The observer is called at
     * equidistant time intervals t0 + n*dt.
     * If the Stepper is a normal stepper without step size control, dt is also
     * used for the numerical scheme. If a ControlledStepper is provided, the
     * algorithm might reduce the step size to meet the error bounds, but it is
     * ensured that the observer is always called at equidistant time points
     * t0 + n*dt. If a DenseOutputStepper is used, the step size also may vary
     * and the dense output is used to call the observer at equidistant time
     * points. The final integration time is always t0 + num_of_steps*dt.
     *
     * \param stepper The stepper to be used for numerical integration.
     * \param system Function/Functor defining the rhs of the ODE.
     * \param start_state The initial condition x0.
     * \param start_time The initial time t0.
     * \param dt The time step between observer calls, _not_ necessarily the
     * time step of the integration.
     * \param num_of_steps Number of steps to be performed
     * \param observer Function/Functor called at equidistant time intervals.
     * \return The number of steps performed.
     */

} // namespace omplext_odeint
} // namespace numeric
} // namespace boost

#endif // BOOST_NUMERIC_ODEINT_INTEGRATE_INTEGRATE_N_STEPS_HPP_INCLUDED


#include <iostream>
#include <map>
#include <cstring>
#include <fstream>
#include <queue>
#include <boost/algorithm/string.hpp>
#include <list>
#include <algorithm>
using namespace std;

bool check_palindrome(std::string s)
{
    std::size_t i = 0;
    std::size_t j = s.length() - 1;
    while (i < j)
    {
        if (s[i++] != s[j--])
        {
            return false;
        }
    }

    return true;
}

std::list<string> GetPalindrome()
{
	string filename;
	cout << "What file do you want to use for input? ";
	getline(cin, filename);
	ifstream fin;
	fin.open(filename.c_str());
	string str;
	char * token;

	std::list<string>List_Palindrome;

	while (fin >> str)
	{
		char *fileName = (char*)str.c_str();
		token = strtok (fileName," ,.-!;:\|'}{][)(");
		while (token != NULL)
		{
			boost::algorithm::to_lower(token);
			//mymap[token]++;
			if(check_palindrome(token))
			{
				List_Palindrome.push_back(str);
			}

			token = strtok (NULL, " ,.-!;:\|'}{][)(");

		}
	}
	fin.close();
	return List_Palindrome;
}

void print (std::list<string> myMap)
{
	for (std::list<string>::iterator it=myMap.begin(); it!=myMap.end(); ++it)
	{
		std::cout << *it<< endl;
	}
}

int print_freq_sorted(std::map<std::string, int> myMap)
{
	int total=0;

	std::priority_queue<std::pair<int, std::string> > pq;
	for (std::map<string,int>::iterator it=myMap.begin(); it!=myMap.end(); ++it)
	{
		pq.push(std::make_pair(it->second, it->first)); ///push into heap
		total=total+it->second;
	}
	int size=myMap.size();
	for(int i=1; i<=size;i++)
	{
		std::cout << pq.top().first << ", " << pq.top().second << std::endl;
		pq.pop();
	}
	cout<<"Total Number of words"<<":"<<total<<endl;
	return total;
}

void print_freq(std::map<std::string,int> mymap)
{

	for (std::map<std::string,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
		std::cout << it->first << " => " << it->second << '\n';

}

std::map<string, int> GetFreq(std::list<string>list)
{
	std::map<std::string, int> Frequency;
	for(std::list<string>::iterator it=list.begin();it!=list.end();++it)
		Frequency[*it]++;
	return Frequency;
}

int main()
{
	std::list<string>Palindromes=GetPalindrome(); /// get the palindromes
	print(Palindromes);				/// print the palindromes
	std::map<string, int> Frequency=GetFreq(Palindromes); //// get the frequency of the palindromes
	print_freq(Frequency);
	print_freq_sorted(Frequency);  ///print the frequency in sorted order

}
// It has a linear time complexity.
// The palindrome checker is O(m/2)~O(m) where m is the size of each string.
// Then I used a hashmap to get the frequency. And then used a priority queue to sort them and print in order
// Thus, worst case is O(n).