#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <set>
#include <iomanip>
#include <boost/program_options.hpp>
#include "string.hpp"
using namespace std;
using namespace std::filesystem;
using namespace boost::program_options;

typedef array<double, 3> vector3;

struct atom
{
	bool valid;
	string name;
	vector3 coord;
};

typedef function<void(const atom&)> callback;

struct atom_iterator
{
	virtual void atoms(istream& istream, callback onatom) = 0;
};

struct measurer
{
	size_t atom_num = 0;
	vector3 lower_bounds{}, upper_bounds{};
	vector3 box_center{}, box_size{};
	vector3 geometric_center{};
	map<string, size_t> elem_num{};

	void measure(istream& istream, atom_iterator& it)
	{
		atom_num = 0;
		lower_bounds.fill(numeric_limits<double>::infinity());
		upper_bounds.fill(-numeric_limits<double>::infinity());
		box_center = {};
		geometric_center = {};
		box_size = {};
		elem_num.clear();

		it.atoms(istream, [&](const atom& a)
			{
				++atom_num;

				if (a.valid)
				{
					for (size_t i = 0; i < 3; ++i)
					{
						lower_bounds[i] = min(lower_bounds[i], a.coord[i]);
						upper_bounds[i] = max(upper_bounds[i], a.coord[i]);
						geometric_center[i] += a.coord[i];
					}

					++elem_num[a.name];
				}
			});

		for (size_t i = 0; i < 3; ++i)
		{
			box_center[i] = (upper_bounds[i] + lower_bounds[i]) / 2;
			box_size[i] = upper_bounds[i] - lower_bounds[i];
			geometric_center[i] /= atom_num;
		}
	}
};

struct pdb_atom_iterator : atom_iterator
{
	void atoms(istream& istream, callback onatom)
	{
		set<string> whitelist = { "ATOM", "HETATM" };

		for (string line; safe_getline(istream, line);)
		{
			if (line.size() < 78)
				continue;

			auto sig = trim_end(line.substr(0, 6));
			if (!whitelist.count(sig))
				continue;

			onatom(atom
				{
					true,
					trim_start(line.substr(76, 2)),
					{ stod(line.substr(30, 8)), stod(line.substr(38, 8)), stod(line.substr(46, 8)) },
				});
		}
	}
};

struct mol2_atom_iterator : atom_iterator
{
	void atoms(istream& istream, callback onatom)
	{
		bool inatoms = false;
		for (string line; safe_getline(istream, line);)
		{
			line = trim(line);
			if (line.length() == 0)
				continue;

			if (inatoms)
			{
				if (line[0] == '@')
					break;
			}
			else
			{
				if (line == "@<TRIPOS>ATOM")
					inatoms = true;
				continue;
			}

			auto fields = split(line, string(" \t\v\f"));
			if (fields.size() < 5)
			{
				// error
				onatom(atom{ false });
				continue;
			}

			onatom(atom
				{
					true,
					fields[1],
					{ stod(fields[2]), stod(fields[3]), stod(fields[4]) },
				});
		}
	}
};

int main(int argc, char* argv[])
{
	static double default_magnification = 2.0;
	static string default_input_format = "pdb";
	set<string> supported_formats = { "pdb", "pdbqt", "mol2" };

	try
	{
		bool all_dimensions, no_ligand_argument, use_centroid, conf_lines;
		path input_path;
		double magnification, lb_size, ub_size;
		string input_fmt;

		options_description options("Options");
		options.add_options()
			("input-file,i", value<path>(&input_path), (join("/", supported_formats.begin(), supported_formats.end()) + " file to be measured, without this argument user will be prompted to input lines").c_str())
			("input-format,f", value<string>(&input_fmt), ("supported input formats: " + join(", ", supported_formats.begin(), supported_formats.end())).c_str())
			("all,a", bool_switch(&all_dimensions), "output detailed dimensional information instead of the form of AutoDock Vina and idock/jdock arguments which is the default")
			("magnification,m", value<double>(&magnification)->default_value(default_magnification), "magnification ratio on the measured size (ignored if --all specified)")
			("lower-bound,l", value<double>(&lb_size), "limit the lower bound for the --size_* arguments in all three dimensions for Vina (ignored if --all specified)")
			("upper-bound,u", value<double>(&ub_size), "limit the upper bound for the --size_* arguments in all three dimensions for Vina (ignored if --all specified)")
			("conf-lines,C", bool_switch(&conf_lines), "output arguments in the form of conf lines instead of one-liner arguments for Vina (ignored if --all specified)")
			("no-ligand-argument,L", bool_switch(&no_ligand_argument), "do not emit --ligand argument for Vina (ignored if --all specified)")
			("centroid,c", bool_switch(&use_centroid), "use centroid (i.e. geometric center, center of mass, or center of gravity) instead of bounding box center when generating --center_* arguments for Vina (ignored if --all specified)")
			("help", "this help information")
			("version", "version information");

		positional_options_description positional;
		positional.add("input-file", 1);

		variables_map vm;
		store(command_line_parser(argc, argv).options(options).positional(positional).run(), vm);
		notify(vm);

		if (vm.count("help")) // program input
		{
			cout << "Usage: " << argv[0] << " [<input-file>] [options]" << endl;
			cout << join("/", supported_formats.begin(), supported_formats.end()) + " measuring tool by Maozi Chen" << endl;
			cout << endl;
			cout << options;
			return 0;
		}

		if (vm.count("version"))
		{
			cout << "1.1.0 (2021-09-30)" << endl;
			return 0;
		}

		vector<string> lines;

		if (!vm.count("input-format") && vm.count("input-file"))
		{
			auto ext = input_path.extension().string();
			if (ext.size())
				ext = ext.substr(1);
			input_fmt = ext;
		}

		if (input_fmt.empty())
			input_fmt = default_input_format;

		if (!supported_formats.count(input_fmt))
		{
			cerr << "ERROR: unknown format '" << input_fmt << "', supported formats are " << join(", ", supported_formats.begin(), supported_formats.end()) << endl;
			return 1;
		}

		unique_ptr<atom_iterator> pit;
		if (starts_with(input_fmt, "pdb"))
			pit = make_unique<pdb_atom_iterator>();
		else
			pit = make_unique<mol2_atom_iterator>();

		measurer m;
		if (!vm.count("input-file"))
		{
			m.measure(cin, *pit);
		}
		else
		{
			ifstream file(input_path);
			m.measure(file, *pit);
		}

		if (all_dimensions)
		{
			if (vm.count("input-file"))
				cout << "file: " << input_path << endl;
			if (m.atom_num)
			{
				cout.precision(3);
				cout.setf(ios::fixed);

				cout << "atoms: " << m.atom_num << endl;
				for (auto [elem, num] : m.elem_num)
					cout << "  " << elem << " atoms: " << num << endl;
				cout << "x bounds: [" << m.lower_bounds[0] << ", " << m.upper_bounds[0] << "]" << endl;
				cout << "y bounds: [" << m.lower_bounds[1] << ", " << m.upper_bounds[1] << "]" << endl;
				cout << "z bounds: [" << m.lower_bounds[2] << ", " << m.upper_bounds[2] << "]" << endl;
				cout << "bounding box center: (" << m.box_center[0] << ", " << m.box_center[1] << ", " << m.box_center[2] << ")" << endl;
				cout << "bounding box size: (" << m.box_size[0] << ", " << m.box_size[1] << ", " << m.box_size[2] << ")" << endl;
				cout << "geometric center: (" << m.geometric_center[0] << ", " << m.geometric_center[1] << ", " << m.geometric_center[2] << ")" << endl;
			}
			else
			{
				cout << "atoms: 0" << endl;
			}
		}
		else
		{
			if (m.atom_num)
			{
				auto [size_x, size_y, size_z] = m.box_size;
				auto [center_x, center_y, center_z] = (use_centroid ? m.geometric_center : m.box_center);

				size_x *= magnification;
				size_y *= magnification;
				size_z *= magnification;

				if (vm.count("lower-bound"))
				{
					size_x = max(size_x, lb_size);
					size_y = max(size_y, lb_size);
					size_z = max(size_z, lb_size);
				}
				if (vm.count("upper-bound"))
				{
					size_x = min(size_x, ub_size);
					size_y = min(size_y, ub_size);
					size_z = min(size_z, ub_size);
				}

				if (conf_lines)
				{
					if (!no_ligand_argument && vm.count("input-file"))
						cout << "ligand=" << input_path << endl;
					cout << "center_x=" << center_x << endl
						 << "center_y=" << center_y << endl
						 << "center_z=" << center_z << endl
						 << "size_x=" << size_x << endl
						 << "size_y=" << size_y << endl
						 << "size_z=" << size_z << endl;
				}
				else
				{
					if (!no_ligand_argument && vm.count("input-file"))
						cout << "--ligand=" << input_path << ' ';
					cout << "--center_x=" << center_x
						<< " --center_y=" << center_y
						<< " --center_z=" << center_z
						<< " --size_x=" << size_x
						<< " --size_y=" << size_y
						<< " --size_z=" << size_z
						<< endl;
				}
			}
			else
			{
				cerr << "ERROR: no atom found for Vina arguments";
				if (vm.count("input-file"))
					cout << " in file " << input_path;
				cout << endl;
				return 2;
			}
		}

		return 0;
	}
	catch (exception& ex)
	{
		cerr << "ERROR: " << ex.what() << endl;
		return 3;
	}
}


//=======================================================================
// Copyright (c)
//
// 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)
//=======================================================================
/**
  * @file thorup_2kminus1.hpp
  * @brief
  * @author Jakub Ocwieja
  * @version 1.0
  * @date 2014-04-28
  */

#ifndef PAAL_THORUP_2KMINUS1_HPP
#define PAAL_THORUP_2KMINUS1_HPP

#include "paal/utils/functors.hpp"
#include "paal/utils/irange.hpp"
#include "paal/utils/assign_updates.hpp"

#include <boost/range/as_array.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/dijkstra_shortest_paths_no_color_map.hpp>

#include <queue>
#include <unordered_map>
#include <limits>
#include <random>
#include <cmath>

namespace paal {

/**
* @brief 2k-1 approximate distance oracle
*
* @tparam Graph graph
* @tparam EdgeWeightMap edge weight map
* @tparam VertexIndexMap vertex index map
* @tparam Rand random engine
*/
template <  typename Graph,
            typename VertexIndexMap,
            typename EdgeWeightMap,
            typename Rand=std::default_random_engine
            >
class distance_oracle_thorup2kminus1approximation {
    using DT = typename boost::property_traits<EdgeWeightMap>::value_type;
    using VT = typename boost::graph_traits<Graph>::vertex_descriptor;

    //! List of pairs (vertex index, distance to vertex)
    using DVect = std::vector< std::pair<int, DT> >;

    //! Maps vertex in a bunch into a distance to it
    using BunchMap = std::unordered_map<int, DT>;

    //! Index map stored to access internal structures
    VertexIndexMap m_index;

    //! For each vertex v a maximal layer number for which v belongs
    /** A_0 = V
    *   A_{i+1} \subset A_i
    *   A_k = \emptyset
    */
    std::vector< int > m_layer_num;

    //! For each vertex v a list of vertices of consecutive layers closest to v
    std::vector< DVect > m_parent;

    //! For each vertex v a set of vertices w closer to v than any vertex in layer m_layer_num[w]+1
    std::vector< BunchMap > m_bunch;

    /**
    * @brief Fills m_layer_num
    *
    * @param g Graph
    * @param k Approximation parameter = maximal number of layers
    * @param p Probability of being chosen to a next layer
    * @param random_engine Random engine
    *
    * @return Number of nonempty layers
    */
    int choose_layers(const Graph& g, int k, long double p, Rand & random_engine) {
        std::uniform_real_distribution<> dist(0,1);

        int max_layer_num = 0;
        long double logp = log(p);

        for (int ind: irange(num_vertices(g))) {
            m_layer_num[ind] = std::min(k-1, (int)(log(dist(random_engine)) / logp));
            assign_max(max_layer_num, m_layer_num[ind]);
        }

        return max_layer_num+1;
    }

    /**
     * @brief A visitor implementation for compute_parents Dijkstra's algorithm call
     *
     * @tparam NearestMap
     * @tparam Tag
     */
    template <typename NearestMap, typename Tag>
    class nearest_recorder : boost::base_visitor< nearest_recorder<NearestMap, Tag> > {

        //! Stores distances to the closest vertex in a particular layer
        NearestMap m_nearest_map;

    public:
        using event_filter = Tag;

