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

#ifndef SWEEP_UTIL_TYPES_H
#define SWEEP_UTIL_TYPES_H

#include <cstdint>

#include <gsl/gsl_util>

namespace sweep::util {
inline namespace types {

using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;

using usize = std::size_t;
using isize = std::ptrdiff_t;

using f32 = float;
using f64 = double;

inline namespace literals {

constexpr u8 operator""_u8(unsigned long long x) noexcept {
    return gsl::narrow_cast<u8>(x);
}

constexpr u16 operator""_u16(unsigned long long x) noexcept {
    return gsl::narrow_cast<u16>(x);
}

constexpr u32 operator""_u32(unsigned long long x) noexcept {
    return gsl::narrow_cast<u32>(x);
}

constexpr u64 operator""_u64(unsigned long long x) noexcept {
    return gsl::narrow_cast<u64>(x);
}

constexpr i8 operator""_i8(unsigned long long x) noexcept {
    return gsl::narrow_cast<i8>(x);
}

constexpr i16 operator""_i16(unsigned long long x) noexcept {
    return gsl::narrow_cast<i16>(x);
}

constexpr i32 operator""_i32(unsigned long long x) noexcept {
    return gsl::narrow_cast<i32>(x);
}

constexpr i64 operator""_i64(unsigned long long x) noexcept {
    return gsl::narrow_cast<i64>(x);
}

constexpr usize operator""_usize(unsigned long long x) noexcept {
    return gsl::narrow_cast<usize>(x);
}

constexpr isize operator""_isize(unsigned long long x) noexcept {
    return gsl::narrow_cast<isize>(x);
}

constexpr f32 operator""_f32(long double x) noexcept {
    return gsl::narrow_cast<f32>(x);
}

constexpr f64 operator""_f64(long double x) noexcept {
    return gsl::narrow_cast<f64>(x);
}

} // inline namespace literals
} // inline namespace types
} // namespace sweep::util

#endif


#ifndef __GSL_PERMUTE_VECTOR_H__
#define __GSL_PERMUTE_VECTOR_H__

#if !defined( GSL_FUN )
#  if !defined( GSL_DLL )
#    define GSL_FUN extern
#  elif defined( BUILD_GSL_DLL )
#    define GSL_FUN extern __declspec(dllexport)
#  else
#    define GSL_FUN extern __declspec(dllimport)
#  endif
#endif

#include <gsl/gsl_permute_vector_complex_long_double.h>
#include <gsl/gsl_permute_vector_complex_double.h>
#include <gsl/gsl_permute_vector_complex_float.h>

#include <gsl/gsl_permute_vector_long_double.h>
#include <gsl/gsl_permute_vector_double.h>
#include <gsl/gsl_permute_vector_float.h>

#include <gsl/gsl_permute_vector_ulong.h>
#include <gsl/gsl_permute_vector_long.h>

#include <gsl/gsl_permute_vector_uint.h>
#include <gsl/gsl_permute_vector_int.h>

#include <gsl/gsl_permute_vector_ushort.h>
#include <gsl/gsl_permute_vector_short.h>

#include <gsl/gsl_permute_vector_uchar.h>
#include <gsl/gsl_permute_vector_char.h>

#endif /* __GSL_PERMUTE_VECTOR_H__ */


/* -*- C -*- */
/**
 * Author: Pierre Schnizer
 * Date : January 2003
 */
#include <gsl/gsl_errno.h>
#include <gsl/gsl_math.h>
#include <pygsl/general_helpers.h>
#include <pygsl/utils.h>
#include <pygsl/profile.h>

PyGSL_API_EXTERN int
PyGSL_set_error_string_for_callback(PyGSL_error_info * info)
{
     PyObject *name_o = NULL;
     PyObject *callback;
     const char * message = "";
     const char * error_description = "";
     const char * mmesg;
     char * name, msg[1024];
     char *formatstring = "For the callback %s evaluted  for function %s, an error occured : %s";

     FUNC_MESS_BEGIN();
     callback = info->callback;
     if(info->message){
	  message = info->message;
     }
     if(info->error_description){
	  error_description = info->error_description;
     }

     if (message == NULL){
	  mmesg = "Unknown";
     }else{
	  mmesg = message;
     }
     assert(callback != NULL);
     name_o = PyObject_GetAttrString(callback, "__name__");
     if (name_o == NULL){
	  name_o = PyObject_GetAttrString(callback, "func_name");
     }
     if (name_o == NULL){
	 PyErr_SetString(PyExc_AttributeError,
			 "While I was treating an errornous callback object,"
			 " I found that it had no attribute '__name__'!");
	 PyGSL_ERROR("Could not get the name of the callback!", GSL_EBADFUNC);
	 goto fail;
     }
     if(!PyString_Check(name_o)){
	  PyErr_SetString(PyExc_TypeError,
			  " For an errornous callback object,"
			  " the attribute '__name__' was not a Python string!");
	 PyGSL_ERROR("Nameobject of the callback was not a string!", GSL_EBADFUNC);
	 goto fail;
     }
     name = PyString_AsString(name_o);

     /* A non completly standard but safe function. */
     FUNC_MESS("\tmakeing string");
     snprintf(msg, 1024, formatstring, name, mmesg, error_description);
     if(DEBUG > 2){
	  fprintf(stderr, "ERROR: \t%s", msg);
     }
     PyGSL_ERROR(msg, GSL_EBADFUNC);

     FUNC_MESS_END();
     return GSL_EBADFUNC;
     /* Py_XDECREF(name_o);  ??? */
 fail:
     return GSL_EBADFUNC;
     /* Py_XDECREF(name_o);  ??? */
}

PyGSL_API_EXTERN int
PyGSL_pyfloat_to_double(PyObject *object, double *result, PyGSL_error_info *info)
{

     PyObject *object1;
     char *msg="The object returned to the GSL Function could not be converted to float";

     FUNC_MESS_BEGIN();
     object1 = PyNumber_Float(object);
     if(object1 == NULL){
	 *result = gsl_nan();
	 if(info){
	      info->error_description = msg;
	      return PyGSL_set_error_string_for_callback(info);
	 }
	 DEBUG_MESS(2, "Not from call back treatment, normal error. info = %p", info);
	 PyGSL_ERROR(msg, GSL_EBADFUNC);
     }

     *result   = PyFloat_AsDouble(object1);
     DEBUG_MESS(3, "found a double of %f\n", *result);
     Py_DECREF(object1);

     PyGSL_INCREASE_float_transform_counter();
     FUNC_MESS_END();
     return GSL_SUCCESS;
}

PyGSL_API_EXTERN int
PyGSL_pylong_to_uint(PyObject *object, unsigned int *result, PyGSL_error_info *info)
{
     int flag;
     unsigned long int tmp;
     flag =PyGSL_pylong_to_ulong(object, &tmp, info);
     *result = (unsigned int) tmp;
     return flag;
}

PyGSL_API_EXTERN int
PyGSL_pylong_to_ulong(PyObject *object, unsigned long *result, PyGSL_error_info *info)
{

     PyObject *object1;
     char *msg="The object returned to the GSL Function could not be converted to unsigned long";

     object1 = PyNumber_Long(object);
     if(object1 == NULL){
	 *result = 0;
	 if(info){
	      info->error_description = msg;
	      return PyGSL_set_error_string_for_callback(info);
	 }
	 PyGSL_ERROR(msg, GSL_EINVAL);
     }

     *result   = PyLong_AsUnsignedLong(object1);
     if(DEBUG>2){
	  fprintf(stderr, "\t\t%s found a double of %ld\n", __FUNCTION__, *result);
     }
     Py_DECREF(object1);

     PyGSL_INCREASE_float_transform_counter();

     return GSL_SUCCESS;
}

PyGSL_API_EXTERN int
PyGSL_pyint_to_int(PyObject *object, int *result, PyGSL_error_info *info)
{

     PyObject *object1;
     char *msg="The object returned to the GSL Function could not be converted to int";
     long tmp;

     FUNC_MESS_BEGIN();
     object1 = PyNumber_Int(object);
     if(object1 == NULL){
	 *result = INT_MIN;
	 if(info){
	      info->error_description = msg;
	      return PyGSL_set_error_string_for_callback(info);
	 }
	 DEBUG_MESS(2, "Not from call back treatment, normal error. info = %p", info);
	 PyGSL_ERROR(msg, GSL_EINVAL);
     }

     tmp = PyInt_AsLong(object1);
     if(tmp > INT_MAX)
	  PyGSL_ERROR("Number too big for int", GSL_EINVAL);
     else if (tmp < INT_MIN)
	  PyGSL_ERROR("Number too small for int", GSL_EINVAL);

     *result = (int) tmp;
     DEBUG_MESS(3, "found a int of %d\n", *result);
     Py_DECREF(object1);
     FUNC_MESS_END();
     return GSL_SUCCESS;
}

/*
 * Checks following conditions:
 *  For No Arguments: Got Py_None and No Error
 *  For 1  Argument:  Got an Object, No None  and No Error
 *         (Is None a legal return for one object? I think so.) On the other hand its a
 *         callback and Conversions are waiting, so its good not to accept None.
 * For 2  Arguments: Got a tuple of approbriate size
 */
#define PyGSL_CHECK_PYTHON_RETURN(object, nargs, info)                              \
  (                                                                                \
        (  ( (nargs) == 0 ) && ( object ) && ( Py_None == (object) ) && ( !PyErr_Occurred() ) )   \
    ||  (  ( (nargs) == 1 ) && ( object ) && ( Py_None != (object) ) && ( !PyErr_Occurred() ) )   \
    ||  (  ( (nargs) >  1 ) && ( object ) && ( PyTuple_Check((object)) ) &&                       \
                 ( (nargs) == PyTuple_GET_SIZE((object)) ) )                        \
 )                                                                                \
 ?                                                                                \
    GSL_SUCCESS                                                                     \
 :                                                                                \
   PyGSL_check_python_return((object), (nargs), (info))

PyGSL_API_EXTERN int
PyGSL_check_python_return(PyObject *object, int nargs, PyGSL_error_info  *info)
{
     int tuple_size, flag=-1;
     char *msg;

     FUNC_MESS_BEGIN();

     assert(info);

     if(object == NULL && PyErr_Occurred()){
	  /*
	   * Error was apparently raised by the function, so lets just add a
	   * traceback frame ....
	   */
	  info->error_description = "User function raised exception!";
	  PyGSL_add_traceback(NULL, "Unknown file", info->message, __LINE__);
	  return GSL_EBADFUNC;
     }
     if(PyErr_Occurred()){
	  info->error_description = "Function raised an exception.";
	  PyGSL_add_traceback(NULL, "Unknown file", info->message, __LINE__);
	  return GSL_EBADFUNC;
	  /* return PyGSL_set_error_string_for_callback(info); */
     }

     /* Expected No argumets */
     if(nargs == 0){
	  if(object != Py_None){
	       info->error_description = "I expected 0 arguments, but I got an object different from None.";
	       return PyGSL_set_error_string_for_callback(info);
	  } else {
	       return GSL_SUCCESS;
	  }
     }

     if(nargs == 1){
	  if(object == Py_None){
	       info->error_description = "Expected 1 argument, but None was returned. This value is not acceptable for"
		    " the following arithmetic calculations.";
	       return PyGSL_set_error_string_for_callback(info);
	  } else {
	       return GSL_SUCCESS;
	  }
     }

     if(nargs > 1){
	  msg = (char *) malloc(256 * sizeof(char));

	  if(object == Py_None){
	       snprintf(msg, 256, "I expected %d arguments, but the function returned None!", nargs);
	       info->error_description = msg;
	       flag = PyGSL_set_error_string_for_callback(info);

	  } else if(!PyTuple_Check(object)){
	       snprintf(msg, 256, "Expected %d arguments, but I didn't get a tuple! "
			"Did you just return one argument?.", nargs);
	       info->error_description = msg;
	       flag = PyGSL_set_error_string_for_callback(info);

	  } else {
	       tuple_size = PyTuple_GET_SIZE(object);
	       if(tuple_size != nargs){
		    snprintf(msg, 256, "I expected %d arguments, but the function returned %d arguments! ",
			     nargs, tuple_size);
	       info->error_description = msg;
	       flag = PyGSL_set_error_string_for_callback(info);

	       } else {
		    flag = GSL_SUCCESS;
	       }
	  }
	  free(msg);
     }
     FUNC_MESS_END();
     return flag;
}

PyGSL_API_EXTERN void
PyGSL_clear_name(char *name, int size)
{
     int j;
     for(j = 0; j<size; j++){
	  if(name[j] == '-')
	       name[j] = '_';
     }
}


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2013.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// --------------------------------------------------------------------------
// $Maintainer: Lars Nilse $
// $Authors: Steffen Sass, Holger Plattfaut, Bastian Blank $
// --------------------------------------------------------------------------

#ifndef OPENMS_FILTERING_DATAREDUCTION_SILACFILTER_H
#define OPENMS_FILTERING_DATAREDUCTION_SILACFILTER_H

#include <OpenMS/KERNEL/StandardTypes.h>
#include <OpenMS/FILTERING/DATAREDUCTION/SILACFiltering.h>
#include <OpenMS/FILTERING/DATAREDUCTION/SILACPattern.h>
#include <OpenMS/FILTERING/DATAREDUCTION/IsotopeDistributionCache.h>
#include <gsl/gsl_interp.h>
#include <gsl/gsl_spline.h>
#include <queue>
#include <list>

namespace OpenMS
{
  /**
   * @brief Filter to use for SILACFiltering
   *
   * A SILACFilter searches for SILAC patterns, which correspond to the defined mass shifts and charge.
   * Only peaks are taken into account, which were not blacklisted by other filters before e.g. are not part of
   * a SILAC pair yet.
   *
   * @see SILACFiltering
   */
  class OPENMS_DLLAPI SILACFilter
  {
private:
    friend class SILACFiltering;

    typedef IsotopeDistributionCache::TheoreticalIsotopePattern TheoreticalIsotopePattern;

    /**
     * @brief mass shift(s) in [Da] to search for
     */
    std::vector<DoubleReal> mass_separations_;

    /**
     * @brief charge of the ions to search for
     */
    Int charge_;

    /**
     * @brief maximal value of which a predicted SILAC feature may deviate from the averagine model
     */
    DoubleReal model_deviation_;

    /**
     * @brief number of peaks per peptide to search for
     */
    Size isotopes_per_peptide_;

    /**
     * @brief minimal intensity of SILAC features
     */
    DoubleReal intensity_cutoff_;

    /**
     * @brief minimal intensity correlation between regions of different peaks
     */
    DoubleReal intensity_correlation_;

    /**
     * @brief flag for missing peaks
     */
    bool allow_missing_peaks_;

    /**
     * Isotope distributions
     */
    static IsotopeDistributionCache * isotope_distribution_;

    /**
     * @brief number of peptides [i.e. number of labelled peptides +1, e.g. for SILAC triplet =3]
     */
    Size number_of_peptides_;

    /**
     * @brief peak positions of SILAC pattern
     */
    std::vector<DoubleReal> peak_positions_;

    /**
     * @brief m/z separation between individual peptides [e.g. {0 Th, 4 Th, 5 Th}]
     */
    std::vector<DoubleReal> mz_peptide_separations_;

    /**
     * @brief m/z shifts relative to mono-isotopic peak of unlabelled peptide
     */
    std::vector<DoubleReal> expected_mz_shifts_;

    /**
     * @brief distance between isotopic peaks of a peptide in [Th]
     */
    DoubleReal isotope_distance_;