        //! Constructor
        explicit nearest_recorder(NearestMap const nearest_map) : m_nearest_map(nearest_map) {}

        //! Copies nearest value from precdessor
        template <typename Edge>
        void operator()(Edge const e, Graph const &g) const {
            auto nearest = get(m_nearest_map, source(e,g));
            put(m_nearest_map, target(e,g), nearest);
        }

    };

    //! Constructs a visitor for compute_parents Dijkstra's algorithm call
    template <typename NearestMap, typename Tag>
    nearest_recorder<NearestMap, Tag>
    make_nearest_recorder(NearestMap nearest_map, Tag) {
        return nearest_recorder<NearestMap, Tag>{nearest_map};
    }

    /**
    * @brief Fills m_parent for a single layer
    *
    * @param g Graph
    * @param layer_num Number of layer
    */
    void compute_parents(const Graph& g, EdgeWeightMap edge_weight, int layer_num) {
        std::vector<DT> distance(num_vertices(g), std::numeric_limits<DT>::max());
        std::vector<int> nearest(num_vertices(g), -1);
        std::vector<VT> roots;

        for (auto v: boost::as_array(vertices(g))) {
            int v_ind = m_index[v];
            if (m_layer_num[v_ind] >= layer_num) {
                nearest[v_ind] = v_ind;
                distance[v_ind] = DT{};
                roots.push_back(v);
            }
        }

        boost::dijkstra_shortest_paths_no_init(
                g,
                roots.begin(),
                roots.end(),
                boost::dummy_property_map(),
                make_iterator_property_map(distance.begin(), m_index, distance[0]),
                edge_weight,
                m_index,
                utils::less{},
                boost::closed_plus<DT>(),
                DT{},
                boost::make_dijkstra_visitor(make_nearest_recorder(
                        make_iterator_property_map(nearest.begin(), m_index, nearest[0]),
                        boost::on_edge_relaxed{})
                    )
            );

        for (int ind: irange(num_vertices(g))) {
            m_parent[ind].push_back(std::make_pair(nearest[ind], distance[ind]));
        }
    }

     //! A distance type crafted to control logic of compute_cluster Dijkstra's algorithm call
    class cluster_dist {
        //! An actual distance
        DT m_value;
        //! Marks unmodified vertices
        bool m_unmodified;

        //! Private constructor
        cluster_dist(DT value, bool unmodified) :
            m_value(value), m_unmodified(unmodified) {}

    public:
        //! Public constructor
        cluster_dist(DT value = DT{}) :
            m_value(value), m_unmodified(false) {}

        //! Allows to create unmodified distances
        static cluster_dist
        make_limit(DT value = std::numeric_limits<DT>::max()) {
            return cluster_dist(value, true);
        }

        //! A comparator struct adjusted to recognize unmodified values
        /** Unmodified values are not smaller then any modified value. To recognize an umodified value we compare it
         *  with a maximal modified value of DT
         */
        struct less {
            //! A comparison operator
            bool operator()(cluster_dist a, cluster_dist b) const {
                return (a.m_value < b.m_value) && (b.m_unmodified || !a.m_unmodified);
            }
        };

        //! Plus operation struct
        struct plus {
            //! Sum operator
            cluster_dist operator()(cluster_dist a, cluster_dist b) const {
                return cluster_dist(a.m_value + b.m_value, a.m_unmodified || b.m_unmodified);
            }
        };

        //! An accessor to the distance
        const DT value() const {
            return m_value;
        }
    };

    /**
     * @brief A property_map with lazy initialization of distances
     *
     * For each vertex of a graph a distance is initialized with an upper limit on a value which causes edge
     * relaxation in compute_cluster Dijkstra's algorithm.
     */
    class cluster_distance_wrapper :
        public boost::put_get_helper<cluster_dist&, cluster_distance_wrapper>
    {
        //! A wrapped distance table
        std::vector< cluster_dist > *m_distance;

        //! An index map stored to access table structures
        VertexIndexMap m_index;

        //! A pointer to a parent table containing initial values of fields of m_distance
        /** The values stored here are copied into m_distance table when m_distance fields are accessed for the
         *  first time. For each vertex of a graph it contains an upper limit on a value which causes edge relaxation
         *  in compute_cluster Dijkstra's algorithm.
         */
        std::vector< DT > *m_limit;

        //! A table storing last access time to m_distance fields
        std::vector<int> *m_last_accessed;

        //! A value necessary to interpret m_last_accessed
        /** The value is initially different than any value in m_last_accessed.
         *  However, it is not necessarily bigger.
         */
        int m_now;

    public:
        typedef VT key_type;
        typedef cluster_dist value_type;
        typedef value_type& reference;
        typedef boost::lvalue_property_map_tag category;

        /**
         * @brief Constructor
         *
         * @param distance A helper vector - required to have num_vertices(g) fields
         * @param index An index map
         * @param limit Compute_cluster Dijkstra's algorithm relaxation limits
         * @param last_accessed A helper vector - required to have num_vertices(g) fields
         * @param now Required to be different than any last_accessed value
         */
        cluster_distance_wrapper(
                std::vector< cluster_dist > *distance,
                VertexIndexMap index,
                std::vector< DT > *limit,
                std::vector< int > *last_accessed,
                int now) :
            m_distance(distance),
            m_index(index),
            m_limit(limit),
            m_last_accessed(last_accessed),
            m_now(now) {}

        /**
         * @brief Map values accessor
         *
         * @param key Key
         *
         * @return Value
         */
        reference operator[](const key_type& key) const {
            int k_ind = m_index[key];
            if ((*m_last_accessed)[k_ind] != m_now) {
                (*m_last_accessed)[k_ind] = m_now;
                (*m_distance)[k_ind] = cluster_dist::make_limit((*m_limit)[k_ind]);
            }
            return (*m_distance)[k_ind];
        }
    };

    /**
     * @brief A visitor implementation for a compute_cluster Dijkstra's algorithm call
     *
     * @tparam DistanceMap
     * @tparam Tag
     */
    template <typename DistanceMap, typename Tag>
    class cluster_recorder : boost::base_visitor< cluster_recorder<DistanceMap, Tag> > {
        //! Vertex whose cluster is recorded
        int m_w_ind;

        //! A pointer to m_bunch field of the oracle
        std::vector< BunchMap >* m_bunch;

        //! Index map stored to access internal structures
        VertexIndexMap m_index;

        //! A distance map
        DistanceMap m_distance;

    public:
        using event_filter = Tag;

        explicit cluster_recorder(int w_ind, std::vector<BunchMap> *bunch,
                VertexIndexMap index, DistanceMap distance) :
            m_w_ind(w_ind), m_bunch(bunch), m_index(index), m_distance(distance) {}

        template <typename Vertex>
        void operator()(Vertex const v, Graph const &g) const {
            (*m_bunch)[m_index[v]].insert(std::make_pair(m_w_ind, m_distance[v].value()));
        }
    };

    /**
     * @brief
     *
     * @tparam DistanceMap
     * @tparam Tag
     * @param cluster
     * @param index
     * @param distance
     * @param Tag
     *
     * @return A visitor for a compute_cluster Dijkstra's algorithm call
     */
    template <typename DistanceMap, typename Tag>
    cluster_recorder<DistanceMap, Tag>
    make_cluster_recorder(int w_ind, std::vector<BunchMap> *bunch, VertexIndexMap index,
            DistanceMap distance, Tag) {
        return cluster_recorder<DistanceMap, Tag>{w_ind, bunch, index, distance};
    };

    /**
     * @brief Fills bunchs with vertices inside a cluster - a set of vertices
     *        which contains w in its bunch
     *
     * @param g Graph
     * @param edge_weight Edge weights
     * @param w Vertex
     * @param k Number of layers
     * @param limit Dijkstra's algorithm relaxation limits for each layer
     * @param distance A helper vector - required to have num_vertices(g) fields
     * @param last_accessed A helper vector - require to be initialized with negative values
     */
    void compute_cluster(const Graph& g, EdgeWeightMap edge_weight, VT w, int k,
            std::vector< std::vector<DT> > &limit,
            std::vector<cluster_dist> &distance, std::vector<int> &last_accessed) {
        DVect cluster;
        int w_ind = m_index[w];
        int w_layer_num = m_layer_num[w_ind];

        cluster_distance_wrapper distance_wrapper(
                &distance, m_index, &limit[w_layer_num + 1],
                &last_accessed, w_ind);
        distance_wrapper[w] = cluster_dist(DT{});

        boost::dijkstra_shortest_paths_no_color_map_no_init(
                g,
                w,
                boost::dummy_property_map(),
                distance_wrapper,
                edge_weight,
                m_index,
                typename cluster_dist::less(),
                typename cluster_dist::plus(),
                cluster_dist(std::numeric_limits<DT>::max()),
                cluster_dist(DT{}),
                boost::make_dijkstra_visitor(make_cluster_recorder(
                        w_ind, &m_bunch, m_index, distance_wrapper,
                        boost::on_examine_vertex{})
                    )
            );
    }

    /**
    * @brief Fills m_bunch
    *
    * @param g Graph
    * @param edge_weight Edge weight
    * @param k Number of layers
    */
    void compute_bunchs(const Graph& g, EdgeWeightMap edge_weight, int k) {
        //! Initialization of reusable structures
        std::vector< std::vector<DT> > limit(k+1,
                std::vector<DT>(num_vertices(g), std::numeric_limits<DT>::max()));
        for (int l: irange(k)) {
            for (int i: irange(num_vertices(g))) {
                limit[l][i] = m_parent[i][l].second;
            }
        }
        std::vector<cluster_dist> distance(num_vertices(g));
        std::vector<int> last_accessed(num_vertices(g), -1);

        for (auto v: boost::as_array(vertices(g))) {
            compute_cluster(g, edge_weight, v, k, limit, distance, last_accessed);
        }
    }

public:

    /**
    * @brief Constructor
    *
    * @param g graph
    * @param index vertex index map
    * @param edge_weight edge weight map
    * @param k approximation parameter
    * @param random_engine random engine
    */
    distance_oracle_thorup2kminus1approximation(const Graph &g,
            VertexIndexMap index,
            EdgeWeightMap edge_weight,
            int k,
            Rand && random_engine = Rand(5426u)) :
        m_index(index),
        m_layer_num(num_vertices(g)),
        m_parent(num_vertices(g)),
        m_bunch(num_vertices(g))
            {
        long double p = powl(num_vertices(g), -1./k);
        k = choose_layers(g, k, p, random_engine);
        for (int layer_num: irange(k)) {
            compute_parents(g, edge_weight, layer_num);
        }
        compute_bunchs(g, edge_weight, k);
    }

    //! Returns an 2k-1 approximate distance between two vertices in O(k) time
    /** Returns a distance of path going through one of parents of u or v */
    DT operator()(VT u, VT v) const {
        int u_ind = m_index[u], v_ind = m_index[v];
        typename std::unordered_map<int, DT>::const_iterator it;
        int l = 0;
        std::pair<int, DT> middle_vertex = m_parent[u_ind][l];
        while ((it = m_bunch[v_ind].find(middle_vertex.first)) == m_bunch[v_ind].end()) {
            ++l;
            middle_vertex = m_parent[v_ind][l];
            std::swap(u_ind, v_ind);
        }
        //! Returns d(v, middle) + d(middle, u)
        return it->second + middle_vertex.second;
    }
};

/**
* @brief
*
* @tparam Graph
* @tparam EdgeWeightMap
* @tparam VertexIndexMap
* @tparam Rand
* @param g - given graph
* @param k - approximation parameter
* @param index - graph index map
* @param edge_weight - graph edge weight map
* @param random_engine - random engine
*
* @return 2k-1 approximate distance oracle
*/
template <  typename Graph,
            typename EdgeWeightMap,
            typename VertexIndexMap,
            typename Rand=std::default_random_engine
         >
distance_oracle_thorup2kminus1approximation<Graph, VertexIndexMap, EdgeWeightMap, Rand>
make_distance_oracle_thorup2kminus1approximation(
        const Graph &g,
        const int k,
        VertexIndexMap index,
        EdgeWeightMap edge_weight,
        Rand && random_engine = Rand(5426u)) {
    return distance_oracle_thorup2kminus1approximation<Graph,
    VertexIndexMap,
    EdgeWeightMap,
    Rand>(g, index, edge_weight, k, std::move(random_engine));
}

/**
* @brief
*
* @tparam Graph
* @tparam P
* @tparam T
* @tparam R
* @tparam Rand
* @param g - given graph
* @param k - approximation parameter
* @param params - named parameters
* @param random_engine - random engine
*
* @return 2k-1 approximate distance oracle
*/
template <  typename Graph,
            typename P = char,
            typename T = boost::detail::unused_tag_type,
            typename R = boost::no_property,
            typename Rand=std::default_random_engine
         >
auto
make_distance_oracle_thorup2kminus1approximation(
        const Graph &g,
        const int k,
        const boost::bgl_named_params<P, T, R>& params = boost::no_named_parameters(),
        Rand && random_engine = Rand(5426u))
    -> distance_oracle_thorup2kminus1approximation<Graph,
    decltype(choose_const_pmap(get_param(params, boost::vertex_index), g, boost::vertex_index)),
    decltype(choose_const_pmap(get_param(params, boost::edge_weight), g, boost::edge_weight)),
    Rand> {
    return make_distance_oracle_thorup2kminus1approximation(g,
            k,
            choose_const_pmap(get_param(params, boost::vertex_index), g, boost::vertex_index),
            choose_const_pmap(get_param(params, boost::edge_weight), g, boost::edge_weight),
            std::move(random_engine));
}

} //paal

#endif // PAAL_THORUP_2KMINUS1_HPP


#pragma once

#include <boost/hana.hpp>

#include <vector>
#include <utility>
#include <array>

namespace eumorphic
{
	namespace detail
	{
		namespace hana = boost::hana;

		template <typename Iterable, typename T>
		constexpr auto index_of(Iterable const& iterable, T const& element) {
			constexpr auto size = decltype(hana::size(iterable)){ };
			constexpr auto dropped = decltype(hana::size(
				hana::drop_while(iterable, hana::not_equal.to(element))
			)){ };
			return size - dropped;
		}
	} //namespace detail

	template <template <typename...> class Container, class ...Types>
	struct collection
	{
		static constexpr auto types               = boost::hana::tuple_t<Types...>;
		static constexpr auto segment_container_template = boost::hana::template_<Container>;
		static constexpr auto segments_container_t = boost::hana::transform(types, segment_container_template);
		using  types_collection            = typename decltype(boost::hana::unpack(segments_container_t, boost::hana::template_<boost::hana::tuple>))::type;

		types_collection data_;

		template <class T>
		auto& get_segment()
		{
			using namespace detail;
			constexpr auto  type_index = index_of(segments_container_t, hana::type_c< Container<T> >);
			return hana::at(data_, type_index);
		}

		template <class T>
		constexpr void insert( T&& value )
		{
			using Td = std::decay_t<T>;
			get_segment<Td>().push_back(std::forward<T>(value));
		}

		std::size_t size()
		{
			using namespace detail;
			std::size_t s = 0;
			hana::for_each( data_, [&s](auto& vec) { s += vec.size(); } );
			return s;
		}

	};

	template <class Container, class F, class ...Types>
	void for_each(Container&& col, std::size_t start, F&& f)
	{
		using namespace detail;
		std::size_t last_index = 0;
		hana::for_each(col.data_, [&f, &last_index, &start](auto& vec)
			{

				if (last_index + vec.size() > start)
				{
					auto i0 = (last_index < start) ? start - last_index : 0;

					for (auto i = i0; i != vec.size(); i++)
					{
						f(vec[i]);
					}
				}
				last_index += vec.size();
			});
	}

	template <class Container, class F, class ...Types>
	void for_each(Container&& col, F&& f)
	{
		using namespace detail;
		hana::for_each(col.data_, [&f](auto& vec)
			{
				for (auto& elem : vec) { f(elem); }
			});
	}

	// Not working atm
	//template <class Container, class F, class ...Types>
	//void for_each_if_valid(Container&& col, F&& f)
	//{
	//		using namespace detail;
	//		hana::for_each(col.data_, [&f](auto& vec)
	//			{
	//				auto maybe_apply_f_on = hana::sfinae([&f](auto&& p)
	//					-> decltype(f(p), bool()){ // It fails here. Does it mean that sfinae is not trasitive?
	//						f(p);
	//						return true;
	//					});
	//				for (auto& element : vec) { maybe_apply_f_on( element ); }
	//			});
	// }

	// Made the constructor do the work, because I couldn't make a function that properly deduces the types
	// TODO: Investigate if we can do this with a function
	template <class ...SubTypes>
	struct subset_for_each
	{
		template <class Collection, class F>
		subset_for_each(Collection&& col, F&& f)
		{
			static  constexpr auto  subset_types = boost::hana::tuple_t<SubTypes...>;
			using Collection_t = std::decay_t<Collection>;
                        [[maybe_unused]] static  constexpr auto  subset_segments_container_t = boost::hana::transform(subset_types, Collection_t::segment_container_template);

			using namespace detail;
			hana::for_each( col.data_, [&f](auto& vec)
			{
				using Container_t = std::decay_t<decltype(vec)>;

				constexpr auto  sub_type_index = index_of(subset_segments_container_t, hana::type_c< Container_t >);

				if constexpr (sub_type_index < sizeof...(SubTypes)) // Is there a better way to check if a type is in a tuple?
				{
					for (auto& elem : vec) { f(elem); }
				}
			});
		}
	};

	template <template <typename...> class Container, class ...Types>
	class ordered_collection : public collection< Container, Types...>
	{
		std::size_t next_index = 0;
		std::vector< std::size_t > types_order;
	public:

		using Base = collection< Container, Types...>;

		using Base::Base;
		template <class T>
		constexpr void insert(T&& value)
		{
                        using namespace detail;
						using Td = std::decay_t<T>;
                        constexpr auto  type_index = index_of(Base::segments_container_t, hana::type_c< Container<Td> >);

                        types_order.push_back( type_index );

                        hana::at(Base::data_, type_index).push_back(std::forward<T>(value));
		}

		// TODO: Facility to remove elements in ordered_collection

                template <template <typename...> class Container1, class F, class ...Types1>
                friend void for_each(ordered_collection<Container1, Types1...>& col, F&& f);
	};

	template <template <typename...> class Container, class F, class ...Types>
	void for_each( ordered_collection<Container, Types...>& col, F&& f)
	{
		// This needs some serious testing.
		using namespace detail;

		std::array< std::size_t, sizeof...(Types) > ti{}; // Per type indexing

		auto  iter = col.types_order.begin();
		while (iter != col.types_order.end())
		{
			hana::for_each(col.data_, [&f, &iter, &col, &ti](auto& vec)
				{
					using namespace detail;
					using Container_t = typename std::decay_t<decltype(vec)>;
					using Collection_t = std::decay_t<decltype(col)>;
					constexpr auto type_index = index_of(Collection_t::segments_container_t, hana::type_c< Container_t >);
					while ((*iter) == type_index && iter != col.types_order.end() )
					{
						f(vec [ ti[type_index]] );
						ti[type_index] += 1;
						iter++;
					}
				});
		}
	}

	// Minimally developed just as proof of concept
	template< class T, std::size_t MAX_SIZE >
	struct bounded_array : std::array<T, MAX_SIZE>
	{
		using Base = std::array<T, MAX_SIZE>;

		using Base::Base;

                const typename Base::size_type& size() const noexcept { return size_; }

		void push_back( const T& elem)
		{
			this->operator[](size_++) = elem;
		}

                typename Base::iterator begin() noexcept
		{
                        return typename Base::iterator(Base::data() );
		}

                typename Base::const_iterator begin() const noexcept
		{
                        return typename Base::const_iterator(Base::data() );
		}

                typename Base::iterator end() noexcept
		{
                        return typename Base::iterator(Base::data() + size_);
		}

                typename Base::const_iterator end() const noexcept
		{
                        return typename Base::const_iterator(Base::data() + size_);
		}

	private:
		std::size_t size_ = 0;
	};

} //namespace eumoprhic


#include <iostream>
#include <vector>

#include <algorithm>
#include <unordered_map>
#include <set>
#include <utility>
#include <stdexcept>
#include <cmath>
#include <chrono>
#include <exception>
#include <boost/filesystem.hpp>

#include "dynet/lstm.h"
#include "dynet/training.h"
#include "dynet/dynet.h"
#include "dynet/dict.h"

#include <boost/functional/hash.hpp>
#include <boost/program_options.hpp>
#include <boost/serialization/unordered_set.hpp>
#include <boost/serialization/priority_queue.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/set.hpp>

#include "lemon_inc.h"
#include "serialization.h"
#include "nn/tagger.h"
#include "nn/head_tagger.h"
#include "nn/biaffine_parser.h"
#include "utils.h"
#include "sgd.h"
#include "status.h"
#include "graph.h"
#include "decoder.h"

#include "dependency.h"
#include "spine_data.h"
#include "activation_function.h"
#include "spine_probs.h"

int main(int argc, char **argv)
{
    std::string test_path;
    std::string model_path;
    std::string output_path;

    std::string unused;

    namespace po = boost::program_options;
    po::options_description desc("Options");
    desc.add_options()
        ("test", po::value<std::string>(&test_path)->required(), "")
        ("model", po::value<std::string>(&model_path)->required(), "")
        ("output", po::value<std::string>(&output_path)->required(), "")
        ("dynet-mem", po::value<std::string>(&unused)->default_value(""), "")
    ;

    po::positional_options_description pod;
    pod.add("test", 1);
    pod.add("model", 1);
    pod.add("output", 1);

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).options(desc).positional(pod).run(), vm);
    po::notify(vm);

    dynet::initialize(argc, argv);

    SpineSettings spine_settings;
    read_object(model_path + ".spine_settings.param", spine_settings);

    std::cerr << "Reading test data..." << std::endl << std::flush;
    SpineData spine_test(spine_settings);
    std::vector<IntSentence> test_data;
    spine_test.read(test_path);
    spine_test.as_int_sentence([&](const IntSentence& s) { test_data.push_back(s); }, false);

    // Neural Network
    dynet::Model tagger_model;

    RNNSettings tagger_rnn_settings;
    read_object(model_path + ".tagger.rnn_settings", tagger_rnn_settings);

    NeuralTaggerSettings tagger_nn_settings;
    read_object(model_path + ".tagger.nn_settings", tagger_nn_settings);

    NeuralTagger<dynet::LSTMBuilder> tagger_nn(tagger_model, tagger_nn_settings, tagger_rnn_settings);
    read_object(model_path + ".tagger.param", tagger_model);

    dynet::Model head_tagger_model;

    RNNSettings head_tagger_rnn_settings;
    read_object(model_path + ".head_tagger.rnn_settings", head_tagger_rnn_settings);

    NeuralTaggerSettings head_tagger_nn_settings;
    read_object(model_path + ".head_tagger.nn_settings", head_tagger_nn_settings);

    NeuralTagger<dynet::LSTMBuilder> head_tagger_nn(head_tagger_model, head_tagger_nn_settings, head_tagger_rnn_settings);
    read_object(model_path + ".head_tagger.param", head_tagger_model);

    dynet::Model parser_model;

    RNNSettings parser_rnn_settings;
    read_object(model_path + ".parser.rnn_settings", parser_rnn_settings);

    NeuralBiaffineParserSettings nn_settings;
    read_object(model_path + ".parser.nn_settings", nn_settings);

    NeuralBiaffineParser<dynet::LSTMBuilder> parser_nn(parser_model, nn_settings, parser_rnn_settings);
    read_object(model_path + ".parser.param", parser_model);

    SpineProbs attachment_probs;
    read_object(model_path + ".attachment-probs", attachment_probs);

    std::vector<std::set<int>> allowed_spine(spine_settings.pos_dict.size());
    read_object(model_path + ".spine_filter", allowed_spine);

    for (IntSentence& sentence : test_data)
    {
            dynet::ComputationGraph cg;
            tagger_nn.compute(
                cg,
                sentence,
                [&] (unsigned index, dynet::expr::Expression& expr) -> void
                {
                    auto vec = dynet::as_vector(expr.value());
                    assert(vec.size() == spine_settings.tpl_dict.size());
                    int predicted = std::distance(std::begin(vec), std::max_element(std::begin(vec), std::end(vec)));
                    sentence[index+1].tpl = predicted;
				}
			);
    }

    for (IntSentence& sentence : test_data)
    {

        LDigraph lemon_graph;
        LArcMap lemon_weights(lemon_graph);

        std::vector<std::vector<float>> head_spine_weights;
        {
            for (unsigned i = 0 ; i <= sentence.size() ; ++i)
            {
                LNode node = lemon_graph.addNode();
                assert(lemon_graph.id(node) == (int) i);
            }

            {
                dynet::ComputationGraph cg;
                head_tagger_nn.compute(
                    cg,
                    sentence,
                    [&] (unsigned index, dynet::expr::Expression& expr) -> void
                    {
                        assert(index == head_spine_weights.size());
                        head_spine_weights.push_back(dynet::as_vector(expr.value()));
                    }
                );
            }
        }

        // Compute arc scores
        {
            dynet::ComputationGraph cg;
            parser_nn.compute(
                cg,
                sentence,
                [&] (const unsigned head, const unsigned modifier, const double score, dynet::expr::Expression& expr) -> void
                {
                    unused_parameter(expr);

                    double new_score = score;
                    int mod_tpl = sentence[modifier].tpl;

                    if (head == 0u)
                    {
                            auto f = attachment_probs.root.find(mod_tpl);

                            // skip if not candidate for dependency
                            if (f == std::end(attachment_probs.root))
                                return;

                            //new_score += att_weight * log(f->second);
                            new_score += head_spine_weights.at(modifier - 1).back();
                    }
                    else
                    {
                        int head_tpl = sentence[head].tpl;
                        auto f = attachment_probs.non_root.find(std::make_pair(head_tpl, mod_tpl));
                        // skip if not candidate for dependency
                        if (f == std::end(attachment_probs.non_root))
                            return;

                        new_score += head_spine_weights.at(modifier - 1).at(head_tpl);
                    }
                    LArc lemon_arc = lemon_graph.addArc(
                        lemon_graph.nodeFromId(head),
                        lemon_graph.nodeFromId(modifier)
                    );
                    lemon_weights[lemon_arc] = -new_score;
                }
            );

        }
        MSA msa(lemon_graph, lemon_weights);
        msa.run(lemon_graph.nodeFromId(0));

        for (unsigned modifier = 1 ; modifier <= sentence.size() ; ++modifier)
        {
            auto msa_pred = msa.pred(lemon_graph.nodeFromId(modifier));
            assert(msa_pred != lemon::INVALID);

            int predicted = lemon_graph.id(lemon_graph.source(msa_pred));
            sentence[modifier].head = predicted;
        }
    } // end for sentence

    // Output
    for (unsigned i = 0u ; i < test_data.size() ; ++i)
    {
        const IntSentence& int_sentence = test_data.at(i);
        SpineSentence& spine_sentence = spine_test.at(i);

        for (unsigned j = 0u ; j < int_sentence.size() ; ++ j)
        {
            const auto& int_token = int_sentence[j+1];
            auto& spine_token = spine_sentence.at(j);

            spine_token.head = std::to_string(int_token.head);
            spine_token.tpl = spine_settings.tpl_dict.convert(int_token.tpl);

            bool regular = false;
            unsigned position = true;
            if (int_token.head != 0)
            {
                regular = attachment_probs.attachments.at(std::make_pair(int_sentence[int_token.head].tpl, int_token.tpl)).first;
                position = attachment_probs.attachments.at(std::make_pair(int_sentence[int_token.head].tpl, int_token.tpl)).second;
            }
            spine_token.att_position = std::to_string(position);
            spine_token.att_type = (regular ? "r" : "s");
        }
    }

    std::ofstream f(output_path);
    f << spine_test;
    f.close();
}