    /**
     * @brief holds the recognized features
     */
    std::vector<SILACPattern> elements_;

    /**
     * @brief m/z at which the filter is currently applied to
     */
    DoubleReal current_mz_;

    /**
     * @brief exact m/z shift of isotopic peaks in a SILAC pattern relative to the mono-isotopic peak of the light peptide, peptides (row) x isotope (column)
     */
    std::vector<std::vector<DoubleReal> > exact_shifts_;

    /**
     * @brief m/z positions mz + exact_shifts in a SILAC pattern, where mz is the m/z of the mono-isotopic peak of light peptide
     */
    std::vector<std::vector<DoubleReal> > exact_mz_positions_;

    /**
     * @brief intensities at mz + exact_shifts in a SILAC pattern, where mz is the m/z of the mono-isotopic peak of light peptide
     */
    std::vector<std::vector<DoubleReal> > exact_intensities_;

    /**
     * @brief expected m/z shift of isotopic peaks in a SILAC pattern relative to the mono-isotopic peak of the light peptide, peptides (row) x isotope (column)
     */
    std::vector<std::vector<DoubleReal> > expected_shifts_;

    /**
     * @brief Checks if there exists a SILAC feature at the given position in the raw (interpolated) data, which corresponds to the filter's properties
     * @param rt RT value of the position
     * @param mz m/z value of the position
     */
    bool isSILACPattern_(const MSSpectrum<Peak1D> &, const SILACFiltering::SpectrumInterpolation &, DoubleReal mz, DoubleReal picked_mz, const SILACFiltering &, MSSpectrum<Peak1D> & debug, SILACPattern & pattern);

    /**
     * @brief Checks if there exists a SILAC feature at the given position in the picked data
     */
    bool isSILACPatternPicked_(const MSSpectrum<Peak1D> &, DoubleReal mz, const SILACFiltering &, MSSpectrum<Peak1D> & debug);

    /**
     * @brief Extracts mass shifts and intensities from the raw (interpolated) data
     */
    bool extractMzShiftsAndIntensities_(const MSSpectrum<Peak1D> &, const SILACFiltering::SpectrumInterpolation &, DoubleReal mz, DoubleReal picked_mz, const SILACFiltering &);

    /**
     * @brief Extracts mass shifts and intensities from the picked data
     */
    bool extractMzShiftsAndIntensitiesPicked_(const MSSpectrum<Peak1D> &, DoubleReal mz, const SILACFiltering &);

    /**
     * @brief Extracts mass shifts and intensities from the picked data and returns pattern information
     */
    bool extractMzShiftsAndIntensitiesPickedToPattern_(const MSSpectrum<Peak1D> &, DoubleReal mz, const SILACFiltering &, SILACPattern & pattern);

    /**
     * @brief Checks all peaks against intensity cutoff
     */
    bool intensityFilter_();

    /**
     * @brief Checks peak form correlation between peaks of one isotope
     */
    bool correlationFilter1_(const SILACFiltering::SpectrumInterpolation &, DoubleReal mz, const SILACFiltering &);

    /**
     * @brief Checks peak form correlation between peaks of different isotopes
     */
    bool correlationFilter2_(const SILACFiltering::SpectrumInterpolation &, DoubleReal mz, const SILACFiltering &);

    /**
     * @brief Checks peak intensities against the averagine model
     */
    bool averageneFilter_(DoubleReal mz);

public:
    /**
     * @brief detailed constructor for SILAC pair filtering
     * @param mass_separations all mass shifts of the filter
     * @param charge charge of the ions to search for
     * @param model_deviation maximum deviation from the averagine model
     * @param isotopes_per_peptide number of peaks per peptide to search for
     * @param intensity_cutoff ...
     * @param intensity_correlation minimal intensity correlation between regions of different peaks
     * @param allow_missing_peaks flag for missing peaks
     */
    SILACFilter(std::vector<DoubleReal> mass_separations, Int charge, DoubleReal model_deviation, Int isotopes_per_peptide,
                DoubleReal intensity_cutoff, DoubleReal intensity_correlation, bool allow_missing_peaks);

    /**
     * @brief gets the m/z values of all peaks , which belong the last identified feature
     */
    std::vector<DoubleReal> getPeakPositions();

    /**
     * @brief gets the m/z shifts relative to mono-isotopic peak of unlabelled peptide
     */
    const std::vector<DoubleReal> & getExpectedMzShifts();

    /**
     * @brief returns all identified elements
     */
    std::vector<SILACPattern> & getElements();

    /**
     * @brief returns the charge of the filter
     */
    Int getCharge();

    /**
     * @brief returns the mass shifts of the filter in [Da]
     */
    std::vector<DoubleReal> & getMassSeparations();
  };
}

#endif /* SILACFILTER_H_ */


#pragma once

#include <gsl\gsl>
#include <winrt\Windows.Foundation.h>
#include <d3d11.h>
#include <array>
#include "DrawableGameComponent.h"
#include "MatrixHelper.h"
#include "PointLight.h"

namespace Library
{
	class PointLight;
	class ProxyModel;
}

namespace Rendering
{
	class MultiplePointLightsMaterial;

	class MultiplePointLightsDemo final : public Library::DrawableGameComponent
	{
	public:
		MultiplePointLightsDemo(Library::Game& game, const std::shared_ptr<Library::Camera>& camera);
		MultiplePointLightsDemo(const MultiplePointLightsDemo&) = delete;
		MultiplePointLightsDemo(MultiplePointLightsDemo&&) = default;
		MultiplePointLightsDemo& operator=(const MultiplePointLightsDemo&) = default;
		MultiplePointLightsDemo& operator=(MultiplePointLightsDemo&&) = default;
		~MultiplePointLightsDemo();

		bool AnimationEnabled() const;
		void SetAnimationEnabled(bool enabled);
		void ToggleAnimation();

		float AmbientLightIntensity() const;
		void SetAmbientLightIntensity(float intensity);

		const std::array<Library::PointLight, 4>& PointLights() const;
		void SetPointLight(const Library::PointLight& light, size_t index);

		const size_t SelectedLightIndex() const;
		const Library::PointLight& SelectedLight() const;
		void UpdateSelectedLight(const Library::PointLight& light);
		void SelectLight(size_t index);

		float SpecularPower() const;
		void SetSpecularPower(float power);

		virtual void Initialize() override;
		virtual void Update(const Library::GameTime& gameTime) override;
		virtual void Draw(const Library::GameTime& gameTime) override;

	private:
		inline static const float RotationRate{ DirectX::XM_PI };

		std::shared_ptr<MultiplePointLightsMaterial> mMaterial;
		DirectX::XMFLOAT4X4 mWorldMatrix{ Library::MatrixHelper::Identity };
		winrt::com_ptr<ID3D11Buffer> mVertexBuffer;
		winrt::com_ptr<ID3D11Buffer> mIndexBuffer;
		std::uint32_t mIndexCount{ 0 };
		std::array<std::unique_ptr<Library::ProxyModel>, 4> mProxyModels;
		size_t mSelectedLightIndex{ 0 };
		float mModelRotationAngle{ 0.0f };
		bool mAnimationEnabled{ true };
		bool mUpdateMaterial{ true };
	};
}


//          Copyright Jean Pierre Cimalando 2019.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE.md or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#pragma once
#include "player/instrument.h"
#include "synth/synth.h"
#include <fmidi/fmidi.h>
#include <gsl/gsl>
#include <memory>

class Midi_Synth_Instrument : public Midi_Instrument {
public:
    Midi_Synth_Instrument();
    ~Midi_Synth_Instrument();

    void flush_events() override;

    void open_midi_output(gsl::cstring_span id) override;
    void close_midi_output() override;

    bool is_synth() const override { return true; }