#include "SPlisHSPlasH/Common.h"
#include <Eigen/Dense>
#include <iostream>
#include "Utilities/Timing.h"
#include "Utilities/FileSystem.h"
#include "extern/cxxopts/cxxopts.hpp"
#include "CompactNSearch.h"
#include "Utilities/StringTools.h"
#include "SPlisHSPlasH/SPHKernels.h"
#include "FoamKernel.h"
#include "Utilities/PartioReaderWriter.h"
#include "extern/partio/src/lib/Partio.h"
#include <array>
#include <future>
#include <random>

// Enable memory leak detection
#ifdef _DEBUG
#ifndef EIGEN_ALIGN
	#define new DEBUG_NEW
#endif
#endif

INIT_TIMING
INIT_LOGGING

using namespace SPH;
using namespace Eigen;
using namespace std;
using namespace Utilities;

void generateFoamFiles();
void queryValues(Real &vDiffAvgMax, Real & omegaDiffAvgMax, Real &curvatureAvgMax, Real &energyAvgMax);
void determineValues();
void generateFoam();
bool readCurrentFrame();
void writeCurrentFrame();
void writeCurrentFrame_bgeo();
void writeCurrentFrame_vtk();

bool readParticles(const std::string &fileName, std::vector<Vector3r> &positions, std::vector<Vector3r> &velocities, std::vector<Vector3r> &angularVelocities);
void computeNormals();
void computeDensities();
void generateFoamParticles(const unsigned int index, const unsigned int numParticles, const unsigned int numTrappedAir, const unsigned int numWaveCrest, const unsigned int numVorticity, const Real I_ke);
void getOrthogonalVectors(const Vector3r &vec, Vector3r &x, Vector3r &y);
Real clampAndNormalize(const Real val, const Real minVal, const Real maxVal);
void advectFoamParticles();
void removeParticles();

std::vector<Vector3r> x_copy;
std::vector<unsigned char> particleType;
enum ParticleType {Foam, Spray, Bubbles, NUM_PARTICLE_TYPES};
enum GeneratedType {TrappedAir, WaveCrest, Vorticity, NUM_GENERATOR_TYPES};
constexpr unsigned int maxNumTypes = (NUM_PARTICLE_TYPES > NUM_GENERATOR_TYPES) ? NUM_PARTICLE_TYPES : NUM_GENERATOR_TYPES;
unsigned int numTypes = 0;
std::array<unsigned int, maxNumTypes> numParticlesOfType;
std::array<std::string, maxNumTypes> nameExtensions;
std::array<std::string, maxNumTypes> extendedFileNames;
std::array<std::vector<Vector3r>, maxNumTypes> particleCopies;
std::vector<Real> v_diff, curvature, omega_diff, energy;
std::string fileName_copy;
std::future<void> handle;
std::array<std::future<void>, maxNumTypes> handlesType;
const Real density0 = 1000.0;
string input = "";
string output = "";
int output_format = 0; // 0: bgeo, 1: vtk
unsigned int startFrame = 1;
unsigned int endFrame = 0xffffffff;
unsigned int currentFrame = 1;
unsigned int skipframes = 0;
string exePath;
Real particleRadius = 0.025;
Real timeStepSize = 0.02;
Real invDt;
Real supportRadius;
Real mass = 1.0;
Real foam_scale = 1000.0;
Real taMin, taMax, wcMin, wcMax, keMin, keMax;
Real k_ta = 4000;
Real k_wc = 100000;
Real k_buoyancy = 2.0;
Real k_drag = 0.8;
Real lifetimeMin = 2.0;
Real lifetimeMax = 5.0;
Vector3r bbMin(Vector3r::Constant(-std::numeric_limits<Real>::max()));
Vector3r bbMax(Vector3r::Constant(std::numeric_limits<Real>::max()));
enum class BbType { Kill, Lifesteal, Clamp };
BbType bbType = BbType::Lifesteal;
CompactNSearch::NeighborhoodSearch *neighborhoodSearch;
std::vector<Vector3r> x0, v0, normals;
std::vector<Real> densities;
std::vector<Vector3r> fx, fv;
std::vector<Real> flifetime;
bool queryMode = false;
bool automaticMode = true;
bool splitTypes = false;
bool splitGenerators = false;
enum KernelType { CubicSpline = 0, Ihmsen2012, NumKernelTypes };
KernelType kernelType = KernelType::CubicSpline;
Real(*kernelFct)(const Vector3r &) = CubicKernel::W;

std::random_device rand_device;
std::mt19937_64 random_generator(rand_device());
std::uniform_real_distribution<Real> uniform_distr_0_1(Real(0.0), Real(1.0));
Real max_v_diff = 0.0;
Real sum_v_diff = 0.0;
Real sum_max_v_diff = 0.0;
Real max_curvature = 0.0;
Real sum_curvature = 0.0;
Real sum_max_curvature = 0.0;
Real max_energy = 0.0;
Real sum_energy = 0.0;
Real sum_max_energy = 0.0;
unsigned int numberOfFrames;
Real inertia = 2.0;
Real k_vo = 4000;
Real voMin, voMax;
std::vector<Vector3r> omega0;
Real max_omega_diff = 0.0;
Real sum_omega_diff = 0.0;
Real sum_max_omega_diff = 0.0;

std::vector<std::vector<Vector3r>> fxPerThread, fvPerThread;
std::vector<std::vector<Real>> flifetimePerThread;
std::vector<std::vector<unsigned char>> particleTypePerThread;
std::vector<std::array<unsigned int, maxNumTypes>> numParticlesOfTypePerThread;

inline unsigned int numberOfPointSets()
{
	return static_cast<unsigned int>(neighborhoodSearch->n_point_sets());
}

inline unsigned int numberOfNeighbors(const unsigned int pointSetIndex, const unsigned int index)
{
	return static_cast<unsigned int>(neighborhoodSearch->point_set(0).n_neighbors(pointSetIndex, index));
}

inline unsigned int getNeighbor(const unsigned int pointSetIndex, const unsigned int index, const unsigned int k)
{
	return neighborhoodSearch->point_set(0).neighbor(pointSetIndex, index, k);
}

inline unsigned int numberOfNeighbors(const unsigned int pointSetIndex, const unsigned int index, const unsigned int neighborPointSetIndex)
{
	return static_cast<unsigned int>(neighborhoodSearch->point_set(pointSetIndex).n_neighbors(neighborPointSetIndex, index));
}

inline unsigned int getNeighbor(const unsigned int pointSetIndex, const unsigned int index, const unsigned int neighborPointSetIndex, const unsigned int k)
{
	return neighborhoodSearch->point_set(pointSetIndex).neighbor(neighborPointSetIndex, index, k);
}

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

	Utilities::logger.addSink(unique_ptr<Utilities::ConsoleSink>(new Utilities::ConsoleSink(Utilities::LogLevel::INFO)));
	exePath = FileSystem::getProgramPath();

	try
	{
		cxxopts::Options options(argv[0], "FoamGen - An implementation of Bender et al. \"Turbulent Micropolar SPH Fluids with Foam\", 2018\n\n"
			"By default the limits and the factors (ta, wc, vo) are determined automatically. The amount of generated foam particles "
			"is solely determined by the parameter foamscale. If the -no_auto flag is set, all parameters can be set manually.\n"
		);

		options.add_options()
			("i,input", "Input file (partio)", cxxopts::value<std::string>())
			("o,output", "Output file (partio or vtk)", cxxopts::value<std::string>())
			("q,query", "Query mode: determines max/avg values ")
			("no-auto", "Disable automatic mode. Limits and factors ta, wc, vo must be set manually.")
			("splittypes", "Output each foam type to a different file")
			("splitgenerators", "Output different foam files depending on which potential generated the foam. Overrides --splittypes.")
			("s,startframe", "Start frame", cxxopts::value<unsigned int>()->default_value("1"))
			("e,endframe", "End frame", cxxopts::value<unsigned int>())
			("r,radius", "Particle radius", cxxopts::value<Real>()->default_value("0.025"))
			("t,timestepsize", "Time step size", cxxopts::value<Real>()->default_value("0.02"))
			("k,kernel", "0: Cubic spline, 1: Ihmsen et al. 2012", cxxopts::value<int>()->default_value("0"))
			("l,limits", "Limits (min/max) for potentials (trapped air, wave crest, vorticity, kinetic energy)", cxxopts::value<std::string>()->default_value("5,20,2,8,5,20,5,50"))
			("lifetime", "Lifetime (min/max)", cxxopts::value<std::string>()->default_value("2.0,5.0"))
			("b,buoyancy", "Buoyancy", cxxopts::value<Real>()->default_value("2.0"))
			("d,drag", "Drag", cxxopts::value<Real>()->default_value("0.8"))
			("ta", "Trapped air factor", cxxopts::value<Real>()->default_value("4000"))
			("wc", "Wave crest factor", cxxopts::value<Real>()->default_value("50000"))
			("vo", "Vorticity factor", cxxopts::value<Real>()->default_value("4000"))
			("bbsize", "minimum and maximum coordinates of and axis aligned bounding-box (minX, minY, minZ, maxX, maxY, maxZ)", cxxopts::value<std::string>())
			("bbtype", "chose how the bounding-box is used [kill | lifesteal | clamp]. Use in combination with --bbsize.", cxxopts::value<std::string>())
			("skipframes", "number of frames to skip when writing foam", cxxopts::value<unsigned int>()->default_value("0"))
			("f,foamscale", "Global multiplier for number of generated foam particles", cxxopts::value<Real>()->default_value("1000"))
			("h,help", "Print help")
			;

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

		if (result.count("help"))
		{
			std::cout << options.help({ "", "Group" }) << std::endl;
			exit(0);
		}

		queryMode = false;
		if (result.count("query"))
		{
			queryMode = true;
		}

		automaticMode = true;
		if (result.count("no-auto"))
		{
			automaticMode = false;
		}

		if (result.count("splitgenerators"))
		{
			splitGenerators = true;
			nameExtensions[GeneratedType::TrappedAir] = std::string("_TrappedAir");
			nameExtensions[GeneratedType::WaveCrest]  = std::string("_WaveCrest") ;
			nameExtensions[GeneratedType::Vorticity]  = std::string("_Vorticity") ;
			numTypes = GeneratedType::NUM_GENERATOR_TYPES;
		}
		if (result.count("splittypes") && !splitGenerators)
		{
			splitTypes = true;
			nameExtensions[ParticleType::Foam] = std::string("_foam");
			nameExtensions[ParticleType::Spray] = std::string("_spray");
			nameExtensions[ParticleType::Bubbles] = std::string("_bubbles");
			numTypes = ParticleType::NUM_PARTICLE_TYPES;
		}

		if (result.count("input"))
		{
			input = result["input"].as<std::string>();
			LOG_INFO << "Input = " << input;
		}
		if (result.count("output"))
		{
			output = result["output"].as<std::string>();
			LOG_INFO << "Output = " << output;

			output_format = 0;
			if (Utilities::StringTools::to_upper(FileSystem::getFileExt(output)) == "VTK")
				output_format = 1;
		}
		if (input == "")
		{
			LOG_ERR << "Input is missing!";
			exit(1);
		}
		if ((output == "") && !queryMode)
		{
			LOG_ERR << "Output is missing!";
			exit(1);
		}

		if (result.count("startframe"))
			startFrame = result["startframe"].as<unsigned int>();

		if (result.count("endframe"))
			endFrame = result["endframe"].as<unsigned int>();

		if (result.count("radius"))
			particleRadius = result["radius"].as<Real>();

		if (result.count("timestepsize"))
			timeStepSize = result["timestepsize"].as<Real>();

		if (result.count("kernel"))
		{
			int kt = result["kernel"].as<int>();
			if ((kt < 0) || (kt >= KernelType::NumKernelTypes))
			{
				kt = 0;
				LOG_WARN << "Wrong kernel type. Using cubic spline kernel.";
			}
			kernelType = static_cast<KernelType>(kt);

			if (kernelType == KernelType::CubicSpline)
				kernelFct = CubicKernel::W;
			else if (kernelType == KernelType::Ihmsen2012)
				kernelFct = FoamKernel::W;
		}

		if (result.count("buoyancy"))
			k_buoyancy = result["buoyancy"].as<Real>();

		if (result.count("drag"))
			k_drag = result["drag"].as<Real>();

		if (result.count("skipframes"))
			skipframes = result["skipframes"].as<unsigned int>();

		foam_scale = result["foamscale"].as<Real>();

		k_ta = result["ta"].as<Real>();
		k_wc = result["wc"].as<Real>();
		k_vo = result["vo"].as<Real>();
		{
			std::vector<string> tokens;
			std::string limitsStr = result["limits"].as<std::string>();
			StringTools::tokenize(limitsStr, tokens, ",");
			if (tokens.size() != 8)
			{
				LOG_ERR << "Wrong number of limit values!";
				exit(1);
			}
			taMin = stof(tokens[0]);
			taMax = stof(tokens[1]);
			wcMin = stof(tokens[2]);
			wcMax = stof(tokens[3]);
			voMin = stof(tokens[4]);
			voMax = stof(tokens[5]);
			keMin = stof(tokens[6]);
			keMax = stof(tokens[7]);
			LOG_INFO << "Limits: " << taMin << ", " << taMax << ", "
				<< wcMin << ", " << wcMax << ", "
				<< voMin << ", " << voMax << ", "
				<< keMin << ", " << keMax;
		}

		{
			std::vector<string> tokens;
			std::string ltStr = result["lifetime"].as<std::string>();
			StringTools::tokenize(ltStr, tokens, ",");
			if (tokens.size() != 2)
			{
				LOG_ERR << "Wrong number of lifetime values!";
				exit(1);
			}
			lifetimeMin = stof(tokens[0]);
			lifetimeMax = stof(tokens[1]);
			LOG_INFO << "Lifetime (min/max): " << lifetimeMin << ", " << lifetimeMax;
		}

		if (result.count("bbsize"))
		{
			std::vector<std::string> tokens;
			std::string limitsStr = result["bbsize"].as<std::string>();
			StringTools::tokenize(limitsStr, tokens, ",");
			if (tokens.size() != 6)
			{
				std::cerr << "Wrong number of bounding box values!\n";
				exit(1);
			}
			bbMin = Vector3r(stof(tokens[0]), stof(tokens[1]), stof(tokens[2]));
			bbMax = Vector3r(stof(tokens[3]), stof(tokens[4]), stof(tokens[5]));
			if ((bbMin.array() > bbMax.array()).any())
			{
				std::cerr << "Bounding-box: A min value is larger than its corresponding max value!\n";
				exit(1);
			}
		}
		if (result.count("bbtype"))
		{
			if (!result.count("bbsize"))
			{
				std::cerr << "No bounding-box given! Use --bbsize to set the size of the bounding box.\n";
				exit(1);
			}
			auto s = result["bbtype"].as<std::string>();
				if (s.compare("kill") == 0)
			{
				bbType = BbType::Kill;
			}
			else if (s.compare("lifesteal") == 0)
			{
				bbType = BbType::Lifesteal;
			}
			else if (s.compare("clamp") == 0)
			{
				bbType = BbType::Clamp;
			}
			else
			{
				std::cerr << "Unknown bounding-box type: " << s << "\n";
				exit(1);
			}
		}
		LOG_INFO << "Particle radius: " << particleRadius;
		LOG_INFO << "Drag: " << k_drag;
		LOG_INFO << "Buoyancy: " << k_buoyancy;
		LOG_INFO << "Trapped air factor: " << k_ta;
		LOG_INFO << "Wave crest factor: " << k_wc;
		LOG_INFO << "Vorticity factor: " << k_vo;
		LOG_INFO << "Bounding box: " << bbMin.transpose() << ", " << bbMax.transpose();
		LOG_INFO << "Bounding box type: " << (int) bbType;
		LOG_INFO << "Foam scale: " << foam_scale;
		LOG_INFO << "Time step size: " << timeStepSize;
		if (skipframes > 0)
			std::cout << "Skipping " << skipframes << " frame(s) between writes." << std::endl;
	}
	catch (const cxxopts::OptionException& e)
	{
		LOG_ERR << "error parsing options: " << e.what();
		exit(1);
	}

#ifdef _OPENMP
	const int maxThreads = omp_get_max_threads();
#else
	const int maxThreads = 1;
#endif

	fxPerThread.resize(maxThreads);
	fvPerThread.resize(maxThreads);
	flifetimePerThread.resize(maxThreads);
	particleTypePerThread.resize(maxThreads);
	numParticlesOfTypePerThread.resize(maxThreads);

	// init particle mass
	const Real diam = static_cast<Real>(2.0) * particleRadius;
	mass = static_cast<Real>(0.8) * diam*diam*diam * density0;

	// if in query mode, determine the max values per frame
	Real vDiffAvgMax, omegaDiffAvgMax, curvatureAvgMax, energyAvgMax;
	if (queryMode)
		queryValues(vDiffAvgMax, omegaDiffAvgMax, curvatureAvgMax, energyAvgMax);
	else if (automaticMode)
	{
		queryValues(vDiffAvgMax, omegaDiffAvgMax, curvatureAvgMax, energyAvgMax);

		taMin = static_cast<Real>(0.1)*vDiffAvgMax;
		taMax = vDiffAvgMax;
		wcMin = static_cast<Real>(0.1)*curvatureAvgMax;
		wcMax = curvatureAvgMax;
		voMin = static_cast<Real>(0.1)*omegaDiffAvgMax;
		voMax = omegaDiffAvgMax;
		keMin = static_cast<Real>(0.1)*energyAvgMax;
		keMax = energyAvgMax;
		LOG_INFO << "Limits: " << taMin << ", " << taMax << ", "
			<< wcMin << ", " << wcMax << ", "
			<< voMin << ", " << voMax << ", "
			<< keMin << ", " << keMax;

		k_ta = 1.0;
		k_wc = 1.0;
		k_vo = 1.0;

		generateFoamFiles();
	}
	else
		generateFoamFiles();

	Timing::printAverageTimes();
	Timing::printTimeSums();

	return 0;
}

/** Read the sequence of particle data files and determine for each frame the values
* that are required to decide how many foam particles should be generated.
* Finally, sum up the max values and compute the average wrt the number offrames. */
void queryValues(Real &vDiffAvgMax, Real & omegaDiffAvgMax, Real &curvatureAvgMax, Real &energyAvgMax)
{
	currentFrame = startFrame;
	numberOfFrames = 0;

	// Initialize neighborhood search
	supportRadius = static_cast<Real>(4.0) * particleRadius;

	// Initialize kernel
	FoamKernel::setRadius(supportRadius);
	CubicKernel::setRadius(supportRadius);

	invDt = static_cast<Real>(1.0) / timeStepSize;

	bool chk = true;
	bool pointSetAdded = false;
	START_TIMING("total");
	while (chk)
	{
		LOG_INFO << "Reading frame: " << currentFrame;
		chk = readCurrentFrame();
		if (!chk)
			LOG_ERR << "Failed to read frame";

		START_TIMING("iteration");
		if (neighborhoodSearch == nullptr)
		{
			neighborhoodSearch = new CompactNSearch::NeighborhoodSearch(supportRadius, false);
			neighborhoodSearch->set_radius(supportRadius);
			// Fluids
			neighborhoodSearch->add_point_set(&x0[0][0], x0.size(), true, true);
		}
		else
			neighborhoodSearch->resize_point_set(0, &x0[0][0], x0.size());

		START_TIMING("neighborhoodSearch");
		neighborhoodSearch->find_neighbors();
		STOP_TIMING_AVG

		START_TIMING("determineValues");
		determineValues();
		STOP_TIMING_AVG

		STOP_TIMING_AVG

		numberOfFrames++;

		if (numberOfFrames % 10 == 0)
		{
			LOG_INFO << "v_diff     - max.: " << max_v_diff << ", avg.: " << sum_v_diff / (Real)numberOfFrames;
			LOG_INFO << "Curvature  - max.: " << max_curvature << ", avg.: " << sum_curvature / (Real)numberOfFrames;
			LOG_INFO << "Omega_diff - max.: " << max_omega_diff << ", avg.: " << sum_omega_diff / (Real)numberOfFrames;
			LOG_INFO << "Energy     - max.: " << max_energy << ", avg.: " << sum_energy / (Real)numberOfFrames;
		}

		currentFrame++;
		if (currentFrame > endFrame)
			break;
	}
	delete neighborhoodSearch;
	neighborhoodSearch = nullptr;

	STOP_TIMING_AVG;
	// average maxima
	vDiffAvgMax = sum_max_v_diff / (Real)numberOfFrames;
	omegaDiffAvgMax = sum_max_omega_diff / (Real)numberOfFrames;
	curvatureAvgMax = sum_max_curvature / (Real)numberOfFrames;
	energyAvgMax = sum_max_energy / (Real)numberOfFrames;
	LOG_INFO << "v_diff     - avg. max.: " << vDiffAvgMax;
	LOG_INFO << "Curvature  - avg. max.: " << curvatureAvgMax;
	LOG_INFO << "Omega_diff - avg. max.: " << omegaDiffAvgMax;
	LOG_INFO << "Energy     - avg. max.: " << energyAvgMax;
}