    void configure_audio(double audio_rate, double audio_latency);
    void generate_audio(float *output, unsigned nframes);

    void preload(const fmidi_smf_t &smf);

protected:
    void handle_send_message(const uint8_t *data, unsigned len, double ts, uint8_t flags) override;

private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
};


#include <string.h>

#include <mpi.h>
#include <pfft.h>
#include <gsl/gsl_rng.h>

#include <fastpm/libfastpm.h>
#include <fastpm/logging.h>
#include "pmpfft.h"

#define HAS(a, b) ((a & b) != 0)

static void
pack_any(FastPMStore * p, ptrdiff_t index, int ci, void * packed)
{
    size_t elsize = p->_column_info[ci].elsize;
    memcpy(packed, p->columns[ci] + index * elsize, elsize);
}

static void
unpack_any(FastPMStore * p, ptrdiff_t index, int ci, void * packed)
{
    size_t elsize = p->_column_info[ci].elsize;
    memcpy(p->columns[ci] + index * elsize, packed, elsize);
}

void
FastPMReduceOverwriteAny(FastPMStore * p, ptrdiff_t index, int ci, void * packed, void * userdata)
{
    size_t elsize = p->_column_info[ci].elsize;

    memcpy(p->columns[ci] + index * elsize, packed, elsize);
}

void
FastPMReduceAddFloat(FastPMStore * src, ptrdiff_t isrc, FastPMStore * dest, ptrdiff_t idest, int ci, void * userdata)
{
    size_t nmemb = dest->_column_info[ci].nmemb ;

    size_t elsize = dest->_column_info[ci].elsize;

    float * pdest = (float*) (dest->columns[ci] + idest * elsize);
    float * psrc  = (float*) (src ->columns[ci] + isrc * elsize);

    int d;
    for(d = 0; d < nmemb; d ++) {
        pdest[d] += psrc[d];
    }
}

static double
to_double_f4 (FastPMStore * p, ptrdiff_t index, int ci, int memb)
{
    size_t nmemb = p->_column_info[ci].nmemb ;
    if(memb > nmemb) {
        fastpm_raise(-1, "memb %d greater than nmemb %d", memb, nmemb);
    }
    size_t elsize = p->_column_info[ci].elsize;

    float * ptr = (float*) (p->columns[ci] + index * elsize);

    return ptr[memb];
}

static double
to_double_f8 (FastPMStore * p, ptrdiff_t index, int ci, int memb)
{
    size_t nmemb = p->_column_info[ci].nmemb ;
    if(memb > nmemb) {
        fastpm_raise(-1, "memb %d greater than nmemb %d", memb, nmemb);
    }
    size_t elsize = p->_column_info[ci].elsize;

    double * ptr = (double*) (p->columns[ci] + index * elsize);

    return ptr[memb];
}

static void
from_double_f4 (FastPMStore * p, ptrdiff_t index, int ci, int memb, const double value)
{
    size_t nmemb = p->_column_info[ci].nmemb ;
    if(memb > nmemb) {
        fastpm_raise(-1, "memb %d greater than nmemb %d", memb, nmemb);
    }
    size_t elsize = p->_column_info[ci].elsize;

    float * ptr = (float*) (p->columns[ci] + index * elsize);

    ptr[memb] = value;
}

const char *
fastpm_species_get_name(enum FastPMSpecies species)
{
    switch(species) {
        case FASTPM_SPECIES_BARYON:
            return "0";
        case FASTPM_SPECIES_CDM:
            return "1";
        case FASTPM_SPECIES_NCDM:
            return "2";
    }
    return "UNKNOWN";
}
void fastpm_store_get_position(FastPMStore * p, ptrdiff_t index, double pos[3])
{
    pos[0] = p->x[index][0];
    pos[1] = p->x[index][1];
    pos[2] = p->x[index][2];
}

void fastpm_store_get_lagrangian_position(FastPMStore * p, ptrdiff_t index, double pos[3])
{
    pos[0] = p->q[index][0];
    pos[1] = p->q[index][1];
    pos[2] = p->q[index][2];
}

double fastpm_store_get_mass(FastPMStore * p, ptrdiff_t index)
{
    /* total mass is the sum of the base and the extra */
    if(p->mass) {
        return p->meta.M0 + p->mass[index];
    } else {
        return p->meta.M0;
    }
}

static ptrdiff_t
_alignsize(ptrdiff_t size)
{
    /* align sizes */
    return ((size + 1024) / 1024) * 1024;
}