/** Compute values that are required to decide how many foam particles should be generated.\n\n
*
* See:\n
* - Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012 \n
* - Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018
*/
void determineValues()
{
	// compute the density for each particle
	computeDensities();
	// compute the particle normals which are required for the curvature term
	computeNormals();

	Real sumVDiff = 0.0;
	Real sumCurvature = 0.0;
	Real sumEnergy = 0.0;
	Real maxVDiff = 0.0;
	Real maxCurvature = 0.0;
	Real maxEnergy = 0.0;
	Real sumOmegaDiff = 0.0;
	Real maxOmegaDiff = 0.0;

	v_diff.resize(x0.size());
	omega_diff.resize(x0.size());
	curvature.resize(x0.size());
	energy.resize(x0.size());

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)x0.size(); i++)
		{
			const Vector3r &xi = x0[i];
			const Vector3r &vi = v0[i];

			v_diff[i] = 0.0;
			curvature[i] = 0.0;
			omega_diff[i] = 0.0;

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				const Vector3r &vj = v0[neighborIndex];

				//////////////////////////////////////////////////////////////////////////
				// Trapped air potential
				// Compute Eq. 2 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				Vector3r vivj = vi - vj;
				const Real vivjmag = vivj.norm();
				if (vivjmag > 1.0e-6)
					vivj = (1.0 / vivjmag) * vivj;

				Vector3r xixj = xi - xj;
				xixj.normalize();

				if (kernelType != KernelType::Ihmsen2012)
					v_diff[i] += mass / densities[j] * vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * kernelFct(xi - xj);
				else
					v_diff[i] += vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * FoamKernel::W(xi - xj);

				//////////////////////////////////////////////////////////////////////////
				// Wave crest curvature
				// Compute Eq. 4 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				if (-xixj.dot(normals[i]) < 0)
				{
					if (kernelType != KernelType::Ihmsen2012)
						curvature[i] += mass / densities[j] * (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * kernelFct(xi - xj);
					else
						curvature[i] += (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * FoamKernel::W(xi - xj);
				}

				//////////////////////////////////////////////////////////////////////////
				// Vorticity
				// Compute omaga^diff in Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018
				//////////////////////////////////////////////////////////////////////////
				if (omega0.size() > 0)
				{
					const Vector3r &omegai = omega0[i];
					const Vector3r &omegaj = omega0[neighborIndex];
					if (kernelType != KernelType::Ihmsen2012)
						omega_diff[i] += mass / densities[j] * (omegai - omegaj).norm() * kernelFct(xi - xj);
					else
						omega_diff[i] += (omegai - omegaj).norm() * FoamKernel::W(xi - xj);
				}
			}

			//////////////////////////////////////////////////////////////////////////
			// Wave crest
			// Compute Eq. 7 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
			//////////////////////////////////////////////////////////////////////////
			Real delta = 0.0;
			Vector3r vi_normalized = vi;
			vi_normalized.normalize();
			if (vi_normalized.dot(normals[i]) >= 0.6)
				delta = 1.0;

			curvature[i] = delta * curvature[i];

			//////////////////////////////////////////////////////////////////////////
			// Kinetic energic
			//////////////////////////////////////////////////////////////////////////
			if (omega0.size() > 0)
				energy[i] = static_cast<Real>(0.5)*mass*vi.squaredNorm() + static_cast<Real>(0.5) * inertia * omega0[i].squaredNorm();
			else
				energy[i] = static_cast<Real>(0.5)*mass*vi.squaredNorm();
		}
	}

	//////////////////////////////////////////////////////////////////////////
	// Compute sum and max of all values
	//////////////////////////////////////////////////////////////////////////
	for (auto i = 0; i < x0.size(); i++)
	{
		sumVDiff += v_diff[i];
		maxVDiff = std::max(maxVDiff, v_diff[i]);

		if (omega0.size() > 0)
		{
			sumOmegaDiff += omega_diff[i];
			maxOmegaDiff = std::max(maxOmegaDiff, omega_diff[i]);
		}

		sumCurvature += curvature[i];
		maxCurvature = std::max(maxCurvature, curvature[i]);

		sumEnergy += energy[i];
		maxEnergy = std::max(maxEnergy, energy[i]);
	}

	sum_v_diff += sumVDiff / (Real)x0.size();
	max_v_diff = std::max(max_v_diff, maxVDiff);
	sum_max_v_diff += maxVDiff;

	if (omega0.size() > 0)
	{
		sum_omega_diff += sumOmegaDiff / (Real)x0.size();
		max_omega_diff = std::max(max_omega_diff, maxOmegaDiff);
		sum_max_omega_diff += maxOmegaDiff;
	}

	sum_curvature += sumCurvature / (Real)x0.size();
	max_curvature = std::max(max_curvature, maxCurvature);
	sum_max_curvature += maxCurvature;

	sum_energy += sumEnergy / (Real)x0.size();
	max_energy = std::max(max_energy, maxEnergy);
	sum_max_energy += maxEnergy;
}

/** Generate a foam file for each frame.
 */
void generateFoamFiles()
{
	currentFrame = startFrame;

	// Initialize kernels
	supportRadius = static_cast<Real>(4.0) * particleRadius;
	FoamKernel::setRadius(supportRadius);
	CubicKernel::setRadius(supportRadius);

	invDt = static_cast<Real>(1.0) / timeStepSize;

#ifdef _OPENMP
	const int maxThreads = omp_get_max_threads();
#else
	const int maxThreads = 1;
#endif

	bool chk = true;
	bool first = true;
	bool pointSetAdded = false;
	START_TIMING("total");
	while (chk)
	{
		// Read current frame of fluid particles
		LOG_INFO << "Reading frame: " << currentFrame;
		chk = readCurrentFrame();

		if (first)
		{
			// init foam particle vectors
			fx.reserve(10 * x0.size());
			fv.reserve(10 * x0.size());
			flifetime.reserve(10 * x0.size());
			first = false;

			for (int i = 0; i < maxThreads; i++)
			{
				fxPerThread.reserve(10 * x0.size());
				fvPerThread.reserve(10 * x0.size());
				flifetimePerThread.reserve(10 * x0.size());
			}
		}

		// init neighborhood search
		if (neighborhoodSearch == NULL)
		{
			neighborhoodSearch = new CompactNSearch::NeighborhoodSearch(supportRadius, false);
			neighborhoodSearch->set_radius(supportRadius);
			// Fluid
			neighborhoodSearch->add_point_set(&x0[0][0], x0.size(), true, true);
			neighborhoodSearch->set_active(0u, 0u, true);
			pointSetAdded = true;
		}
		else
			neighborhoodSearch->resize_point_set(0, &x0[0][0], x0.size());

		// find the fluid neighbors of each fluid particle
		START_TIMING("neighborhoodSearch - advection");
		neighborhoodSearch->find_neighbors();
		STOP_TIMING_AVG

		// remove foam particles which exceeded their lifetime
		START_TIMING("removeParticles");
		removeParticles();
		STOP_TIMING_AVG;

		// advect each foam particle by performing the time integration
		START_TIMING("advectFoamParticles");
		advectFoamParticles();
		STOP_TIMING_AVG

		// write the foam particles
		START_TIMING("writeCurrentFrame");
		writeCurrentFrame();
		STOP_TIMING_AVG;

		// generate new foam particles
		START_TIMING("generateFoam");
		generateFoam();
		STOP_TIMING_AVG;

		currentFrame++;
		if (currentFrame > endFrame)
			break;
	}

	delete neighborhoodSearch;
	neighborhoodSearch = NULL;

	STOP_TIMING_AVG;
}

/** Generate new foam particles using the algorithm described in :\n
* - Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012 \n
* - Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018
*/
void generateFoam()
{
	// Compute the density for each fluid particle
	computeDensities();
	// Compute the normals for the fluid particles which is needed to compute the curvature
	computeNormals();

#ifdef _OPENMP
	const int maxThreads = omp_get_max_threads();
#else
	const int maxThreads = 1;
#endif

	if (splitGenerators)
	{
		for (int g = 0; g < NUM_GENERATOR_TYPES; ++g)
		{
			numParticlesOfType[g] = 0;
			for (int i=0; i < maxThreads; i++)
			{
				numParticlesOfTypePerThread[i][g] = 0;
			}
		}
	}

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)x0.size(); i++)
		{
			const Vector3r &xi = x0[i];
			const Vector3r &vi = v0[i];

			Real v_diff = 0.0;
			Real curvature = 0.0;
			Real omega_diff = 0.0;

			// Only generate foam if we have enough neighbors
			if (numberOfNeighbors(0, i) < 15)
				continue;

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				const Vector3r &vj = v0[neighborIndex];

				//////////////////////////////////////////////////////////////////////////
				// Trapped air potential
				// Compute Eq. 2 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				Vector3r vivj = vi - vj;
				const Real vivjmag = vivj.norm();
				if (vivjmag > 1.0e-6)
					vivj = (1.0 / vivjmag) * vivj;

				Vector3r xixj = xi - xj;
				xixj.normalize();

				if (kernelType != KernelType::Ihmsen2012)
					v_diff += mass / densities[j] * vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * kernelFct(xi - xj);
				else
					v_diff += vivjmag * (static_cast<Real>(1.0) - vivj.dot(xixj)) * FoamKernel::W(xi - xj);

				//////////////////////////////////////////////////////////////////////////
				// Wave crest curvature
				// Compute Eq. 4 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
				//////////////////////////////////////////////////////////////////////////
				if (-xixj.dot(normals[i]) < 0)
				{
					if (kernelType != KernelType::Ihmsen2012)
						curvature += mass / densities[j] * (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * kernelFct(xi - xj);
					else
						curvature += (static_cast<Real>(1.0) - normals[i].dot(normals[neighborIndex])) * FoamKernel::W(xi - xj);
				}

				//////////////////////////////////////////////////////////////////////////
				// Vorticity
				// Compute omaga^diff in Bender et al., "Turbulent Micropolar SPH Fluids with Foam", 2018
				//////////////////////////////////////////////////////////////////////////
				if (omega0.size() > 0)
				{
					const Vector3r &omegai = omega0[i];
					const Vector3r &omegaj = omega0[neighborIndex];
					if (kernelType != KernelType::Ihmsen2012)
						omega_diff += mass / densities[j] * (omegai - omegaj).norm() * kernelFct(xi - xj);
					else
						omega_diff += (omegai - omegaj).norm() * FoamKernel::W(xi - xj);
				}
			}

			//////////////////////////////////////////////////////////////////////////
			// Trapped air potential
			//////////////////////////////////////////////////////////////////////////
			const Real I_ta = clampAndNormalize(v_diff, taMin, taMax);

			//////////////////////////////////////////////////////////////////////////
			// Vorticity potential
			//////////////////////////////////////////////////////////////////////////
			Real I_vo = 0.0;
			if (omega0.size() > 0)
				I_vo = clampAndNormalize(omega_diff, voMin, voMax);

			//////////////////////////////////////////////////////////////////////////
			// Wave crest
			// Compute Eq. 7 in Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
			//////////////////////////////////////////////////////////////////////////
			Real delta = 0.0;
			Vector3r vi_normalized = vi;
			vi_normalized.normalize();
			if (vi_normalized.dot(normals[i]) >= 0.6)
				delta = 1.0;
			const Real I_wc = clampAndNormalize(delta * curvature, wcMin, wcMax);

			//////////////////////////////////////////////////////////////////////////
			// Kinetic energy
			//////////////////////////////////////////////////////////////////////////
			Real I_ke = 0.0;
			unsigned int nd = 0;
			unsigned int nv = 0;
			if (omega0.size() > 0)
			{
				I_ke = clampAndNormalize(static_cast<Real>(0.5)*mass*vi.squaredNorm() + static_cast<Real>(0.5) * inertia * omega0[i].squaredNorm(), keMin, keMax);
				nd = (unsigned int)(foam_scale * I_ke * (k_ta * I_ta + k_wc * I_wc + k_vo * I_vo) * timeStepSize + 0.5);
				nv = (unsigned int)(foam_scale * I_ke * k_vo * I_vo * timeStepSize + 0.5);
			}
			else
			{
				I_ke = clampAndNormalize(static_cast<Real>(0.5)*mass*vi.squaredNorm(), keMin, keMax);
				nd = (unsigned int) std::max((foam_scale * I_ke * (k_ta * I_ta + k_wc * I_wc) * timeStepSize + static_cast<Real>(0.5)), static_cast<Real>(0.0));
				nv = 0;
			}

			const unsigned int nt = (unsigned int)(foam_scale * I_ke * k_ta * I_ta * timeStepSize + 0.5);
			const unsigned int nw = (unsigned int)(foam_scale * I_ke * k_wc * I_wc * timeStepSize + 0.5);

			//////////////////////////////////////////////////////////////////////////
			// Generate foam particles
			//////////////////////////////////////////////////////////////////////////
			if (splitGenerators)
				generateFoamParticles(i, nt + nw + nv, nt, nw, nv, I_ke);
			else
				generateFoamParticles(i, nd, -1, -1, -1, I_ke);
		}
	}

	// Merge particles that have been generated in different threads
	for (int j = 0; j < maxThreads; j++)
	{
		for (int i = 0; i < fxPerThread[j].size(); i++)
		{
			fx.push_back(fxPerThread[j][i]);
			fv.push_back(fvPerThread[j][i]);
			flifetime.push_back(flifetimePerThread[j][i]);
			particleType.push_back(particleTypePerThread[j][i]);
		}
		fxPerThread[j].clear();
		fvPerThread[j].clear();
		flifetimePerThread[j].clear();
		particleTypePerThread[j].clear();

		for (int i = 0; i < 3; i++)
		{
			numParticlesOfType[i] += numParticlesOfTypePerThread[j][i];
		}
	}

	LOG_INFO << "# foam particles: " << fx.size();
}

std::string zeroPadding(const unsigned int number, const unsigned int length)
{
	ostringstream out;
	out << std::internal << std::setfill('0') << std::setw(length) << number;
	return out.str();
}

/** Substitute the placeholder in the file name with the current frame number.
*/
std::string convertFileName(const std::string &inputFileName, const unsigned int currentFrame)
{
	std::string fileName = inputFileName;
	std::string::size_type pos1 = fileName.find_first_of("#", 0);
	if (pos1 == std::string::npos)
	{
		LOG_ERR << "# missing in file name.";
		exit(1);
	}
	std::string::size_type pos2 = fileName.find_first_not_of("#", pos1);
	std::string::size_type length = pos2 - pos1;

	std::string numberStr = zeroPadding(currentFrame, (unsigned int)length);
	fileName.replace(pos1, length, numberStr);
	return fileName;
}

/** Read fluid particles of current frame.
*/
bool readCurrentFrame()
{
	std::string fileName = convertFileName(input, currentFrame);

	x0.clear();
	v0.clear();
	omega0.clear();
	return readParticles(fileName, x0, v0, omega0);
}

void writeCurrentFrame()
{
	if (output_format == 1)
		writeCurrentFrame_vtk();
	else
		writeCurrentFrame_bgeo();
}

/** Write foam particles of current frame.
*/
void writeCurrentFrame_bgeo()
{
	if (currentFrame % (1 + skipframes))
	{
		std::cout << "Skipping write of frame: " << currentFrame << std::endl;
		return;
	}
	else
	{
		std::cout << "Writing frame: " << currentFrame << std::endl;
	}

	std::string fileName = output;
	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// local references for lambda capture
	Utilities::Logger & logger = Utilities::logger;
	const auto & numOfType = numParticlesOfType;
	auto & handles = handlesType;
	const auto & particleData = fx;
	const auto & particleTypes = particleType;
	const auto & nameExt = nameExtensions;
	// helper lambda to write particles depending on type
	auto writeByType = [&](const char type) {
		extendedFileNames[type] = convertFileName(fileName, currentFrame / (1 + skipframes));
		const std::string::size_type pos = extendedFileNames[type].rfind(".");
		if (pos != std::string::npos)
			extendedFileNames[type].insert(pos, nameExt[type]);

		LOG_INFO << "Writing: " << extendedFileNames[type];
		if (handles[type].valid())
			handles[type].wait();
		particleCopies[type].clear();
		particleCopies[type].reserve(numOfType[type]);
		for (int i = 0; i < (int)particleData.size(); i++)
		{
			if (particleTypes[i] == type)
				particleCopies[type].push_back(particleData[i]);
		}
		handles[type] = std::async(std::launch::async, [type] { PartioReaderWriter::writeParticles(extendedFileNames[type], (unsigned int)particleCopies[type].size(), particleCopies[type].data(), nullptr, 0.0); });
	};

	if (splitGenerators)
	{
		writeByType(GeneratedType::TrappedAir);
		writeByType(GeneratedType::WaveCrest );
		writeByType(GeneratedType::Vorticity );
	}
	else if (splitTypes)
	{
		writeByType(ParticleType::Foam   );
		writeByType(ParticleType::Spray  );
		writeByType(ParticleType::Bubbles);
	}
	else
	{
		std::string fileName = convertFileName(output, currentFrame / (1 + skipframes));

		LOG_INFO << "Writing: " << fileName;

		if (handle.valid())
			handle.wait();

		x_copy.resize(fx.size());
		//v_copy.resize(fv.size());
		fileName_copy = fileName;
		for (int i = 0; i < (int)fx.size(); i++)
		{
			x_copy[i] = fx[i];
		}

		handle = std::async(std::launch::async, [] { PartioReaderWriter::writeParticles(fileName_copy, (unsigned int)x_copy.size(), x_copy.data(), nullptr, 0.0); });
	}
}

// VTK expects big endian
template<typename T>
inline void swapByteOrder(T* v)
{
	constexpr size_t n = sizeof(T);
	uint8_t* bytes = reinterpret_cast<uint8_t*>(v);
	for (unsigned int c = 0u; c < n / 2; c++)
		std::swap(bytes[c], bytes[n - c - 1]);
}

void writeParticlesVTK(const std::string& fileName, const unsigned int numParticles, const Vector3r* particlePositions)
{
	if (0 == numParticles)
		return;

#ifdef USE_DOUBLE
	const char* real_str = " double\n";
#else
	const char* real_str = " float\n";
#endif

	// Open the file
	std::ofstream outfile{ fileName, std::ios::binary };
	if (!outfile.is_open())
	{
		LOG_WARN << "Cannot open a file to save VTK particles.";
		return;
	}

	outfile << "# vtk DataFile Version 4.1\n";
	outfile << "SPlisHSPlasH particle data\n"; // title of the data set, (any string up to 256 characters+\n)
	outfile << "BINARY\n";
	outfile << "DATASET UNSTRUCTURED_GRID\n";

	//////////////////////////////////////////////////////////////////////////
	// export position attribute as POINTS
	{
		std::vector<Vector3r> positions;
		positions.reserve(numParticles);
		for (unsigned int i = 0u; i < numParticles; i++)
			positions.emplace_back(particlePositions[i]);
		// swap endianess
		for (unsigned int i = 0; i < numParticles; i++)
			for (unsigned int c = 0; c < 3; c++)
				swapByteOrder(&positions[i][c]);
		// export to vtk
		outfile << "POINTS " << numParticles << real_str;
		outfile.write(reinterpret_cast<char*>(positions[0].data()), 3 * numParticles * sizeof(Real));
		outfile << "\n";
	}

	//////////////////////////////////////////////////////////////////////////
	// export particle IDs as CELLS
	{
		std::vector<Eigen::Vector2i> cells;
		cells.reserve(numParticles);
		unsigned int nodes_per_cell_swapped = 1;
		swapByteOrder(&nodes_per_cell_swapped);
		for (unsigned int i = 0u; i < numParticles; i++)
		{
			unsigned int idSwapped = i;
			swapByteOrder(&idSwapped);
			cells.emplace_back(nodes_per_cell_swapped, idSwapped);
		}

		// particles are cells with one element and the index of the particle
		outfile << "CELLS " << numParticles << " " << 2 * numParticles << "\n";
		outfile.write(reinterpret_cast<char*>(cells[0].data()), 2 * numParticles * sizeof(unsigned int));
		outfile << "\n";
	}
	//////////////////////////////////////////////////////////////////////////
	// export cell types
	{
		// the type of a particle cell is always 1
		std::vector<int> cellTypes;
		int cellTypeSwapped = 1;
		swapByteOrder(&cellTypeSwapped);
		cellTypes.resize(numParticles, cellTypeSwapped);
		outfile << "CELL_TYPES " << numParticles << "\n";
		outfile.write(reinterpret_cast<char*>(cellTypes.data()), numParticles * sizeof(int));
		outfile << "\n";
	}

	//////////////////////////////////////////////////////////////////////////
	// write additional attributes as per-particle data
	{
		outfile << "POINT_DATA " << numParticles << "\n";
		// write IDs
		outfile << "SCALARS id unsigned_int 1\n";
		outfile << "LOOKUP_TABLE id_table\n";
		// copy data
		std::vector<unsigned int> attrData;
		attrData.reserve(numParticles);
		for (unsigned int i = 0u; i < numParticles; i++)
			attrData.emplace_back(i);
		// swap endianess
		for (unsigned int i = 0; i < numParticles; i++)
			swapByteOrder(&attrData[i]);
		// export to vtk
		outfile.write(reinterpret_cast<char*>(attrData.data()), numParticles * sizeof(unsigned int));
		outfile << "\n";
	}

	outfile.close();
}

/** Write foam particles of current frame.
*/
void writeCurrentFrame_vtk()
{
	if (currentFrame % (1 + skipframes))
	{
		std::cout << "Skipping write of frame: " << currentFrame << std::endl;
		return;
	}
	else
	{
		std::cout << "Writing frame: " << currentFrame << std::endl;
	}

	std::string fileName = output;
	FileSystem::makeDir(FileSystem::getFilePath(FileSystem::normalizePath(output)));

	// local references for lambda capture
	Utilities::Logger& logger = Utilities::logger;
	const auto& numOfType = numParticlesOfType;
	auto& handles = handlesType;
	const auto& particleData = fx;
	const auto& particleTypes = particleType;
	const auto& nameExt = nameExtensions;
	// helper lambda to write particles depending on type
	auto writeByType = [&](const char type) {
		extendedFileNames[type] = convertFileName(fileName, currentFrame / (1 + skipframes));
		const std::string::size_type pos = extendedFileNames[type].rfind(".");
		if (pos != std::string::npos)
			extendedFileNames[type].insert(pos, nameExt[type]);

		LOG_INFO << "Writing: " << extendedFileNames[type];
		if (handles[type].valid())
			handles[type].wait();
		particleCopies[type].clear();
		particleCopies[type].reserve(numOfType[type]);
		for (int i = 0; i < (int)particleData.size(); i++)
		{
			if (particleTypes[i] == type)
				particleCopies[type].push_back(particleData[i]);
		}
		handles[type] = std::async(std::launch::async, [type] { writeParticlesVTK(extendedFileNames[type], (unsigned int)particleCopies[type].size(), particleCopies[type].data()); });
	};

	if (splitGenerators)
	{
		writeByType(GeneratedType::TrappedAir);
		writeByType(GeneratedType::WaveCrest);
		writeByType(GeneratedType::Vorticity);
	}
	else if (splitTypes)
	{
		writeByType(ParticleType::Foam);
		writeByType(ParticleType::Spray);
		writeByType(ParticleType::Bubbles);
	}
	else
	{
		std::string fileName = convertFileName(output, currentFrame / (1 + skipframes));

		LOG_INFO << "Writing: " << fileName;

		if (handle.valid())
			handle.wait();

		x_copy.resize(fx.size());
		//v_copy.resize(fv.size());
		fileName_copy = fileName;
		for (int i = 0; i < (int)fx.size(); i++)
		{
			x_copy[i] = fx[i];
		}

		handle = std::async(std::launch::async, [] { writeParticlesVTK(fileName_copy, (unsigned int)x_copy.size(), x_copy.data()); });
	}
}

/** Clamp and normalize value. Compute Eq. 1 in
* Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
*/
Real clampAndNormalize(const Real val, const Real minVal, const Real maxVal)
{
	return (std::min(val, maxVal) - std::min(val, minVal)) / (maxVal - minVal);
}

/** Compute normals of fluid particles using a color field.
*/
void computeNormals()
{
	normals.resize(x0.size());

	// Compute normals
	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)x0.size(); i++)
		{
			const Vector3r &xi = x0[i];
			Vector3r &ni = normals[i];
			ni.setZero();

			// We are only interested in surface particles
			if (numberOfNeighbors(0, i) > 20)
				continue;

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				const Real density_j = densities[neighborIndex];
				ni -= mass / density_j * CubicKernel::gradW(xi - xj);
			}
			ni.normalize();
		}
	}

}

/** Compute densities of the fluid particles.
*/
void computeDensities()
{
	densities.resize(x0.size());

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)x0.size(); i++)
		{
			Real &density = densities[i];

			// Compute current density for particle i
			density = mass * CubicKernel::W_zero();
			const Vector3r &xi = x0[i];

			//////////////////////////////////////////////////////////////////////////
			// Fluid
			//////////////////////////////////////////////////////////////////////////
			for (unsigned int j = 0; j < numberOfNeighbors(0, i); j++)
			{
				const unsigned int neighborIndex = getNeighbor(0, i, j);
				const Vector3r &xj = x0[neighborIndex];
				density += mass * CubicKernel::W(xi - xj);
			}
		}
	}
}