void
fastpm_store_init_details(FastPMStore * p,
                  const char * name,
                  size_t np_upper,
                  FastPMColumnTags attributes,
                  enum FastPMMemoryLocation loc,
                  const char * file,
                  const int line)
{
    p->mem = _libfastpm_get_gmem();

    /* only set the name if name is not NULL; this is to allow setting the name before calling init.
     * */
    if(name) {
        strcpy(p->name, name);
    }

    p->attributes = attributes;

    p->np = 0;
    p->np_upper = np_upper;

    /* clear the column pointers. */
    memset(p->columns, 0, sizeof(p->columns));
    memset(p->_column_info, 0, sizeof(p->_column_info));

    /* clear the meta */
    memset(&p->meta, 0, sizeof(p->meta));
    int it;
    p->_base = NULL;

    /* first loop initialize the _column_info struct for corresponding items in the pointer union;
     * second loop initialize the pointers */
    #define DEFINE_COLUMN(column, attr_, dtype_, nmemb_) \
        { \
            int ci = FASTPM_STORE_COLUMN_INDEX(column); \
            if(attr_ != (1 << ci)) { fastpm_raise(-1, "attr and column are out of order for %s\n", # column ); } \
            strcpy(p->_column_info[ci].dtype, dtype_);  \
            strcpy(p->_column_info[ci].name, # column); \
            p->_column_info[ci].elsize = sizeof(p->column[0]);  \
            p->_column_info[ci].nmemb = nmemb_;  \
            p->_column_info[ci].membsize = sizeof(p->column[0]) / nmemb_;  \
            p->_column_info[ci].attribute = attr_;  \
            p->_column_info[ci].pack = pack_any;  \
            p->_column_info[ci].unpack = unpack_any;  \
            p->_column_info[ci].from_double = NULL;  \
            p->_column_info[ci].to_double = NULL;  \
        }

    #define COLUMN_INFO(column) (p->_column_info[FASTPM_STORE_COLUMN_INDEX(column)])

    DEFINE_COLUMN(x, COLUMN_POS, "f8", 3);
    DEFINE_COLUMN(q, COLUMN_Q, "f4", 3);
    DEFINE_COLUMN(v, COLUMN_VEL, "f4", 3);
    DEFINE_COLUMN(acc, COLUMN_ACC, "f4", 3);
    DEFINE_COLUMN(dx1, COLUMN_DX1, "f4", 3);
    DEFINE_COLUMN(dx2, COLUMN_DX2, "f4", 3);
    DEFINE_COLUMN(dv1, COLUMN_DV1, "f4", 3);
    DEFINE_COLUMN(aemit, COLUMN_AEMIT, "f4", 1);
    DEFINE_COLUMN(rho, COLUMN_DENSITY, "f4", 1);
    DEFINE_COLUMN(potential, COLUMN_POTENTIAL, "f4", 1);
    DEFINE_COLUMN(tidal, COLUMN_TIDAL, "f4", 6);
    DEFINE_COLUMN(id, COLUMN_ID, "i8", 1);
    DEFINE_COLUMN(pgdc, COLUMN_PGDC, "f4", 3);
    DEFINE_COLUMN(mask, COLUMN_MASK, "i1", 1);
    DEFINE_COLUMN(minid, COLUMN_MINID, "i8", 1);
    DEFINE_COLUMN(task, COLUMN_TASK, "i4", 1);
    DEFINE_COLUMN(length, COLUMN_LENGTH, "i4", 1);
    DEFINE_COLUMN(rdisp, COLUMN_RDISP, "f4", 6);
    DEFINE_COLUMN(vdisp, COLUMN_VDISP, "f4", 6);
    DEFINE_COLUMN(rvdisp, COLUMN_RVDISP, "f4", 9);
    DEFINE_COLUMN(mass, COLUMN_MASS, "f4", 1);

    COLUMN_INFO(x).to_double = to_double_f8;
    COLUMN_INFO(v).to_double = to_double_f4;
    COLUMN_INFO(rho).to_double = to_double_f4;
    COLUMN_INFO(dx1).to_double = to_double_f4;
    COLUMN_INFO(dx2).to_double = to_double_f4;
    COLUMN_INFO(dv1).to_double = to_double_f4;
    COLUMN_INFO(acc).to_double = to_double_f4;
    COLUMN_INFO(mass).to_double = to_double_f4;

    COLUMN_INFO(rho).from_double = from_double_f4;
    COLUMN_INFO(acc).from_double = from_double_f4;
    COLUMN_INFO(pgdc).from_double = from_double_f4;
    COLUMN_INFO(dx1).from_double = from_double_f4;
    COLUMN_INFO(dx2).from_double = from_double_f4;
    COLUMN_INFO(dv1).from_double = from_double_f4;
    COLUMN_INFO(potential).from_double = from_double_f4;
    COLUMN_INFO(tidal).from_double = from_double_f4;

    ptrdiff_t size = 0;
    ptrdiff_t offset = 0;
    for(it = 0; it < 2; it ++) {
        int ci;
        for(ci = 0; ci < 32; ci ++) {
            if(it == 0) {
                size += ((attributes & p->_column_info[ci].attribute) != 0) * (_alignsize(p->_column_info[ci].elsize * np_upper)); \
            } else { \
                if(attributes & p->_column_info[ci].attribute) { \
                    p->columns[ci] = (void*) (((char*) p->_base) + offset); \
                    offset += _alignsize(p->_column_info[ci].elsize * np_upper); \
                } else { \
                    p->columns[ci] = NULL; \
                } \
            }

        }
        if(it == 0) {
            p->_base = fastpm_memory_alloc_details(p->mem, "FastPMStore", size, loc, file, line);
            /* zero out all memory */
            memset(p->_base, 0, size);
        }
    };
}

void
fastpm_store_set_name(FastPMStore * p, const char * name)
{
    strcpy(p->name, name);
}

size_t
fastpm_store_init_evenly_details(FastPMStore * p,
    const char * name, size_t np_total, FastPMColumnTags attributes, double alloc_factor, MPI_Comm comm,
    const char * file,
    const int line)
{
    /* allocate for np_total cross all */
    /* name means name of species*/
    int NTask;
    MPI_Comm_size(comm, &NTask);

    size_t np_upper = (size_t)(1.0 * np_total / NTask * alloc_factor);

    MPI_Bcast(&np_upper, 1, MPI_LONG, 0, comm);
    fastpm_store_init_details(p, name, np_upper, attributes, FASTPM_MEMORY_HEAP, file, line);
    return 0;
}

size_t
fastpm_store_get_np_total(FastPMStore * p, MPI_Comm comm)
{
    long long np = p->np;
    MPI_Allreduce(MPI_IN_PLACE, &np, 1, MPI_LONG_LONG, MPI_SUM, comm);
    return np;
}

size_t
fastpm_store_get_mask_sum(FastPMStore * p, MPI_Comm comm)
{
    long long np = 0;
    ptrdiff_t i;
    for(i = 0; i < p->np; i ++) {
        np += p->mask[i] != 0;
    }
    MPI_Allreduce(MPI_IN_PLACE, &np, 1, MPI_LONG_LONG, MPI_SUM, comm);
    return np;
}

void
fastpm_packing_plan_init(FastPMPackingPlan * plan, FastPMStore * p, FastPMColumnTags attributes)
{
    int ci;
    int i = 0;
    plan->elsize = 0;
    plan->attributes = attributes;
    for (ci = 0; ci < 32; ci ++) {
        if (!(p->_column_info[ci].attribute & attributes)) continue;
        plan->_ci[i] = ci;
        plan->_offsets[ci] = plan->elsize;
        ptrdiff_t elsize = p->_column_info[ci].elsize;
        plan->elsize += elsize;
        plan->_column_info[ci] = p->_column_info[ci];
        i++;
    }
    /* Pad the elsize to 8 bytes. This ensures anything goes to the MPI wire is 8 byte aligned,
     * and minimizes the chances of hitting an implementation bug. */
    if (plan->elsize % 8 != 0) {
        plan->elsize += (8 - plan->elsize % 8);
    }
    plan->Ncolumns = i;
}

void
fastpm_packing_plan_pack(FastPMPackingPlan * plan,
            FastPMStore * p, ptrdiff_t i, void * packed)
{
    int t;
    memset(packed, 0, plan->elsize);
    for (t = 0; t < plan->Ncolumns; t ++) {
        int ci = plan->_ci[t];
        ptrdiff_t offset = plan->_offsets[ci];
        plan->_column_info[ci].pack(p, i, ci,
            ((char*) packed) + offset);
    }
}

void
fastpm_packing_plan_unpack(FastPMPackingPlan * plan,
            FastPMStore * p, ptrdiff_t i, void * packed)
{
    int t;
    for (t = 0; t < plan->Ncolumns; t ++) {
        int ci = plan->_ci[t];
        fastpm_packing_plan_unpack_ci(plan, ci, p, i, packed);
    }
}

/* unpack a single column from the offset in packed data. */
void
fastpm_packing_plan_unpack_ci(FastPMPackingPlan * plan, int ci,
            FastPMStore * p, ptrdiff_t i, void * packed)
{
    ptrdiff_t offset = plan->_offsets[ci];
    plan->_column_info[ci].unpack(p, i, ci,
        ((char*) packed) + offset);
}

int
fastpm_store_find_column_id(FastPMStore * p, FastPMColumnTags attribute)
{
    int ci;
    for (ci = 0; ci < 32; ci ++) {
        if (p->_column_info[ci].attribute == attribute) {
            return ci;
        }
    }
    fastpm_raise(-1, "Unknown column %x", attribute);
    return -1;
}

void
fastpm_store_destroy(FastPMStore * p)
{
    fastpm_memory_free(p->mem, p->_base);
}

static void permute(void * data, int np, size_t elsize, int * ind) {
    void * tmp = malloc(elsize * np);
    if(!tmp) {
        fastpm_raise(-1, "No memory for permuting\n");
    }
    int i;
    for(i = 0; i < np; i ++) {
        memcpy(((char*) tmp) + i * elsize, ((char*) data) + ind[i] * elsize, elsize);
    }
    memcpy(data, tmp, np * elsize);
    free(tmp);
}

void fastpm_store_permute(FastPMStore * p, int * ind)
{
    int c;
    for(c = 0; c < 32; c ++) {
        if(!p->columns[c]) continue;
        permute(p->columns[c], p->np, p->_column_info[c].elsize, ind);
    }
}

static FastPMStore *  _fastpm_store_sort_store;
static int (*_fastpm_store_sort_cmp_func)(const int i1, const int i2, FastPMStore * p);

int _sort_by_id_cmpfunc(const void * p1, const void * p2)
{
    const int * i1 = (const int*) p1;
    const int * i2 = (const int*) p2;

    return _fastpm_store_sort_cmp_func(*i1, *i2, _fastpm_store_sort_store);
}

int
FastPMLocalSortByID(const int i1,
                    const int i2,
                    FastPMStore * p)
{
    int v1 = (p->id[i1] < p->id[i2]);
    int v2 = (p->id[i1] > p->id[i2]);

    return v2 - v1;
}

/* sort a store locally with in the MPI rank.
 *
 * cmp_func is called with three arguments.
 * */
void
fastpm_store_sort(FastPMStore * p,
        int (*cmp_func)(const int i1, const int i2, FastPMStore * p))
{
    int * arg = fastpm_memory_alloc(p->mem, "Temp", sizeof(int) * p->np, FASTPM_MEMORY_HEAP);
    int i;
    for(i = 0; i < p->np; i ++) {
        arg[i] = i;
    }
    /* FIXME: copy some version of qsort_r */
    _fastpm_store_sort_store = p;
    _fastpm_store_sort_cmp_func = cmp_func;
    qsort(arg, p->np, sizeof(arg[0]), _sort_by_id_cmpfunc);
    fastpm_store_permute(p, arg);
    fastpm_memory_free(p->mem, arg);
}

void
fastpm_store_wrap(FastPMStore * p, double BoxSize[3])
{
    int i;
    int d;
    for(i = 0; i < p->np; i ++) {
        for(d = 0; d < 3; d ++) {
            double n = abs(p->x[i][d] / BoxSize[d]);

            double x1 = remainder(p->x[i][d], BoxSize[d]);

            while(x1 < 0) x1 += BoxSize[d];
            while(x1 > BoxSize[d]) x1 -= BoxSize[d];
            p->x[i][d] = x1;

            if(n > 10000) {
                double q[3];
                if(fastpm_store_has_q(p)) {
                    fastpm_store_get_q_from_id(p, p->id[i], q);
                }
                fastpm_raise(-1, "Particle at %g %g %g (q = %g %g %g) is too far from the bounds. Wrapping failed.\n",
                        p->x[i][0],
                        p->x[i][1],
                        p->x[i][2],
                        q[0], q[1], q[2]
                );
            }
        }
    }
}

int
FastPMTargetPM (FastPMStore * p, ptrdiff_t i, PM * pm)
{
    double pos[3];
    fastpm_store_get_position(p, i, pos);
    return pm_pos_to_rank(pm, pos);
}

int
fastpm_store_decompose(FastPMStore * p,
    fastpm_store_target_func target_func,
    void * data, MPI_Comm comm)
{
    if(fastpm_store_get_np_total(p, comm) == 0) return 0 ;

    VALGRIND_CHECK_MEM_IS_DEFINED(p->x, sizeof(p->x[0]) * p->np);

    FastPMPackingPlan plan[1];

    fastpm_packing_plan_init(plan, p, p->attributes);

    size_t elsize = plan->elsize;

    size_t Nsend_limit = 1000 * 1024 * 1024 / elsize;     // this large number effectively prevents throttling

    int NTask, ThisTask;

    MPI_Comm_rank(comm, &ThisTask);
    MPI_Comm_size(comm, &NTask);

    if(p->np > p->np_upper) {
        fastpm_raise(-1, "Particle buffer overrun detected np = %td > np_upper %td.\n", p->np, p->np_upper);
    }

    /* do a bincount; offset by -1 because -1 is for self */
    int * count = calloc(NTask + 1, sizeof(int));
    int * offsets = calloc(NTask + 1, sizeof(int));
    int * sendcount = count + 1;
    int * recvcount = malloc(sizeof(int) * (NTask));
    int * recvoffset = malloc(sizeof(int) * (NTask));
    int * sendoffset = malloc(sizeof(int) * (NTask));

    int incomplete = 1;
    int iter = 0;
    /* terminate based on incomplete for throttling*/
    while(1) {
        incomplete = 0;
        int * target = fastpm_memory_alloc(p->mem, "Target", sizeof(int) * p->np, FASTPM_MEMORY_HEAP);

        size_t Nsend_all = 0;
        ptrdiff_t i;
        for(i = 0; i < p->np; i ++) {
            target[i] = target_func(p, i, data);
            if(ThisTask == target[i]) {
                target[i] = -1;
            } else {
                Nsend_all++;
                /* Throttling: never send more than this many particles. */
                if(Nsend_all >= Nsend_limit) {
                    incomplete = 1;
                    target[i] = -1;
                } else {
                }
            }
        }

        for(i = 0; i < NTask + 1; i ++) {
            count[i] = 0;
            offsets[i] = 0;
        }
        for(i = 0; i < NTask; i ++) {
            sendoffset[i] = 0;
            recvoffset[i] = 0;
        }

        for(i = 0; i < p->np; i ++) {
            sendcount[target[i]] ++;
        }
        cumsum(offsets, count, NTask + 1);

        int * arg = fastpm_memory_alloc(p->mem, "PermArg", sizeof(int) * p->np, FASTPM_MEMORY_HEAP);
        for(i = 0; i < p->np; i ++) {
            int offset = offsets[target[i] + 1] ++;
            arg[offset] = i;
        }

        fastpm_store_permute(p, arg);

        VALGRIND_CHECK_MEM_IS_DEFINED(p->x, sizeof(p->x[0]) * p->np);

        fastpm_memory_free(p->mem, arg);
        fastpm_memory_free(p->mem, target);

        MPI_Alltoall(sendcount, 1, MPI_INT,
                     recvcount, 1, MPI_INT,
                     comm);

        size_t Nsend = cumsum(sendoffset, sendcount, NTask);
        size_t Nrecv = cumsum(recvoffset, recvcount, NTask);

        volatile size_t neededsize = p->np + Nrecv - Nsend;

        if(neededsize > p->np_upper) {
            fastpm_ilog(INFO, "Need %td particles on rank %d; %td allocated\n", neededsize, ThisTask, p->np_upper);
        }

        if(MPIU_Any(comm, neededsize > p->np_upper)) {
            goto fail_oom;
        }

        p->np -= Nsend;

        void * send_buffer = fastpm_memory_alloc(p->mem, "SendBuf", elsize * Nsend, FASTPM_MEMORY_HEAP);
        void * recv_buffer = fastpm_memory_alloc(p->mem, "RecvBuf", elsize * Nrecv, FASTPM_MEMORY_HEAP);

        {
            double nmin, nmax, nmean, nstd;

            MPIU_stats(comm, elsize * Nsend, "<>-s", &nmin, &nmax, &nmean, &nstd);
            fastpm_info("Send buffer size : min=%g max=%g mean=%g, std=%g bytes", nmin, nmax, nmean, nstd);
            MPIU_stats(comm, elsize * Nrecv, "<>-s", &nmin, &nmax, &nmean, &nstd);
            fastpm_info("Recv buffer size : min=%g max=%g mean=%g, std=%g bytes", nmin, nmax, nmean, nstd);
        }

        int ipar = 0;
        int j;
        for(i = 0; i < NTask; i ++) {
            for(j = sendoffset[i]; j < sendoffset[i] + sendcount[i]; j ++, ipar++) {
                fastpm_packing_plan_pack(plan, p, j + p->np, (char*) send_buffer + ipar * elsize);
            }
        }

        size_t Nsendsum;
        size_t Nsendallsum;
        MPI_Allreduce(&Nsend, &Nsendsum, 1, MPI_LONG, MPI_SUM, comm);
        MPI_Allreduce(&Nsend_all, &Nsendallsum, 1, MPI_LONG, MPI_SUM, comm);
        fastpm_info("Decomposition iter %d,  exchange of %td particles; need %td", iter, Nsendsum, Nsendallsum);

        MPI_Datatype PTYPE;
        MPI_Type_contiguous(elsize, MPI_BYTE, &PTYPE);
        MPI_Type_commit(&PTYPE);

        MPI_Alltoallv_sparse(
                send_buffer, sendcount, sendoffset, PTYPE,
                recv_buffer, recvcount, recvoffset, PTYPE,
                comm);

        MPI_Type_free(&PTYPE);

        ipar = 0;
        for(i = 0; i < NTask; i ++) {
            for(j = recvoffset[i]; j < recvoffset[i] + recvcount[i]; j++, ipar ++) {
                fastpm_packing_plan_unpack(plan, p, j + p->np, (char*) recv_buffer + ipar * elsize);
            }
        }

        fastpm_memory_free(p->mem, recv_buffer);
        fastpm_memory_free(p->mem, send_buffer);

        p->np += Nrecv;
        iter++;
        if(MPIU_Any(comm, incomplete)) continue;
        else break;
    }
    free(recvcount);
    free(recvoffset);
    free(sendoffset);
    free(offsets);
    free(count);

    return 0;

    fail_oom:
        free(recvcount);
        free(recvoffset);
        free(sendoffset);
        free(offsets);
        free(count);
    return -1;
}

int fastpm_store_has_q(FastPMStore * p)
{
    return p->meta._q_size != 0;
}

void
fastpm_store_get_q_from_id(FastPMStore * p, uint64_t id, double q[3])
{
    ptrdiff_t pabs[3];

    int d;
    id = id % p->meta._q_size;
    for(d = 0; d < 3; d++) {
        pabs[d] = id / p->meta._q_strides[d];
        id -= pabs[d] * p->meta._q_strides[d];
    }

    for(d = 0; d < 3; d++) {
        q[d] = pabs[d] * p->meta._q_scale[d];
        q[d] += p->meta._q_shift[d];
    }
}//if all 0 drop

void
fastpm_store_get_iq_from_id(FastPMStore * p, uint64_t id, ptrdiff_t pabs[3])
{
    /* integer version of the initial position q
    i.e. the lattice coordinates of the cells. */
    int d;
    for(d = 0; d < 3; d++) {
        pabs[d] = id / p->meta._q_strides[d];
        id -= pabs[d] * p->meta._q_strides[d];
    }
}

void
fastpm_store_fill(FastPMStore * p, PM * pm, double * shift, ptrdiff_t * Nc)
{
    /* fill p with a uniform grid, respecting domain given by pm. use a subsample ratio.
     * (every subsample grid points) */
    if(Nc == NULL) {
        Nc = pm_nmesh(pm);
    }
    int d;
    p->np = 1;
    for(d = 0; d < 3; d++) {
        int start = pm->IRegion.start[d] * Nc[d] / pm->Nmesh[d];
        int end = (pm->IRegion.start[d] + pm->IRegion.size[d]) * Nc[d] / pm->Nmesh[d];
        p->np *= end - start;
    }
    if(p->np > p->np_upper) {
        fastpm_raise(-1, "Need %td particles; %td allocated\n", p->np, p->np_upper);
    }
    ptrdiff_t ptr = 0;

    for(d = 0; d < 3; d ++) {
        if(shift)
            p->meta._q_shift[d] = shift[d];
        else
            p->meta._q_shift[d] = 0;

        p->meta._q_scale[d] = pm->BoxSize[d] / Nc[d];
    }

    p->meta._q_size = Nc[0] * Nc[1] * Nc[2];
    p->meta._q_strides[0] = Nc[1] * Nc[2];
    p->meta._q_strides[1] = Nc[2];
    p->meta._q_strides[2] = 1;

    PMXIter iter;
    for(pm_xiter_init(pm, &iter);
       !pm_xiter_stop(&iter);
        pm_xiter_next(&iter)){
        ptrdiff_t pabs_start[3];
        ptrdiff_t pabs_end[3];
        ptrdiff_t ii, jj, kk;

        for(d = 0; d < 3; d ++) {
            pabs_start[d] = iter.iabs[d] * Nc[d] / pm->Nmesh[d];
            pabs_end[d] = (iter.iabs[d] + 1) * Nc[d] / pm->Nmesh[d];
        }
        for(ii = pabs_start[0]; ii < pabs_end[0]; ii ++)
        for(jj = pabs_start[1]; jj < pabs_end[1]; jj ++)
        for(kk = pabs_start[2]; kk < pabs_end[2]; kk ++) {
            ptrdiff_t pabs[3] = {ii, jj, kk};

            uint64_t id = pabs[2] * p->meta._q_strides[2] +
                          pabs[1] * p->meta._q_strides[1] +
                          pabs[0] * p->meta._q_strides[0] ;

            if(p->id) p->id[ptr] = id;
            if(p->mask) p->mask[ptr] = 0;

            fastpm_store_get_q_from_id(p, id, &p->x[ptr][0]);

            if(p->q) {
                /* set q if it is allocated. */
                for(d = 0; d < 3; d ++) {
                    p->q[ptr][d] = p->x[ptr][d];
                }
            }
            ptr ++;
        }
    }
    if(ptr != p->np) {
        fastpm_raise(-1, "This is an internal error, particle number mismatched with grid. %td != %td, allocsize=%td, shape=(%td %td %td)\n",
            ptr, p->np, pm->allocsize,
            pm->IRegion.size[0],
            pm->IRegion.size[1],
            pm->IRegion.size[2]
            );
    }
    p->meta.a_x = p->meta.a_v = 0.;
}

void
fastpm_store_summary(FastPMStore * p,
        FastPMColumnTags attribute,
        MPI_Comm comm,
        const char * fmt,
        ...)
{
    va_list va;
    va_start(va, fmt);

    int ci = fastpm_store_find_column_id(p, attribute);
    size_t nmemb = p->_column_info[ci].nmemb;

    double rmin[nmemb], rmax[nmemb], rsum1[nmemb], rsum2[nmemb];

    if (NULL == p->_column_info[ci].to_double) {
        fastpm_raise(-1, "Column %s didnot set to_double virtual function\n",
            p->_column_info[ci].name);
    }
    int d;
    for(d = 0; d < nmemb; d ++) {
        rmin[d] = 1e20;
        rmax[d] = -1e20;
        rsum1[d] = 0;
        rsum2[d] = 0;
    }
    ptrdiff_t i;

#pragma omp parallel
    {
        double tmin[nmemb], tmax[nmemb], tsum1[nmemb], tsum2[nmemb];
        int d;
        for(d = 0; d < nmemb; d ++) {
            tmin[d] = 1e20;
            tmax[d] = -1e20;
            tsum1[d] = 0;
            tsum2[d] = 0;
        }
        #pragma omp for
        for(i = 0; i < p->np; i ++) {
            int d;
            for(d =0; d < nmemb; d++) {
                double value = p->_column_info[ci].to_double(p, i, ci, d);
                tsum1[d] += value;
                tsum2[d] += value * value;
                tmin[d] = fmin(tmin[d], value);
                tmax[d] = fmax(tmax[d], value);
            }
        }
        #pragma omp critical
        {
            int d;
            for(d =0; d < nmemb; d++) {
                rsum1[d] += tsum1[d];
                rsum2[d] += tsum2[d];
                rmin[d] = fmin(rmin[d], tmin[d]);
                rmax[d] = fmax(rmax[d], tmax[d]);
            }
        }
    }
    uint64_t Ntot = p->np;

    MPI_Allreduce(MPI_IN_PLACE, rsum1, nmemb, MPI_DOUBLE, MPI_SUM, comm);
    MPI_Allreduce(MPI_IN_PLACE, rsum2, nmemb, MPI_DOUBLE, MPI_SUM, comm);
    MPI_Allreduce(MPI_IN_PLACE, rmin, nmemb, MPI_DOUBLE, MPI_MIN, comm);
    MPI_Allreduce(MPI_IN_PLACE, rmax, nmemb, MPI_DOUBLE, MPI_MAX, comm);
    MPI_Allreduce(MPI_IN_PLACE, &Ntot,   1, MPI_LONG,  MPI_SUM, comm);

    for(i = 0; i < strlen(fmt); i ++) {
        void * r = va_arg(va, void *);
        double * dr = (double * ) r;

        for(d = 0; d < 3; d++) {
            switch(fmt[i]) {
                case '-':
                    dr[d] = rsum1[d] / Ntot;
                    break;
                case '<':
                    dr[d] = rmin[d];
                    break;
                case '>':
                    dr[d] = rmax[d];
                    break;
                case 's':
                    dr[d] = sqrt(rsum2[d] / Ntot - pow(rsum1[d] / Ntot, 2));
                    break;
                case 'S':
                    dr[d] = sqrt(1.0 * Ntot / (Ntot - 1.)) * sqrt(rsum2[d] / Ntot - pow(rsum1[d] / Ntot, 2));
                    break;
                case 'v':
                    dr[d] = (rsum2[d] / Ntot - pow(rsum1[d] / Ntot, 2));
                    break;
                case 'V':
                    dr[d] = (1.0 * Ntot / (Ntot - 1.)) * (rsum2[d] / Ntot - pow(rsum1[d] / Ntot, 2));
                    break;
                default:
                    fastpm_raise(-1, "Unknown format str. Use '<->sSvV'\n");
            }
        }
    }
    va_end(va);
}

void
fastpm_store_steal(FastPMStore * p, FastPMStore * po, FastPMColumnTags attributes)
{
    int c;
    for(c = 0; c < 32; c ++) {
        if (!(p->_column_info[c].attribute & attributes)) continue;
        po->columns[c] = p->columns[c];
    }

    po->np = p->np;
    po->meta = p->meta;
}

static void
_fastpm_store_copy(FastPMStore * p, ptrdiff_t start, FastPMStore * po, ptrdiff_t offset, size_t ncopy)
{
    if(ncopy + start > p->np) {
        fastpm_raise(-1, "Copy out of bounds from source FastPMStore: asking for %td but has %td\n", ncopy + start, p->np);
    }
    if(ncopy + offset > po->np_upper) {
        fastpm_raise(-1, "Not enough storage in target FastPMStore: asking for %td but has %td\n", ncopy + offset, po->np_upper);
    }

    int c;
    for(c = 0; c < 32; c ++) {
        if(!po->columns[c]) continue;
        size_t elsize = po->_column_info[c].elsize;

        memcpy(po->columns[c] + offset * elsize,
                p->columns[c] + start * elsize, elsize * ncopy);

    }
    po->np = offset + ncopy;
    po->meta = p->meta;
}

void
fastpm_store_copy(FastPMStore * p, FastPMStore * po)
{
    _fastpm_store_copy(p, 0, po, 0, p->np);
}

void
fastpm_store_take(FastPMStore * p, ptrdiff_t i, FastPMStore * po, ptrdiff_t j)
{
    _fastpm_store_copy(p, i, po, j, 1);
}

/* extends p by extra. */
void
fastpm_store_extend(FastPMStore * p, FastPMStore * extra)
{
    _fastpm_store_copy(extra, 0, p, p->np, extra->np);
}

void
fastpm_store_fill_subsample_mask(FastPMStore * p,
        double fraction,
        FastPMParticleMaskType * mask,
        MPI_Comm comm)
{
    gsl_rng * random_generator = gsl_rng_alloc(gsl_rng_ranlxd1);
    int ThisTask;
    MPI_Comm_rank(comm, &ThisTask);

    /* set uncorrelated seeds */
    double seed=1231584; //FIXME: set this properly.
    gsl_rng_set(random_generator, seed);
    int d;

    for(d = 0; d < ThisTask * 8; d++) {
        seed = 0x7fffffff * gsl_rng_uniform(random_generator);
    }

    gsl_rng_set(random_generator, seed);

    memset(mask, 0, p->np * sizeof(mask[0]));

    ptrdiff_t i;
    for(i=0; i < p->np; i++) {
        double rand_i = gsl_rng_uniform(random_generator);
        int flag = fraction > 1 || rand_i <= fraction;
        mask[i] = flag;
    }

    gsl_rng_free(random_generator);
}

void
fastpm_store_fill_subsample_mask_every_dim(FastPMStore * p,
                                              int every, /* take 1 every 'every' per dimension */
                                              FastPMParticleMaskType * mask)
{
    if(!fastpm_store_has_q(p)) {
        /* This can be relaxed by using a less strict subsample algorithm, e.g. subsample by a hash of ID. */
        fastpm_raise(-1, "Subsample is not supported if the store does not have meta.q.");
    }
    /* UNUSED */
    memset(mask, 0, p->np * sizeof(mask[0]));

    ptrdiff_t i, d;
    for(i = 0; i < p->np; i++) {
        ptrdiff_t pabs[3];   //pabs is the iq index. move out of loop?
        uint64_t id = p->id[i];
        fastpm_store_get_iq_from_id(p, id, pabs);

        int flag = 1;
        for(d = 0; d < 3; d++) {
            flag *= !(pabs[d] % every);
        }
        mask[i] = flag;
    }
}

/*
 * Create a subsample, keeping only those with mask == True;
 *
 * if po is NULL, only return number of items.
 * */
size_t
fastpm_store_subsample(FastPMStore * p, FastPMParticleMaskType * mask, FastPMStore * po)
{
    ptrdiff_t i;
    ptrdiff_t j;

    j = 0;
    for(i = 0; i < p->np; i ++) {
        if(!mask[i]) continue;
        /* just counting */
        if(po == NULL) { j++; continue; }
        /* avoid memcpy of same address if we are doing subsample inplace */
        if(p == po && j == i) {j ++; continue; }

        int c;
        for(c = 0; c < 32; c ++) {
            if (!po->columns[c]) continue;

            size_t elsize = po->_column_info[c].elsize;
            memcpy(po->columns[c] + j * elsize, p->columns[c] + i * elsize, elsize);
        }

        j ++;
    }

    if(po) {
        po->np = j;
        po->meta = p->meta;   ///????
    }
    return j;
}

#if 0
static ptrdiff_t
binary_search(double foo, double a[], size_t n) {
    ptrdiff_t left = 0, right = n;
    ptrdiff_t mid;
    /* find the right side, because hist would count the number < edge, not <= edge */
    if(a[left] > foo) {
        return 0;
    }
    if(a[right - 1] <= foo) {
        return n;
    }
    while(right - left > 1) {
        mid = left + ((right - left - 1) >> 1);
        double pivot = a[mid];
        if(pivot > foo) {
            right = mid + 1;
            /* a[right - 1] > foo; */
        } else {
            left = mid + 1;
            /* a[left] <= foo; */
        }
    }
    return left;
}
#endif
/* this is cumulative */
void
fastpm_store_histogram_aemit_sorted(FastPMStore * store,
        int64_t * hist,
        double * aedges,
        size_t nedges,
        MPI_Comm comm)
{
    ptrdiff_t i;

    int64_t * hist1 = malloc(sizeof(hist1[0]) * (nedges + 1));

    memset(hist1, 0, sizeof(hist1[0]) * (nedges + 1));

#pragma omp parallel
    {
        /* FIXME: use standard reduction with OpenMP 4.7 */

        int64_t * hist2 = malloc(sizeof(hist2[0]) * (nedges + 1));
        memset(hist2, 0, sizeof(hist2[0]) * (nedges + 1));

        int iedge = 0;

        /* this works because openmp will send each thread at most 1
         * chunk with the static scheduling; thus a thread never sees
         * out of order aemit */
        #pragma omp for schedule(static)
        for(i = 0; i < store->np; i ++) {
            while(iedge < nedges && store->aemit[i] >= aedges[iedge]) iedge ++;

//           int ibin = binary_search(store->aemit[i], aedges, nedges);
//           if(ibin != iedge) abort();

            hist2[iedge] ++;
        }

        #pragma omp critical
        {
            for(i = 0; i < nedges + 1; i ++) {
                hist1[i] += hist2[i];
            }
        }

        free(hist2);
    }

    MPI_Allreduce(MPI_IN_PLACE, hist1, nedges + 1, MPI_LONG, MPI_SUM, comm);

    for(i = 0; i < nedges + 1; i ++) {
        hist[i] += hist1[i];
    }

    free(hist1);
}