/** Returns two orthogonal vectors to vec which are also orthogonal to each other.
*/
void getOrthogonalVectors(const Vector3r &vec, Vector3r &x, Vector3r &y)
{
	// Get plane vectors x, y
	Vector3r v(1, 0, 0);

	// Check, if v has same direction as vec
	if (fabs(v.dot(vec)) > 0.999)
		v = Vector3r(0, 1, 0);

	x = vec.cross(v);
	y = vec.cross(x);
	x.normalize();
	y.normalize();
}

/** Generate new foam particles for the fluid particle with the given index.
* The particles are generated in a cylinder as described in
* Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
*/
void generateFoamParticles(const unsigned int index, const unsigned int numParticles, const unsigned int numTrappedAir, const unsigned int numWaveCrest, const unsigned int numVorticity, const Real I_ke)
{
	Vector3r e1, e2;
	const Vector3r v = v0[index];
	Vector3r vn = v;
	vn.normalize();
	getOrthogonalVectors(vn, e1, e2);

	e1 = particleRadius * e1;
	e2 = particleRadius * e2;

	for (unsigned int i = 0; i < numParticles; i++)
	{
		// Generate a random distribution of the foam particles in a cylinder.
		const Real Xr = uniform_distr_0_1(random_generator);
		const Real Xtheta = uniform_distr_0_1(random_generator);
		const Real Xh = uniform_distr_0_1(random_generator);

		const Real r = particleRadius * sqrt(Xr);
		const Real theta = Xtheta * static_cast<Real>(2.0 * M_PI);
		const Real h = (Xh- static_cast<Real>(0.5)) * timeStepSize * v.norm();

		const Vector3r xd = x0[index] + r * cos(theta) * e1 + r * sin(theta) * e2 + h * vn;
		const Vector3r vd = r * cos(theta) * e1 + r * sin(theta) * e2 + v;

		unsigned char generatorType = GeneratedType::TrappedAir;
		if (i >= numTrappedAir)
			generatorType = GeneratedType::WaveCrest;
		if (i >= (numTrappedAir + numWaveCrest))
			generatorType = GeneratedType::Vorticity;

#ifdef _OPENMP
		int tid = omp_get_thread_num();
#else
		int tid = 0;
#endif
		fxPerThread[tid].push_back(xd);
		fvPerThread[tid].push_back(vd);
		if (splitGenerators)
		{
			particleTypePerThread[tid].push_back(generatorType);
			numParticlesOfTypePerThread[tid][generatorType]++;
		}
		else
		{
			particleTypePerThread[tid].push_back(ParticleType::Foam); // default, actual value will be set during advection
		}
		Real lt = lifetimeMin + I_ke / keMax * uniform_distr_0_1(random_generator) *(lifetimeMax - lifetimeMin);
		flifetimePerThread[tid].push_back(lt);
	}
}

/** Avect all foam particles according to
* Ihmsen et al., "Unified spray, foam and air bubbles for particle-based fluids", 2012
*/
void advectFoamParticles()
{
	Vector3r g(0.0, -9.81, 0.0);
	if (splitTypes)
	{
		numParticlesOfType[ParticleType::Foam] = 0;
		numParticlesOfType[ParticleType::Spray] = 0;
		numParticlesOfType[ParticleType::Bubbles] = 0;
	}

	std::vector<std::vector<unsigned int>> neighbors;
	neighbors.reserve(100);

	#pragma omp parallel default(shared), private(neighbors)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int) fx.size(); i++)
		{
			const Vector3r &xi = fx[i];

			// determine particle type
			neighborhoodSearch->find_neighbors(xi.data(), neighbors);
			const unsigned int numFluidNeighbors = (unsigned int) neighbors[0].size();

			unsigned char ftype = ParticleType::Foam;
			if (numFluidNeighbors < 6)
				ftype = ParticleType::Spray;
			else if (numFluidNeighbors > 20)
				ftype = ParticleType::Bubbles;

			// Fix particle type if classified as foam because of boundary.
			// If a particle is close to the boundary the number of fluid neighbors will be lower than for a particle inside the fluid.
			// A corrected number of fluid neighbors is estimated by adding the current number of fluid neighbors scaled by the volume
			// fraction of the cap of the sphere making up the neighborhood that lies outside of the boundary.
			if (ftype == ParticleType::Foam)
			{
				auto correctedFluidNeighbors = numFluidNeighbors;
				// Used to compute the volume of the cap of the neighborhood sphere that lies outside the boundary.
				auto unitSphereCapVolume = [&](const Real h)
				{
					return h * h * (M_PI / 3) * (3 - h);
				};
				for (auto j = 0u; j < 3; ++j)
				{
					Real d;
					d = std::abs(xi[j] - bbMin[j]);
					if (d < supportRadius)
						correctedFluidNeighbors += (decltype(correctedFluidNeighbors))(std::ceil(correctedFluidNeighbors * unitSphereCapVolume(1 - d / supportRadius)));
					d = std::abs(xi[j] - bbMax[j]);
					if (d < supportRadius)
						correctedFluidNeighbors += (decltype(correctedFluidNeighbors))(std::ceil(correctedFluidNeighbors * unitSphereCapVolume(1 - d / supportRadius)));
					if (correctedFluidNeighbors > 20)
					{
						ftype = ParticleType::Bubbles;
						break;
					}
				}
			}

			if (splitTypes)
			{
				particleType[i] = ftype;
				numParticlesOfType[ftype]++;
			}

			// handle bounding box
			if (((xi.array() < bbMin.array() || xi.array() > bbMax.array()).any()))
			{
				if (bbType == BbType::Kill)
				{
					flifetime[i] = 0;
				}
				else if (bbType == BbType::Lifesteal)
				{
					flifetime[i] -= static_cast<Real>(1000.0) * timeStepSize;
				}
				// clamp particle position to bounding box and reflect velocity on box wall
				else if (bbType == BbType::Clamp)
				{
					const Vector3r bbNormal = ((fx[i].array() < bbMin.array()).cast<Real>() - (fx[i].array() > bbMax.array()).cast<Real>()).matrix().normalized();
					const Vector3r vReflect = fv[i] - 2 * bbNormal.dot(fv[i]) * bbNormal;
					fv[i] = Real(0.25) * vReflect;
					fx[i] = (fx[i].array() < bbMin.array()).select(bbMin, fx[i]);
					fx[i] = (fx[i].array() > bbMax.array()).select(bbMax, fx[i]);
					// also lifesteal
					//flifetime[i] -= 1000.0 * timeStepSize;
				}
			}

			// advect particle
			if (ftype == ParticleType::Spray)
			{
				// spray
				fv[i] += timeStepSize * g;
				//fv[i] *= 0.99;
				fx[i] += timeStepSize * fv[i];

// 				if (numFluidNeighbors < 2)
// 					flifetime[i] -= 15.0*timeStepSize;
// 				else
// 					flifetime[i] -= 2.0*timeStepSize;
			}
			else if ((ftype == ParticleType::Foam) || (ftype == ParticleType::Bubbles))
			{
				Vector3r vf = Vector3r::Zero();
				Real sumK = 0.0;
				for (unsigned int j = 0; j < numFluidNeighbors; j++)
				{
					const unsigned int neighborIndex = neighbors[0][j];
					const Vector3r &xj = x0[neighborIndex];
					const Real K = CubicKernel::W(xi - xj);
					const Vector3r v = v0[neighborIndex];
					vf += v * K;
					sumK += K;
				}
				vf = (1.0 / sumK) * vf;

				if (ftype == ParticleType::Foam)
				{
					// foam
					//fv[i] = vf;
					fx[i] += timeStepSize * vf;
					flifetime[i] -= timeStepSize;
				}
				else if (ftype == ParticleType::Bubbles)
				{
					// bubbles
					fv[i] += timeStepSize * (-k_buoyancy * g + k_drag * invDt * (vf - fv[i]));
					fx[i] += timeStepSize * fv[i];

					//flifetime[i] -= 0.333*timeStepSize;
				}
			}
		}
	}
	if (splitTypes || splitGenerators)
		for (unsigned int i = 0; i < numTypes; i++)
			LOG_INFO << "#" << nameExtensions[i] << ": " << numParticlesOfType[i];
}

/** Remove foam particles which are at the end of their lifetime.
*/
void removeParticles()
{
	unsigned int removedParticles = 0;
	for (int i = 0; i < (int)fx.size(); i++)
	{
		if (flifetime[i] <= 0.0)
		{
			removedParticles++;
		}
		else
		{
			fx[i - removedParticles] = fx[i];
			fv[i - removedParticles] = fv[i];
			flifetime[i - removedParticles] = flifetime[i];
			particleType[i - removedParticles] = particleType[i];
		}
	}
	if (removedParticles > 0)
	{
		fx.resize(fx.size() - removedParticles);
		fv.resize(fv.size() - removedParticles);
		flifetime.resize(flifetime.size() - removedParticles);
		particleType.resize(particleType.size() - removedParticles);
	}
}

/** Read particle data from a partio file.
*/
bool readParticles(const std::string &fileName, std::vector<Vector3r> &positions, std::vector<Vector3r> &velocities, std::vector<Vector3r> &angularVelocities)
{
	if (!FileSystem::fileExists(fileName))
		return false;

	Partio::ParticlesDataMutable* data = Partio::read(fileName.c_str());
	if (!data)
		return false;

	unsigned int posIndex = 0xffffffff;
	unsigned int velIndex = 0xffffffff;
	unsigned int omegaIndex = 0xffffffff;

	for (int i = 0; i < data->numAttributes(); i++)
	{
		Partio::ParticleAttribute attr;
		data->attributeInfo(i, attr);
		if (attr.name == "position")
			posIndex = i;
		else if (attr.name == "velocity")
			velIndex = i;
		else if (attr.name == "angularVelocity")
			omegaIndex = i;
	}

	Partio::ParticleAttribute attr;

	if (posIndex != 0xffffffff)
	{
		unsigned int fSize = (unsigned int)positions.size();
		positions.resize(fSize + data->numParticles());
		data->attributeInfo(posIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float *pos = data->data<float>(attr, i);
			Vector3r x(pos[0], pos[1], pos[2]);
			positions[i + fSize] = x;
		}
	}

	if (velIndex != 0xffffffff)
	{
		unsigned int fSize = (unsigned int)velocities.size();
		velocities.resize(fSize + data->numParticles());
		data->attributeInfo(velIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float *vel = data->data<float>(attr, i);
			Vector3r v(vel[0], vel[1], vel[2]);
			velocities[i + fSize] = v;
		}
	}
	else
	{
		unsigned int fSize = (unsigned int)velocities.size();
		velocities.resize(fSize + data->numParticles());
		for (int i = 0; i < data->numParticles(); i++)
			velocities[i + fSize].setZero();
	}

	if (omegaIndex != 0xffffffff)
	{
		unsigned int fSize = (unsigned int)angularVelocities.size();
		angularVelocities.resize(fSize + data->numParticles());
		data->attributeInfo(omegaIndex, attr);
		for (int i = 0; i < data->numParticles(); i++)
		{
			const float *vel = data->data<float>(attr, i);
			Vector3r v(vel[0], vel[1], vel[2]);
			angularVelocities[i + fSize] = v;
		}
	}

	data->release();
	return true;
}


/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2015 Blender Foundation.
 * All rights reserved.
 */

#ifndef __EIGEN3_EIGENVALUES_C_API_CC__
#define __EIGEN3_EIGENVALUES_C_API_CC__

/* Eigen gives annoying huge amount of warnings here, silence them! */
#if defined(__GNUC__) && !defined(__clang__)
#  pragma GCC diagnostic ignored "-Wlogical-op"
#endif

#include <Eigen/Core>
#include <Eigen/Eigenvalues>

#include "eigenvalues.h"

using Eigen::SelfAdjointEigenSolver;

using Eigen::Map;
using Eigen::MatrixXf;
using Eigen::VectorXf;

using Eigen::Success;

bool EIG_self_adjoint_eigen_solve(const int size,
                                  const float *matrix,
                                  float *r_eigen_values,
                                  float *r_eigen_vectors)
{
  SelfAdjointEigenSolver<MatrixXf> eigen_solver;

  /* Blender and Eigen matrices are both column-major. */
  eigen_solver.compute(Map<MatrixXf>((float *)matrix, size, size));

  if (eigen_solver.info() != Success) {
    return false;
  }

  if (r_eigen_values) {
    Map<VectorXf>(r_eigen_values, size) = eigen_solver.eigenvalues().transpose();
  }

  if (r_eigen_vectors) {
    Map<MatrixXf>(r_eigen_vectors, size, size) = eigen_solver.eigenvectors();
  }

  return true;
}

#endif /* __EIGEN3_EIGENVALUES_C_API_CC__ */