//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_HASH_STREAM_POSTPROCESSOR_HPP
#define CRYPTO3_HASH_STREAM_POSTPROCESSOR_HPP

#include <array>

#include <boost/assert.hpp>
#include <boost/concept_check.hpp>

#include <boost/range/concepts.hpp>
#include <boost/array.hpp>

#include <nil/crypto3/detail/pack.hpp>

#include <nil/crypto3/hash/accumulators/hash.hpp>

namespace nil {
    namespace crypto3 {
        namespace hashes {
            namespace detail {
                template<typename HashAccumulatorSet>
                struct ref_hash_impl {
                    typedef HashAccumulatorSet accumulator_set_type;
                    typedef
                        typename boost::mpl::front<typename accumulator_set_type::features_type>::type accumulator_type;

                    typedef typename accumulator_type::hash_type hash_type;

                    ref_hash_impl(accumulator_set_type &&stream_hash) : accumulator_set(stream_hash) {
                    }

                    accumulator_set_type &accumulator_set;
                };

                template<typename HashAccumulatorSet>
                struct value_hash_impl {
                    typedef HashAccumulatorSet accumulator_set_type;
                    typedef
                        typename boost::mpl::front<typename accumulator_set_type::features_type>::type accumulator_type;

                    typedef typename accumulator_type::hash_type hash_type;

                    value_hash_impl(accumulator_set_type &&stream_hash) :
                        accumulator_set(std::forward<accumulator_set_type>(stream_hash)) {
                    }

                    mutable accumulator_set_type accumulator_set;
                };

                template<typename HashStateImpl>
                struct range_hash_impl : public HashStateImpl {
                    typedef HashStateImpl hash_state_impl_type;

                    typedef typename hash_state_impl_type::accumulator_type accumulator_type;
                    typedef typename hash_state_impl_type::accumulator_set_type accumulator_set_type;

                    typedef typename hash_state_impl_type::hash_type hash_type;

                    typedef typename boost::mpl::apply<accumulator_set_type, accumulator_type>::type::result_type
                        result_type;

                    template<typename SinglePassRange>
                    range_hash_impl(const SinglePassRange &range, accumulator_set_type &&ise) :
                        HashStateImpl(std::forward<accumulator_set_type>(ise)) {
                        BOOST_RANGE_CONCEPT_ASSERT((boost::SinglePassRangeConcept<const SinglePassRange>));

                        typedef
                            typename std::iterator_traits<typename SinglePassRange::iterator>::value_type value_type;
                        BOOST_STATIC_ASSERT(std::numeric_limits<value_type>::is_specialized);
                        typedef typename hash_type::template stream_processor<
                            accumulator_set_type,
                            std::numeric_limits<value_type>::digits + std::numeric_limits<value_type>::is_signed>::type
                            stream_processor;

                        stream_processor(this->accumulator_set)(range.begin(), range.end());
                    }

                    template<typename InputIterator>
                    range_hash_impl(InputIterator first, InputIterator last, accumulator_set_type &&ise) :
                        HashStateImpl(std::forward<accumulator_set_type>(ise)) {
                        BOOST_CONCEPT_ASSERT((boost::InputIteratorConcept<InputIterator>));

                        typedef typename std::iterator_traits<InputIterator>::value_type value_type;
                        BOOST_STATIC_ASSERT(std::numeric_limits<value_type>::is_specialized);
                        typedef typename hash_type::template stream_processor<
                            accumulator_set_type,
                            std::numeric_limits<value_type>::digits + std::numeric_limits<value_type>::is_signed>::type
                            stream_processor;

                        stream_processor(this->accumulator_set)(first, last);
                    }

                    template<typename T, std::size_t Size>
                    inline operator std::array<T, Size>() const {
                        result_type result =
                            boost::accumulators::extract_result<accumulator_type>(this->accumulator_set);
                        std::array<T, Size> out;
                        std::copy(result.begin(), result.end(), out.end());
                        return out;
                    }

                    template<typename T, std::size_t Size>
                    inline operator boost::array<T, Size>() const {
                        result_type result =
                            boost::accumulators::extract_result<accumulator_type>(this->accumulator_set);
                        boost::array<T, Size> out;
                        std::copy(result.begin(), result.end(), out.end());
                        return out;
                    }

                    template<typename OutputRange>
                    inline operator OutputRange() const {
                        result_type result =
                            boost::accumulators::extract_result<accumulator_type>(this->accumulator_set);
                        return OutputRange(result.begin(), result.end());
                    }

                    inline operator result_type() const {
                        return boost::accumulators::extract_result<accumulator_type>(this->accumulator_set);
                    }

                    inline operator accumulator_set_type &() const {
                        return this->accumulator_set;
                    }

                    template<typename Integral,
                             typename = typename std::enable_if<std::is_integral<Integral>::value &&
                                                                hash_type::digest_bits <=
                                                                    std::numeric_limits<Integral>::digits>::type>
                    inline operator Integral() const {
                        std::array<Integral, 1> out;
                        result_type res = boost::accumulators::extract_result<accumulator_type>(this->accumulator_set);
                        ::nil::crypto3::detail::pack_to<stream_endian::little_octet_big_bit>(res, out);
                        return out[0];
                    }

#ifndef CRYPTO3_RAW_HASH_STRING_OUTPUT

                    template<typename Char, typename CharTraits, typename Alloc>
                    inline operator std::basic_string<Char, CharTraits, Alloc>() const {
                        return std::to_string(
                            boost::accumulators::extract_result<accumulator_type>(this->accumulator_set));
                    }

#endif
                };

                template<typename HashStateImpl, typename OutputIterator>
                struct itr_hash_impl : public HashStateImpl {
                private:
                    mutable OutputIterator out;

                public:
                    typedef HashStateImpl hash_state_impl_type;

                    typedef typename hash_state_impl_type::accumulator_type accumulator_type;
                    typedef typename hash_state_impl_type::accumulator_set_type accumulator_set_type;

                    typedef typename hash_state_impl_type::hash_type hash_type;

                    typedef typename boost::mpl::apply<accumulator_set_type, accumulator_type>::type::result_type
                        result_type;

                    template<typename SinglePassRange>
                    itr_hash_impl(const SinglePassRange &range, OutputIterator out, accumulator_set_type &&ise) :
                        HashStateImpl(std::forward<accumulator_set_type>(ise)), out(std::move(out)) {
                        BOOST_CONCEPT_ASSERT((boost::SinglePassRangeConcept<const SinglePassRange>));

                        typedef
                            typename std::iterator_traits<typename SinglePassRange::iterator>::value_type value_type;
                        BOOST_STATIC_ASSERT(std::numeric_limits<value_type>::is_specialized);
                        typedef typename hash_type::template stream_processor<
                            accumulator_set_type,
                            std::numeric_limits<value_type>::digits + std::numeric_limits<value_type>::is_signed>::type
                            stream_processor;

                        stream_processor(this->accumulator_set)(range.begin(), range.end());
                    }

                    template<typename InputIterator>
                    itr_hash_impl(InputIterator first, InputIterator last, OutputIterator out,
                                  accumulator_set_type &&ise) :
                        HashStateImpl(std::forward<accumulator_set_type>(ise)),
                        out(std::move(out)) {
                        BOOST_CONCEPT_ASSERT((boost::InputIteratorConcept<InputIterator>));

                        typedef typename std::iterator_traits<InputIterator>::value_type value_type;
                        BOOST_STATIC_ASSERT(std::numeric_limits<value_type>::is_specialized);
                        typedef typename hash_type::template stream_processor<
                            accumulator_set_type,
                            std::numeric_limits<value_type>::digits + std::numeric_limits<value_type>::is_signed>::type
                            stream_processor;

                        stream_processor(this->accumulator_set)(first, last);
                    }

                    inline operator accumulator_set_type &() const {
                        return this->accumulator_set;
                    }

                    inline operator OutputIterator() const {
                        result_type result =
                            boost::accumulators::extract_result<accumulator_type>(this->accumulator_set);
                        return std::move(result.cbegin(), result.cend(), out);
                    }
                };
            }    // namespace detail
        }        // namespace hashes
    }            // namespace crypto3
}    // namespace nil

#endif


/**
 * Copyright (c) 2018, University Osnabrück
 * 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 University Osnabrück 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 University Osnabrück 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 __ClSurface_H
#define __ClSurface_H

#include <lvr2/reconstruction/QueryPoint.hpp>
#include <lvr2/reconstruction/LBKdTree.hpp>
#include <lvr2/geometry/BaseVector.hpp>
#include <lvr2/geometry/LBPointArray.hpp>

#include <boost/filesystem.hpp>
#include <boost/shared_array.hpp>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define CL_HPP_TARGET_OPENCL_VERSION 120
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
#define __CL_ENABLE_EXCEPTIONS

#if defined(__APPLE__) || defined(__MACOSX)
    #include <OpenCL/opencl.h>
#else
    #include <CL/cl.h>
#endif
#include <lvr2/reconstruction/opencl/cl_helper.h>

#define MAX_SOURCE_SIZE (0x1024)

namespace lvr2
{

typedef boost::shared_array<float> floatArr;

using Vec = BaseVector<float>;
typedef QueryPoint<Vec> QueryPointC;

class ClSurface {
public:
    ClSurface(floatArr& points, size_t num_points, int device = 0);
    ~ClSurface();

    /**
    * @brief Starts calculation the normals on GPU
    *
    */
    void calculateNormals();

    /**
     * @brief Get the resulting normals of the normal calculation. After calling "start".
     *
     * @param output_normals     PointArray as return value
     */
    void getNormals(floatArr output_normals);

    /**
     * @brief Set the number of k nearest neighbors
     *        k-neighborhood
     *
     * @param k             The size of the used k-neighborhood
     *
     */
    void setKn(int kn);

    /**
     * @brief Set the number of k nearest neighbors
     *        k-neighborhood for interpolation
     *
     * @param k             The size of the used k-neighborhood
     *
     */
    void setKi(int ki);

    /**
     * @brief Set the number of k nearest neighbors
     *        k-neighborhood for distance
     *
     * @param k             The size of the used k-neighborhood
     *
     */
    void setKd(int kd);

    /**
     * @brief Set the viewpoint to orientate the normals
     *
     * @param v_x     Coordinate X axis
     * @param v_y     Coordinate Y axis
     * @param v_z     Coordinate Z axis
     *
     */
    void setFlippoint(float v_x, float v_y, float v_z);

    /**
     * @brief Set Method for normal calculation
     *
     * @param method   "PCA","RANSAC"
     *
     */
    void setMethod(std::string method);

    /**
    * Reconstuction Mode:
    * Points stay in gpu until reconstruction is finished
    */
    void setReconstructionMode(bool mode = true);

    /**
    * TODO:
    *    Implement
    */
    void distances(std::vector<QueryPoint<Vec> >& query_points, float voxel_size);

    void freeGPU();

private:

    void init();

    const char *getErrorString(cl_int error);

    void initKdTree();

    void getDeviceInformation(int platform_id=0, int device_id=0);

    void loadEstimationKernel();

    void loadInterpolationKernel();

    void initCl();

    void finalizeCl();

    // V->points and normals
    LBPointArray<float> V;
    LBPointArray<float>* kd_tree_values;
    LBPointArray<unsigned char>* kd_tree_splits;

    LBPointArray<float> Result_Normals;
    boost::shared_ptr<LBKdTree> kd_tree_gen;

    float m_vx, m_vy, m_vz;
    int m_k, m_ki, m_kd;

    int m_calc_method;
    bool m_reconstruction_mode;

    // Device Information
    cl_platform_id m_platform_id;
    cl_device_id m_device_id;
    cl_uint m_mps;
    cl_uint m_threads_per_block;
    cl_ulong m_device_global_memory;
    cl_int m_ret;
    cl_context m_context;
    cl_command_queue m_command_queue;
    cl_program m_program_es;
    cl_program m_program_in;
    cl_kernel m_kernel_normal_estimation;
    cl_kernel m_kernel_normal_interpolation;

    cl_mem D_V;
    cl_mem D_kd_tree_values;
    cl_mem D_kd_tree_splits;
    cl_mem D_Normals;

const char *NORMAL_ESTIMATION_KERNEL_STRING = "\n"
"unsigned int GetKdTreePosition(__global const float* D_kd_tree_values,"
"const unsigned int num_values,__global const unsigned char* D_kd_tree_splits,"
"const unsigned int num_splits, float x, float y, float z) \n"
"{ \n"
"    unsigned int pos = 0; \n"
"    unsigned int current_dim = 0; \n"
"    while(pos < num_splits) \n"
"    { \n"
"        current_dim = (unsigned int)(D_kd_tree_splits[pos]); \n"
"        if(current_dim == 0) \n"
"        { \n"
"            if(x <= D_kd_tree_values[pos] ) \n"
"            { \n"
"                pos = pos*2+1; \n"
"            } else { \n"
"                pos = pos*2+2; \n"
"            } \n"
"        } else if(current_dim == 1) { \n"
"            if(y <= D_kd_tree_values[pos] ){ \n"
"                pos = pos*2+1; \n"
"            }else{ \n"
"                pos = pos*2+2; \n"
"            } \n"
"        } else { \n"
"            if(z <= D_kd_tree_values[pos] ){ \n"
"                pos = pos*2+1; \n"
"            }else{ \n"
"                pos = pos*2+2; \n"
"            } \n"
"        } \n"
"    } \n"
"    return pos; \n"
"} \n"
" \n"
"__kernel void NormalEstimationKernel(__global const float* D_V, const unsigned int num_points,"
"__global const float* D_kd_tree_values, const unsigned int num_values,"
"__global const unsigned char* D_kd_tree_splits , const unsigned int num_splits,"
"__global float* D_Normals, const unsigned int num_pointnormals,const unsigned int k,"
"const float flip_x, const float flip_y, const float flip_z) \n"
"{ \n"
"    unsigned int loc_id = get_local_id(0); \n"
"    unsigned int loc_size = get_local_size(0); \n"
"    unsigned int glob_id = get_global_id(0); \n"
"    unsigned int glob_size = get_global_size(0); \n"
"    unsigned int group_id = get_group_id(0); \n"
"    unsigned int group_size = get_num_groups(0); \n"
"    unsigned int tid = glob_id; \n"
"    const unsigned int offset = glob_size; \n"
"    for(;tid < num_points; tid += offset) \n"
"    { \n"
"        unsigned int pos = GetKdTreePosition(D_kd_tree_values, "
"num_values,D_kd_tree_splits, num_splits,D_V[tid * 3], D_V[tid * 3 + 1], D_V[tid * 3 +2] ); \n"
"        unsigned int vertex_index = (unsigned int)(D_kd_tree_values[pos]+ 0.5); \n"
"        if(vertex_index < num_points) \n"
"        { \n"
"            float vertex_x = D_V[ vertex_index * 3 + 0 ]; \n"
"            float vertex_y = D_V[ vertex_index * 3 + 1 ]; \n"
"            float vertex_z = D_V[ vertex_index * 3 + 2 ]; \n"
"            unsigned int nearest_index; \n"
"            int start = pos-(k/2); \n"
"            int end = pos+((k+1)/2); \n"
"            int correct = 0; \n"
"            if(start < num_splits) \n"
"            { \n"
"                correct = num_splits - start; \n"
"            }else if(end > num_values) \n"
"            { \n"
"                correct = num_values - end; \n"
"            } \n"
"            start += correct; \n"
"            end += correct; \n"
"            float result_x = 0.0; \n"
"            float result_y = 0.0; \n"
"            float result_z = 0.0; \n"
"            float xx = 0.0; \n"
"            float xy = 0.0; \n"
"            float xz = 0.0; \n"
"            float yy = 0.0; \n"
"            float yz = 0.0; \n"
"            float zz = 0.0; \n"
"            for(unsigned int i = start; i < end && i<num_values; i++ ) \n"
"            { \n"
"                if(i != pos) \n"
"                { \n"
"                    nearest_index = (unsigned int)(D_kd_tree_values[i]+ 0.5); \n"
"                    if(nearest_index < num_points) \n"
"                    { \n"
"                        float rx = D_V[ nearest_index * 3 + 0 ] - vertex_x; \n"
"                        float ry = D_V[ nearest_index * 3 + 1 ] - vertex_y; \n"
"                        float rz = D_V[ nearest_index * 3 + 2 ] - vertex_z; \n"
"                        xx += rx * rx; \n"
"                        xy += rx * ry; \n"
"                        xz += rx * rz; \n"
"                        yy += ry * ry; \n"
"                        yz += ry * rz; \n"
"                        zz += rz * rz; \n"
"                    } \n"
"                } \n"
"            } \n"
"            float det_x = yy * zz - yz * yz; \n"
"            float det_y = xx * zz - xz * xz; \n"
"            float det_z = xx * yy - xy * xy; \n"
"            float dir_x; \n"
"            float dir_y; \n"
"            float dir_z; \n"
"            if( det_x >= det_y && det_x >= det_z) \n"
"            { \n"
"                dir_x = 1.0; \n"
"                dir_y = (xz * yz - xy * zz) / det_x; \n"
"                dir_z = (xy * yz - xz * yy) / det_x; \n"
"            } \n"
"            else if( det_y >= det_x && det_y >= det_z) \n"
"            { \n"
"                dir_x = (yz * xz - xy * zz) / det_y; \n"
"                dir_y = 1.0; \n"
"                dir_z = (xy * xz - yz * xx) / det_y; \n"
"            } \n"
"            else{ \n"
"                dir_x = (yz * xy - xz * yy ) / det_z; \n"
"                dir_y = (xz * xy - yz * xx ) / det_z; \n"
"                dir_z = 1.0; \n"
"            } \n"
"            float invnorm = 1/sqrt( dir_x * dir_x + dir_y * dir_y + dir_z * dir_z ); \n"
"            result_x = dir_x * invnorm; \n"
"            result_y = dir_y * invnorm; \n"
"            result_z = dir_z * invnorm; \n"
"            float x_dir = flip_x - vertex_x; \n"
"            float y_dir = flip_y - vertex_y; \n"
"            float z_dir = flip_z - vertex_z; \n"
"            float scalar = x_dir * result_x + y_dir * result_y + z_dir * result_z; \n"
"            if(scalar < 0) \n"
"            { \n"
"                result_x = -result_x; \n"
"                result_y = -result_y; \n"
"                result_z = -result_z; \n"
"            } \n"
"            D_Normals[tid * 3 ] = result_x; \n"
"            D_Normals[tid * 3 + 1 ] = result_y; \n"
"            D_Normals[tid * 3 + 2 ] = result_z; \n"
"        } \n"
"    } \n"
"} \n";

const char *NORMAL_INTERPOLATION_KERNEL_STRING = "\n"
"float getGaussianFactor(const unsigned int index, const unsigned int middle_i, "
"const unsigned int ki, const float norm) \n"
"{ \n"
"    float val = (float)(index); \n"
"    float middle = (float)(middle_i); \n"
"    float ki_2 = (float)(ki)/2.0; \n"
"    if(val > middle) \n"
"    { \n"
"        val = val - middle; \n"
"    }else{ \n"
"        val = middle - val; \n"
"    } \n"
"    if(val > ki_2) \n"
"    { \n"
"        return 0.0; \n"
"    }else{ \n"
"        float border_val = 0.2; \n"
"        float gaussian = 1.0 - pow((float)val/ki_2, (float)2.0) * (1.0-border_val); \n"
"        return gaussian * norm; \n"
"    } \n"
"} \n"
" \n"
"__kernel void NormalInterpolationKernel(__global float* D_kd_tree_values,"
"const unsigned int num_values, __global float* D_kd_tree_splits, "
"const unsigned int num_splits, __global float* D_Normals, "
"const unsigned int num_pointnormals, const unsigned int ki) \n"
"{ \n"
"    unsigned int loc_id = get_local_id(0); \n"
"    unsigned int loc_size = get_local_size(0); \n"
"    unsigned int glob_id = get_global_id(0); \n"
"    unsigned int glob_size = get_global_size(0); \n"
"    unsigned int group_id = get_group_id(0); \n"
"    unsigned int group_size = get_num_groups(0); \n"
"    unsigned int tid = glob_id; \n"
"    const unsigned int offset = glob_size; \n"
"    for(;tid < num_pointnormals; tid += offset) \n"
"    { \n"
"        int c = 0; \n"
"        unsigned int offset = num_splits; \n"
"        unsigned int query_index = (unsigned int)(D_kd_tree_values[offset + tid]+ 0.5); \n"
"        unsigned int nearest_index; \n"
"        float gaussian = 5.0; \n"
"        if(query_index < num_pointnormals) \n"
"        { \n"
"            float n_x = D_Normals[query_index * 3 + 0]; \n"
"            float n_y = D_Normals[query_index * 3 + 1]; \n"
"            float n_z = D_Normals[query_index * 3 + 2]; \n"
"            if(tid > 1) \n"
"            { \n"
"                for(unsigned int i = tid-1; i > 0 && c < ki/2; i--,c++ ) \n"
"                { \n"
"                    nearest_index = (unsigned int)(D_kd_tree_values[i + offset]+ 0.5); \n"
"                    if(nearest_index < num_pointnormals) \n"
"                    { \n"
"                        gaussian = getGaussianFactor(i, tid, ki, 5.0); \n"
"                        n_x += gaussian * D_Normals[nearest_index * 3 + 0]; \n"
"                        n_y += gaussian * D_Normals[nearest_index * 3 + 1]; \n"
"                        n_z += gaussian * D_Normals[nearest_index * 3 + 2]; \n"
"                    } \n"
"                } \n"
"            } \n"
"            if(tid < num_pointnormals-1) \n"
"            { \n"
"                for(unsigned int i = tid+1; i < num_pointnormals && c < ki; i++,c++ ) \n"
"                { \n"
"                    nearest_index = (unsigned int)(D_kd_tree_values[i + offset]+ 0.5); \n"
"                    if(nearest_index < num_pointnormals) \n"
"                    { \n"
"                        gaussian = getGaussianFactor(i, tid, ki, 5.0); \n"
"                        n_x += gaussian * D_Normals[nearest_index * 3 + 0]; \n"
"                        n_y += gaussian * D_Normals[nearest_index * 3 + 1]; \n"
"                        n_z += gaussian * D_Normals[nearest_index * 3 + 2]; \n"
"                    } \n"
"                } \n"
"            } \n"
"            float norm = sqrt(pow(n_x,2) + pow(n_y,2) + pow(n_z,2)); \n"
"            n_x = n_x/norm; \n"
"            n_y = n_y/norm; \n"
"            n_z = n_z/norm; \n"
"            D_Normals[query_index * 3 + 0] = n_x; \n"
"            D_Normals[query_index * 3 + 1] = n_y; \n"
"            D_Normals[query_index * 3 + 2] = n_z; \n"
"        } \n"
"    } \n"
"} \n";

};

} /* namespace lvr2 */

#endif // !__ClSurface_H


#ifdef COMPILATION// -*-indent-tabs-mode:t;c-basic-offset:4;tab-width:4;autowrap:nil;-*-
echo $X
[ ! -d build.$X ] && (mkdir build.$X && cd build.$X && cmake ..)
cd build.$X && make -j && ctest -j --output-on-failure
exit
#endif
// $CXXX $CXXFLAGS $0 -o $0.$X&&$0.$X&&rm $0.$X;exit
//  © Alfredo A. Correa 2018-2019

#ifndef BOOST_MULTI_ARRAY_HPP
#define BOOST_MULTI_ARRAY_HPP

#include "./array_ref.hpp"
#include "./config/NO_UNIQUE_ADDRESS.hpp"

#include "./memory/allocator.hpp"
#include "./detail/memory.hpp"
#include "./detail/adl.hpp"

#include<memory>

namespace boost{
namespace multi{

template<class Allocator> struct array_allocator{
	using allocator_type = Allocator;
protected:
	MULTI_NO_UNIQUE_ADDRESS allocator_type alloc_;
	allocator_type& alloc(){return alloc_;}
	array_allocator(allocator_type const& a = {}) : alloc_{a}{}
	typename std::allocator_traits<allocator_type>::pointer
	allocate(typename std::allocator_traits<allocator_type>::size_type n){
		return n?std::allocator_traits<allocator_type>::allocate(alloc_, n):nullptr;
	}
	typename std::allocator_traits<allocator_type>::pointer
	allocate(typename std::allocator_traits<allocator_type>::size_type n, typename std::allocator_traits<allocator_type>::const_void_pointer hint){
		return n?std::allocator_traits<allocator_type>::allocate(alloc_, n, hint):nullptr;
	}
	auto uninitialized_fill_n(typename std::allocator_traits<allocator_type>::pointer base, typename std::allocator_traits<allocator_type>::size_type num_elements, typename std::allocator_traits<allocator_type>::value_type e){
		return adl_alloc_uninitialized_fill_n(alloc_, base, num_elements, e);
	}
	template<typename It>
	auto uninitialized_copy_n(It first, size_type n, typename std::allocator_traits<allocator_type>::pointer data){
		return adl_alloc_uninitialized_copy_n(alloc_, first, n, data);
	}
	template<typename It>
	auto destroy_n(It first, size_type n){
		return adl_alloc_destroy_n(this->alloc(), first, n);
	}
public:
	allocator_type get_allocator() const{return alloc_;}
	friend allocator_type get_allocator(array_allocator const& s){return s.get_allocator();}
};

template<class T, class Ptr = T*> struct move_ptr : std::move_iterator<Ptr>{
	using std::move_iterator<Ptr>::move_iterator;
	explicit operator Ptr() const{return std::move_iterator<Ptr>::base();}
};

// static_array is not a value type because it doesn't define assignment for static_arrays of different extensions
template<class T, dimensionality_type D, class Alloc = std::allocator<T>>
struct static_array :
	protected array_allocator<Alloc>,
	public array_ref<T, D, typename std::allocator_traits<typename array_allocator<Alloc>::allocator_type>::pointer>
{
private:
	using array_alloc = array_allocator<Alloc>;
public:
	static_assert( std::is_same<typename std::allocator_traits<Alloc>::value_type, typename static_array::element>{},
		"allocator value type must match array value type");
	static_assert( std::is_same<typename std::allocator_traits<Alloc>::pointer, typename static_array::element_ptr>{},
		"allocator pointer type must match array pointer type");
	using array_alloc::get_allocator;
	using typename array_allocator<Alloc>::allocator_type;
//	using allocator_type = typename static_array::allocator_type;
	using decay_type = array<T, D, Alloc>;
protected:
	using alloc_traits = typename std::allocator_traits<typename static_array::allocator_type>;
	using ref = array_ref<T, D, typename std::allocator_traits<typename std::allocator_traits<Alloc>::template rebind_alloc<T>>::pointer>;
	auto uninitialized_value_construct(){
		return adl_alloc_uninitialized_value_construct_n(static_array::alloc(), this->base_, this->num_elements());
	}
	auto uninitialized_default_construct(){
	//	return std::uninitialized_default_construct_n(this->base_, this->num_elements());
		return adl_alloc_uninitialized_default_construct_n(static_array::alloc(), this->base_, this->num_elements());
	}
	template<typename It> auto uninitialized_copy_elements(It first){
		return array_alloc::uninitialized_copy_n(first, this->num_elements(), this->data_elements());
	}
	void destroy_aux(std::false_type){array_alloc::destroy_n(this->data_elements(), this->num_elements());}
	void destroy_aux(std::true_type ){}
	void destroy(){destroy_aux(std::is_trivially_destructible<typename static_array::element>{});}
	void allocate(){this->base_ = array_alloc::allocate(static_array::num_elements());}
public:
	using value_type = typename std::conditional<
		(static_array::dimensionality>1),
		array<typename static_array::element, static_array::dimensionality-1, allocator_type>,
		typename std::conditional<
			static_array::dimensionality == 1,
			typename static_array::element,
			typename static_array::element // TODO or void?
		>::type
	>::type;

	using typename ref::size_type;
	using typename ref::difference_type;
	explicit static_array(typename static_array::allocator_type const& a) : array_alloc{a}{}
protected:
	static_array(static_array&& other, typename static_array::allocator_type const& a) noexcept     //6b
	:	array_alloc{a},
		ref{other.base_, other.extensions()}
	{
		other.ref::layout_t::operator=({});
		other.base_ = nullptr;
	}
public:
	// cppcheck-suppress noExplicitConstructor ; because argument can be well-represented
	static_array(
		basic_array<typename static_array::element, static_array::dimensionality, multi::move_ptr<typename static_array::element, typename static_array::element_ptr>>&& other,
		typename static_array::allocator_type const& a = {}
	) noexcept :
		array_alloc{a},
		ref{
			other.layout()==typename static_array::layout_t(other.extensions())?
				other.base_.base():
				array_alloc::allocate(other.num_elements())
			,
			other.extensions()
		}
	{
		if(other.base_.base() != static_array::base_)
			recursive<D>::alloc_uninitialized_copy(static_array::alloc(),
				other.template static_array_cast<typename static_array::element, typename static_array::element_ptr>().begin(),
				other.template static_array_cast<typename static_array::element, typename static_array::element_ptr>().end()  ,
				this->begin()
			);
	}
//	template<class Array>//, std::enable_if_t<std::is_same<Array, basic_array>{}, int> =0>
//	auto operator==(Array&& o) const&
//	->decltype(std::move(modify(*this)).ref::operator==(std::forward<Array>(o))){
//		return std::move(modify(*this)).ref::operator==(std::forward<Array>(o));}

//	auto operator==(static_array const& o) const&{return std::move(modify(*this)).ref::operator==(std::move(modify(o)));}

	template<class TT, class... Args>
	bool operator==(basic_array<TT, D, Args...> const& other) const{
		return ref::operator==(other);
	}

	template<class It, class=typename std::iterator_traits<std::decay_t<It>>::difference_type>//edecltype(std::distance(std::declval<It>(), std::declval<It>()), *std::declval<It>())>
	// analogous to std::vector::vector (5) https://en.cppreference.com/w/cpp/container/vector/vector
	static_array(It first, It last, typename static_array::allocator_type const& a = {}) :
		array_alloc{a},
		ref{
			array_alloc::allocate(typename static_array::layout_t{index_extension(adl_distance(first, last))*multi::extensions(*first)}.num_elements()),
			index_extension(adl_distance(first, last))*multi::extensions(*first)
		}
	{
//		recursive<D>::alloc_uninitialized_copy(static_array::alloc(), first, last, this->begin());
		adl_alloc_uninitialized_copy(static_array::alloc(), first, last, ref::begin());
//		adl::uninitialized_copy(first, last, ref::begin());
	}

	template<
		class Range, class=std::enable_if_t<not std::is_base_of<static_array, std::decay_t<Range>>{}>,
		class=decltype(/*static_array*/(std::declval<Range&&>().begin(), std::declval<Range&&>().end())), // instantiation of static_array here gives a compiler error in 11.0
		class=std::enable_if_t<not is_basic_array<Range&&>{}>// TODO add is_assignable<value_type> check
	>
	// cppcheck-suppress noExplicitConstructor ; because I want to use equal for lazy assigments form range-expressions
	static_array(Range&& rng) : static_array(std::forward<Range>(rng).begin(), std::forward<Range>(rng).end()){}

	template<class TT>
	auto uninitialized_fill_elements(TT const& value){
		return array_alloc::uninitialized_fill_n(this->data_elements(), this->num_elements(), value);
	}

	template<class TT, class... As>
	// cppcheck-suppress noExplicitConstructor ; because argument can be well-represented
	static_array(array_ref<TT, D, As...> const& other, typename static_array::allocator_type const& a = {}) :
		array_alloc{a},
		ref{array_alloc::allocate(other.num_elements()), other.extensions()}
	{
		adl_alloc_uninitialized_copy_n(static_array::alloc(), other.data_elements(), other.num_elements(), this->data_elements());
	}

	static_array(typename static_array::extensions_type x, typename static_array::element const& e, typename static_array::allocator_type const& a) : //2
		array_alloc{a},
		ref(array_alloc::allocate(typename static_array::layout_t{x}.num_elements()), x)
	{
		array_alloc::uninitialized_fill_n(this->data_elements(), this->num_elements(), e);
	}
	template<class Element, std::enable_if_t<std::is_convertible<Element, typename static_array::element>{} and D==0, int> = 0>
	explicit static_array(Element const& e, typename static_array::allocator_type const& a) :
		static_array(typename static_array::extensions_type{}, e, a){}

	static_array(typename static_array::extensions_type x, typename static_array::element const& e) : //2
		array_alloc{}, ref(array_alloc::allocate(typename static_array::layout_t{x}.num_elements()), x)
	{
		array_alloc::uninitialized_fill_n(this->base(), this->num_elements(), e);
	}
	explicit static_array(typename static_array::extensions_type x, typename std::allocator_traits<Alloc>::const_void_pointer hint) :
		array_alloc{}, ref(array_alloc::allocate(typename static_array::layout_t{x}.num_elements(), hint), x)
	{}
//	template<class Elem, typename = std::enable_if_t<std::is_convertible<Elem, typename static_array::element>{} and D==0>>
//	static_array(Elem const& e)  //2
//	:	static_array(multi::iextensions<D>{}, e){}

//	explicit static_array(typename static_array::index n, typename static_array::value_type const& v, typename static_array::allocator_type const& a = {})
//	: 	static_array(typename static_array::index_extension(n), v, a){}
	template<class ValueType, typename = std::enable_if_t<std::is_same<ValueType, typename static_array::value_type>{}>>
	explicit static_array(typename static_array::index_extension const& e, ValueType const& v, typename static_array::allocator_type const& a = {}) //3
	= delete;
//	: static_array(e*extensions(v), a) {
//		adl::fill(this->begin(), this->end(), v); // TODO this should be alloc_unintialized_fill
//	}
//	template<class Allocator, typename = std::enable_if_t<std::is_same<Allocator, allocator_type>{}> >
//	explicit
// analgous to std::vector::vector ((4)) https://en.cppreference.com/w/cpp/container/vector/vector
	explicit static_array(typename static_array::extensions_type x, typename static_array::allocator_type const& a = typename static_array::allocator_type{}) :
		array_alloc{a}, ref{array_alloc::allocate(typename static_array::layout_t{x}.num_elements()), x}
	{
		if(not std::is_trivially_default_constructible<typename static_array::element_type>{})
			uninitialized_default_construct();
	}
	template<class TT, class... Args,
		class=std::enable_if_t<std::is_assignable<typename ref::element_ref, typename multi::basic_array<TT, D, Args...>::element>{}>,
		class=decltype(adl_copy(std::declval<multi::basic_array<TT, D, Args...> const&>().begin(), std::declval<multi::basic_array<TT, D, Args...> const&>().end(), std::declval<typename static_array::iterator>()))
	>
	// cppcheck-suppress noExplicitConstructor ; because argument can be well-represented
	static_array(multi::basic_array<TT, D, Args...> const& o, typename static_array::allocator_type const& a = {})
	: static_array(o.extensions(), a) // TODO: should be uninitialized_copy
	{
		adl_copy(o.begin(), o.end(), this->begin()); // TODO: should be uninitialized_copy, and recursive
	}

	template<class TT, class... Args>
	// cppcheck-suppress noExplicitConstructor ; because argument can be well-represented
	static_array(array_ref<TT, D, Args...>&& o)
	: array_alloc{}, ref{array_alloc::allocate(o.num_elements()), o.extensions()}{
		static_array::uninitialized_copy_elements(std::move(o).data_elements());
	}
	static_array(static_array const& o, typename static_array::allocator_type const& a) //5b
	: array_alloc{a}, ref{static_array::allocate(num_elements(o)), extensions(o)}{
		uninitialized_copy_from(data(o));
	}
	static_array(static_array const& o)                                  //5b
	: array_alloc{o.get_allocator()}, ref{array_alloc::allocate(num_elements(o)), extensions(o)}{
		uninitialized_copy_elements(o.data_elements());
	}
//	TODO static_array(static_array&& o)                                  //5b'
//	: array_alloc{o.get_allocator()}, ref{array_alloc::allocate(num_elements(o)), extensions(o)}{
//		array_alloc::uninitialized_move_elements(data_elements(o));
//	}
	// cppcheck-suppress noExplicitConstructor ; to allow assigment-like construction of nested arrays
	constexpr static_array(std::initializer_list<typename static_array<T, D>::value_type> mil) :
		static_array(static_array<T, D>(mil.begin(), mil.end()))
	{}
	static_array(
		std::initializer_list<typename static_array<T, D>::value_type> mil,
		typename static_array::allocator_type const& a
	) : static_array(static_array<T, D>(mil.begin(), mil.end()), a){}
	template<class TT, std::size_t N>
	// cppcheck-suppress noExplicitConstructor ; to allow assigment-like construction from c-arrays
	constexpr static_array(TT(&array)[N]) : static_array(std::begin(array), std::end(array)){}
	template<class It> static auto distance(It a, It b){using std::distance; return distance(a, b);}
protected:
	void deallocate(){
		if(this->num_elements()) alloc_traits::deallocate(this->alloc(), this->base_, static_cast<typename alloc_traits::size_type>(this->num_elements()));
	}
	void clear() noexcept{
		this->destroy();
		deallocate();
		layout_t<D>::operator=({});
	}
	template<class... Ts>
	constexpr static_array&& reindex(Ts... a)&&{
		static_array::layout_t::reindex(a...);
		return std::move(*this);
	}
	template<class... Ts>
	constexpr static_array& reindex(Ts... a)&{
		static_array::layout_t::reindex(a...);
		return *this;
	}
public:
	static_array() = default;
	~static_array() noexcept{destroy(); deallocate();}
	using element_const_ptr = typename std::pointer_traits<typename static_array::element_ptr>::template rebind<typename static_array::element const>;
	using element_move_ptr  = std::move_iterator<typename static_array::element_ptr>;
	using reference = typename std::conditional<
		(static_array::dimensionality > 1),
		basic_array<typename static_array::element, static_array::dimensionality-1, typename static_array::element_ptr>,
		typename std::conditional<
			static_array::dimensionality == 1,
			typename std::iterator_traits<typename static_array::element_ptr>::reference,
			void
		>::type
	//	typename pointer_traits<typename static_array::element_ptr>::element_type&
	>::type;
	using const_reference = typename std::conditional<
		(static_array::dimensionality > 1),
		basic_array<typename static_array::element, static_array::dimensionality-1, typename static_array::element_const_ptr>, // TODO should be const_reference, but doesn't work witn rangev3
		typename std::conditional<
			static_array::dimensionality == 1,
			decltype(*std::declval<typename static_array::element_const_ptr>()),
		//	typename std::iterator_traits<typename static_array::element_const_ptr>::reference,
			void
		>::type
	//	typename pointer_traits<typename static_array::element_ptr>::element_type const&
	>::type;
	using       iterator = multi::array_iterator<T, static_array::dimensionality, typename static_array::element_ptr      >;//, reference>;
	using const_iterator = multi::array_iterator<T, static_array::dimensionality, typename static_array::element_const_ptr>;//, const_reference>;
//	reference
//	      reference operator[](index i)     &&{return std::move(*this).ref::operator[](i);}
//	      reference operator[](index i)      &{return ref::operator[](i);}
//	const_reference operator[](index i) const&{return ref::operator[](i);}
//	typename static_array::allocator_type get_allocator() const{return static_cast<typename static_array::allocator_type const&>(*this);}
	friend typename static_array::allocator_type get_allocator(static_array const& self){return self.get_allocator();}

//	[[deprecated("use ::data_elements()")]] typename static_array::element_ptr data()       {return ref::data_elements();}
//	[[deprecated("use ::data_elements()")]] constexpr auto data() const{return typename static_array::element_const_ptr{ref::data_elements()};}
//#ifndef __NVCC__ // deprecated friend doesn't work in nvcc
//	[[deprecated("use data_elements()")]]
//#else
//	__attribute__((deprecated))
//#endif
//	friend typename static_array::element_ptr       data(static_array&       s){return s.data_elements();}
//#ifndef __NVCC__ // deprecated friend doesn't work in nvcc
//	[[deprecated("use data_elements()")]]
//#else
//	__attribute__((deprecated))
//#endif
//	friend typename static_array::element_const_ptr data(static_array const& s){return s.data_elements();}

	element_const_ptr                   data_elements() const&{return this->base_;}
	typename static_array::element_ptr  data_elements()      &{return this->base_;}
	static_array::element_move_ptr      data_elements()     &&{return std::make_move_iterator(this->base_);}

	friend auto data_elements(static_array const& s){return           s .data_elements();}
	friend auto data_elements(static_array      & s){return           s .data_elements();}
	friend auto data_elements(static_array     && s){return std::move(s).data_elements();}

	constexpr typename static_array::element_ptr       base()      {return ref::base();}
	constexpr typename static_array::element_const_ptr base() const{return typename static_array::element_const_ptr{ref::base()};}
	friend typename static_array::element_ptr       base(static_array&       s){return s.base();}
	friend typename static_array::element_const_ptr base(static_array const& s){return s.base();}

	typename static_array::element_ptr       origin()      {return ref::origin();}
	typename static_array::element_const_ptr origin() const{return ref::origin();}
	friend typename static_array::element_ptr       origin(static_array&       s){return s.origin();}
	friend typename static_array::element_const_ptr origin(static_array const& s){return s.origin();}

//	template<class... Args> decltype(auto) operator()(Args const&... args)&{return ref::operator()(args...);}
//	template<class... Args> decltype(auto) operator()(Args const&... args) const&{return ref::operator()(args...);}
//	using ref::operator();

//	basic_array<T, D, typename static_array::element_ptr>
//	decltype(auto) operator()() &{
//		this->template static_array_cast<typename static_array::element_type>();
	//	return static_array_cast<typename static_array::element_type>(*this);//(std::forward<Ts>(t)...);
	//	return ref::operator()();
	//	return *this;
//	}
//	basic_array<T, D, typename static_array::element_const_ptr> operator()() const&{
//		this->template static_array_cast<typename static_array::element_type>();
	//	return static_array_cast<typename static_array::element_type const>(*this);
	//	return basic_array<T, D, typename static_array::element_const_ptr>{this->layout(), this->base_};
//	}
//	template<class... Ts> decltype(auto) operator()(Ts&&... t) &     {assert(0); return ref::operator()(std::forward<Ts>(t)...);}
//	template<class... Ts> decltype(auto) operator()(Ts&&... t) &&    {return std::move(*this).ref::operator()(std::forward<Ts>(t)...);}
//	template<class... Ts> decltype(auto) operator()(Ts&&... t) const&{return ref::operator()(std::forward<Ts>(t)...);}

//	template<class... Ts> decltype(auto) operator()(Ts&&... t) const{return static_array_cast<typename static_array::element_type const>(*this)(std::forward<Ts>(t)...);}

#if 0
	template<class... As> decltype(auto) paren(index a, As... as) &     {return ref::paren(a, std::forward<As>(as)...);}
	template<class... As> decltype(auto) paren(index a, As... as) && = delete;//{return ref::operator()(a, std::forward<As>(as)...);}
	template<class... As> decltype(auto) paren(index a, As... as) const&{return ref::paren(a, std::forward<As>(as)...);}

	template<class... As> decltype(auto) paren(index_range a, As... as) &{return ref::paren(a, std::forward<As>(as)...);}
	template<class... As> decltype(auto) paren(index_range a, As... as) && = delete;//{return ref::operator()(a, std::forward<As>(as)...);}
	template<class... As> decltype(auto) paren(index_range a, As... as) const&{return ref::paren(a, std::forward<As>(as)...);}

	decltype(auto) operator()(index i) & {return operator[](i);}
	decltype(auto) operator()(index i) && {return std::move(*this).operator[](i);}
	decltype(auto) operator()(index i) const& {return operator[](i);}
//#define SARRAY1(A1) auto operator()(A1 a1) const{return operator()<>(a1);}
#define SARRAY2(A1, A2)	\
	auto operator()(A1 a1, A2 a2) const& JUSTRETURN(paren<A2>(a1, a2)) \
	auto operator()(A1 a1, A2 a2) && = delete;/*     JUSTRETURN(std::move(*this).static_array::template paren<A2>(a1, a2))*/ \
	auto operator()(A1 a1, A2 a2) &      JUSTRETURN(paren<A2>(a1, a2))
	SARRAY2(index, index ); SARRAY2(irange, index );
	SARRAY2(index, irange); SARRAY2(irange, irange);
#undef SARRAY2
#if 0
#define SARRAY3(A1, A2, A3) auto operator()(A1 a1, A2 a2, A3 a3) const{return operator()<A2, A3>(a1, a2, a3);} auto operator()(A1 a1, A2 a2, A3 a3){return operator()<A2, A3>(a1, a2, a3);}
	SARRAY3(index, index , index ); SARRAY3(irange, index , index );
	SARRAY3(index, index , irange); SARRAY3(irange, index , irange);
	SARRAY3(index, irange, index ); SARRAY3(irange, irange, index );
	SARRAY3(index, irange, irange); SARRAY3(irange, irange, irange);
#undef SARRAY3
#define SARRAY4(A1, A2, A3, A4) auto operator()(A1 a1, A2 a2, A3 a3, A4 a4) const{return operator()<A2, A3, A4>(a1, a2, a3, a4);} auto operator()(A1 a1, A2 a2, A3 a3, A4 a4) {return operator()<A2, A3, A4>(a1, a2, a3, a4);}
	SARRAY4(index, index, index , index ); SARRAY4(index, irange, index , index );
	SARRAY4(index, index, index , irange); SARRAY4(index, irange, index , irange);
	SARRAY4(index, index, irange, index ); SARRAY4(index, irange, irange, index );
	SARRAY4(index, index, irange, irange); SARRAY4(index, irange, irange, irange);
	SARRAY4(irange, index, index , index ); SARRAY4(irange, irange, index , index );
	SARRAY4(irange, index, index , irange); SARRAY4(irange, irange, index , irange);
	SARRAY4(irange, index, irange, index ); SARRAY4(irange, irange, irange, index );
	SARRAY4(irange, index, irange, irange); SARRAY4(irange, irange, irange, irange);
#undef SARRAY4
#endif
#endif
//	using const_reverse_iterator = basic_reverse_iterator<const_iterator>;
	constexpr auto rotated(dimensionality_type d = 1) const&{
		typename static_array::layout_t new_layout = *this;
		new_layout.rotate(d);
		return basic_array<T, D, typename static_array::element_const_ptr>{new_layout, this->base_};
	}
	constexpr auto rotated(dimensionality_type d = 1)&{
		typename static_array::layout_t new_layout = *this;
		new_layout.rotate(d);
		return basic_array<T, D, typename static_array::element_ptr>{new_layout, this->base_};
	}
	constexpr auto rotated(dimensionality_type d = 1)&&{
		typename static_array::layout_t new_layout = *this;
		new_layout.rotate(d);
		return basic_array<T, D, typename static_array::element_ptr>{new_layout, this->base_};
	}
//	friend decltype(auto) rotated(static_array const& self){return self.rotated();}
//	template<class Array, typename = std::enable_if_t<std::is_same<static_array, std::decay_t<Array>>{}> >
	friend constexpr decltype(auto) rotated(static_array&       s){return s.rotated();}
	friend constexpr decltype(auto) rotated(static_array const& s){return s.rotated();}

	constexpr auto unrotated(dimensionality_type d = 1) const&{
		typename static_array::layout_t new_layout = *this;
		new_layout.unrotate(d);
		return basic_array<T, D, typename static_array::element_const_ptr>{new_layout, this->base_};
	}
	constexpr auto unrotated(dimensionality_type d = 1)&{
		typename static_array::layout_t new_layout = *this;
		new_layout.unrotate(d);
		return basic_array<T, D, typename static_array::element_ptr>{new_layout, this->base_};
	}
	friend constexpr decltype(auto) unrotated(static_array& self){return self.unrotated();}
	friend constexpr decltype(auto) unrotated(static_array const& self){return self.unrotated();}

	constexpr decltype(auto) operator<<(dimensionality_type d)      {return   rotated(d);}
	constexpr decltype(auto) operator>>(dimensionality_type d)      {return unrotated(d);}
	constexpr decltype(auto) operator<<(dimensionality_type d) const{return   rotated(d);}
	constexpr decltype(auto) operator>>(dimensionality_type d) const{return unrotated(d);}

	static_array& operator=(static_array const& other) &{
		assert( extensions(other) == static_array::extensions() );
		adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements());
		return *this;
	}
	template<class TT, class... As>
	static_array& operator=(static_array<TT, static_array::dimensionality, As...> const& other)&{assert( extensions(other) == static_array::extensions() );
		adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements());
		return *this;
	}
//	template<class... As>
//	static_array operator=(static_array<static_array::value_type, static_array::dimensionality, As...> const& o){assert( extensions(o) == static_array::extensions() );
//		return adl::copy_elements(o.data_elements()), *this;
//	}
	constexpr operator basic_array<typename static_array::value_type, static_array::dimensionality, typename static_array::element_const_ptr, typename static_array::layout_t>()&{
		return this->template static_array_cast<typename static_array::value_type, typename static_array::element_const_ptr>(*this);
//		return static_array_cast<typename static_array::value_type, typename static_array::element_const_ptr>(*this);
	}
};

template<class T, class Alloc>
struct static_array<T, dimensionality_type{0}, Alloc> :
	protected array_allocator<Alloc>,
	public array_ref<T, 0, typename std::allocator_traits<typename array_allocator<Alloc>::allocator_type>::pointer>
{
private:
	using array_alloc = array_allocator<Alloc>;
public:
	static_assert( std::is_same<typename std::allocator_traits<Alloc>::value_type, typename static_array::element>{},
		"allocator value type must match array value type");
	using array_alloc::get_allocator;
	using allocator_type = typename static_array::allocator_type;
protected:
	using alloc_traits = typename std::allocator_traits<allocator_type>;
	using ref = array_ref<T, 0, typename std::allocator_traits<typename std::allocator_traits<Alloc>::template rebind_alloc<T>>::pointer>;
	auto uninitialized_value_construct(){return adl_alloc_uninitialized_value_construct_n(static_array::alloc(), this->base_, this->num_elements());}
	template<typename It> auto uninitialized_copy(It first){return adl_alloc_uninitialized_copy_n(this->alloc(), first, this->num_elements(), this->data_elements());}
	template<typename It>
	auto uninitialized_move(It first){
		return adl_alloc_uninitialized_move_n(this->alloc(), first, this->num_elements(), this->data_elements());
	}
	void destroy(){array_alloc::destroy_n(this->data_elements(), this->num_elements());}
public:
	using typename ref::value_type;
	using typename ref::size_type;
	using typename ref::difference_type;
	constexpr explicit static_array(allocator_type const& a) : array_alloc{a}{}
protected:
	constexpr static_array(static_array&& other, allocator_type const& a)                           //6b
	:	array_alloc{a},
		ref{other.base_, other.extensions()}
	{
		other.ref::layout_t::operator=({});
	}
public:
	using ref::operator==;
	using ref::operator!=;

	template<class TT,
		std::enable_if_t<std::is_constructible<T, TT>{}, int>* = nullptr,
		class = decltype(adl_uninitialized_copy_n(&std::declval<TT&>(), 1, std::declval<typename static_array::element_ptr&>()))
	>
	// cppcheck-suppress noExplicitConstructor ; because I want to use equal for lazy assigments form range-expressions
	static_array(TT&& tt) : ref(static_array::allocate(typename static_array::layout_t{}.num_elements()), {}){
		adl_uninitialized_copy_n(&tt, 1, this->base());
	}
//	template<class Range0,
//		std::enable_if_t<std::is_constructible<typename static_array::element, typename Range0::element>{}, int>* = nullptr,
//		class = decltype(adl_uninitialized_copy_n(&std::declval<Range0&>(), 1, std::declval<typename static_array::element_ptr&>()))
//	>
////	template<class Range0, class = decltype(adl_uninitialized_copy_n(&std::declval<Range0&>(), 1, std::declval<typename static_array::element_ptr&>()))>
//	// cppcheck-suppress noExplicitConstructor ; because I want to use equal for lazy assigments form range-expressions
//	static_array(Range0&& r) : ref(static_array::allocate(typename static_array::layout_t{}.num_elements()), {}){
//		adl_uninitialized_copy_n(&r, 1, this->base());
//	}
	static_array(typename static_array::extensions_type x, typename static_array::element const& e, allocator_type const& a) : //2
		array_alloc{a},
		ref(static_array::allocate(typename static_array::layout_t{x}.num_elements()), x)
	{
		uninitialized_fill(e);
	}
	static_array(typename static_array::element_type const& e, allocator_type const& a)
		: static_array(typename static_array::extensions_type{}, e, a){}
	auto uninitialized_fill(typename static_array::element const& e){array_alloc::uninitialized_fill_n(this->base_, this->num_elements(), e);}
	static_array(typename static_array::extensions_type const& x, typename static_array::element const& e)  //2
		: array_alloc{}, ref(static_array::allocate(typename static_array::layout_t{x}.num_elements()), x)
	{
		uninitialized_fill(e);
	}

	static_array() : static_array(multi::iextensions<0>{}){}

	explicit static_array(typename static_array::element const& e)  //2
		: static_array(multi::iextensions<0>{}, e)
	{}

	template<class ValueType, typename = std::enable_if_t<std::is_same<ValueType, typename static_array::value_type>{}>>
	explicit static_array(typename static_array::index_extension const& e, ValueType const& v, allocator_type const& a = {}) //3
		: static_array(e*extensions(v), a)
	{
		using std::fill; fill(this->begin(), this->end(), v);
	}

	explicit static_array(typename static_array::extensions_type const& x, allocator_type const& a = {}) //3
	: array_alloc{a}, ref{static_array::allocate(typename static_array::layout_t{x}.num_elements()), x}{
		if(not std::is_trivially_default_constructible<typename static_array::element>{}) uninitialized_value_construct();
	}
	template<class TT, class... Args>
	// cppcheck-suppress noExplicitConstructor ; because argument can be well-represented
	static_array(multi::basic_array<TT, 0, Args...> const& other, allocator_type const& a = {})
		: array_alloc{a}, ref(static_array::allocate(other.num_elements()), extensions(other))
	{
		using std::copy; copy(other.begin(), other.end(), this->begin());
	}
	template<class TT, class... Args>
	// cppcheck-suppress noExplicitConstructor ; because argument can be well-represented
	static_array(array_ref<TT, 0, Args...> const& other)
	:	array_alloc{}, ref{static_array::allocate(other.num_elements()), extensions(other)}{
		uninitialized_copy_(other.data_elements());
	}
	static_array(static_array const& other, allocator_type const& a)                      //5b
	:	array_alloc{a}, ref{static_array::allocate(other.num_elements()), extensions(other)}{
	//	assert(0);
		uninitialized_copy_(other.data_elements());
	}
	static_array(static_array const& o) :                                  //5b
		array_alloc{o.get_allocator()},
		ref{static_array::allocate(o.num_elements()), o.extensions()}
	{
		uninitialized_copy(o.data_elements());
	}
	static_array(static_array&& o) :       // it is private because it is a valid operation for derived classes //5b
		array_alloc{o.get_allocator()},
		ref{static_array::allocate(o.num_elements()), o.extensions()}
	{
		uninitialized_move(o.data_elements()); // TODO: uninitialized_move?
	}
	template<class It> static auto distance(It a, It b){using std::distance; return distance(a, b);}
protected:
	void deallocate(){ // TODO move this to array_allocator
		if(this->num_elements()) alloc_traits::deallocate(this->alloc(), this->base_, static_cast<typename alloc_traits::size_type>(this->num_elements()));
	}
	void clear() noexcept{
		this->destroy();
		deallocate();
		layout_t<0>::operator=({});
	}
public:
//	static_array() = default;
	~static_array() noexcept{
		this->destroy();
		deallocate();
	}
	using element_const_ptr = typename std::pointer_traits<typename static_array::element_ptr>::template rebind<typename static_array::element const>;
	friend allocator_type get_allocator(static_array const& self){return self.get_allocator();}

//	[[deprecated("use data_elements() instead of data()")]]
//	constexpr typename static_array::element_ptr       data()      {return ref::data_elements();}
//	[[deprecated("use data_elements() instead of data()")]]
//	constexpr auto data() const{return typename static_array::element_const_ptr{ref::data_elements()};}

	// TODO find how to use `deprecated` with nvcc
//	friend constexpr typename static_array::element_ptr       data(static_array&       s)
//	{return s.data_elements();}
//	friend constexpr typename static_array::element_const_ptr data(static_array const& s)
//	{return s.data_elements();}

	       constexpr typename static_array::element_ptr       base()                 &   {return ref::base();}
	       constexpr typename static_array::element_const_ptr base()            const&   {return ref::base();}
	friend constexpr typename static_array::element_ptr       base(static_array&       s){return s.base();}
	friend constexpr typename static_array::element_const_ptr base(static_array const& s){return s.base();}

	constexpr typename static_array::element_ptr       origin()      {return ref::origin();}
	constexpr typename static_array::element_const_ptr origin() const{return ref::origin();}
	friend constexpr typename static_array::element_ptr       origin(static_array&       s){return s.origin();}
	friend constexpr typename static_array::element_const_ptr origin(static_array const& s){return s.origin();}

//	template<class... Args> decltype(auto) operator()(Args const&... args)&{return ref::operator()(args...);}
//	template<class... Args> decltype(auto) operator()(Args const&... args) const&{return ref::operator()(args...);}

//	using ref::operator typename static_array::element_ref;//{return *(this->base_);}
//	operator decltype(auto)(){return *(this->base_);}
//	operator decltype(auto)() const{return *(this->base_);}
//	operator typename static_array::element_type() const{return *(this->base_);}
//	using ref::operator();
//	operator typename static_array::element_ref() const& = delete;//{return *(this->base_);}
	constexpr operator typename std::iterator_traits<typename static_array::element_const_ptr>::reference() const&{
		return *(this->base_);
	}
	constexpr operator typename std::add_rvalue_reference<typename std::iterator_traits<typename static_array::element_ptr>::reference>::type()&&{
		return *(this->base_);
	}
	constexpr operator typename std::iterator_traits<typename static_array::element_ptr>::reference()&{
		return *(this->base_);
	}
	constexpr explicit operator typename std::iterator_traits<typename static_array::element_const_ptr>::value_type(){
		return *(this->base_);
	}
//	basic_array<T, D, typename static_array::element_ptr>
//	decltype(auto) operator()()&{
//		return ref::operator()();
	//	return *this;
//	}
//	basic_array<T, 0, typename static_array::element_const_ptr> operator()() const&{
//		return basic_array<T, 0, typename static_array::element_const_ptr>{this->layout(), this->base_};
//	}

//	typename std::iterator_traits<typename static_array::element_const_ptr>::reference operator()() const&{
//		return *(this->base_);
//	}

//	using const_reverse_iterator = basic_reverse_iterator<const_iterator>;
	constexpr auto rotated(dimensionality_type d = 1) const&{
		typename static_array::layout_t new_layout = *this;
		new_layout.rotate(d);
		return basic_array<T, 0, typename static_array::element_const_ptr>{new_layout, this->base_};
	}
	constexpr auto rotated(dimensionality_type d = 1)&{
		typename static_array::layout_t new_layout = *this;
		new_layout.rotate(d);
		return basic_array<T, 0, typename static_array::element_ptr>{new_layout, this->base_};
	}
	constexpr auto rotated(dimensionality_type d = 1)&&{
		typename static_array::layout_t new_layout = *this;
		new_layout.rotate(d);
		return basic_array<T, 0, typename static_array::element_ptr>{new_layout, this->base_};
	}
//	friend decltype(auto) rotated(static_array const& self){return self.rotated();}
//	template<class Array, typename = std::enable_if_t<std::is_same<static_array, std::decay_t<Array>>{}> >
	friend constexpr decltype(auto) rotated(static_array&       s){return s.rotated();}
	friend constexpr decltype(auto) rotated(static_array const& s){return s.rotated();}

	constexpr auto unrotated(dimensionality_type d = 1) const&{
		typename static_array::layout_t new_layout = *this;
		new_layout.unrotate(d);
		return basic_array<T, 0, typename static_array::element_const_ptr>{new_layout, this->base_};
	}
	constexpr auto unrotated(dimensionality_type d = 1)&{
		typename static_array::layout_t new_layout = *this;
		new_layout.unrotate(d);
		return basic_array<T, 0, typename static_array::element_ptr>{new_layout, this->base_};
	}
	friend constexpr decltype(auto) unrotated(static_array& self){return self.unrotated();}
	friend constexpr decltype(auto) unrotated(static_array const& self){return self.unrotated();}

	constexpr decltype(auto) operator<<(dimensionality_type d){return rotated(d);}
	constexpr decltype(auto) operator>>(dimensionality_type d){return unrotated(d);}
	constexpr decltype(auto) operator<<(dimensionality_type d) const{return rotated(d);}
	constexpr decltype(auto) operator>>(dimensionality_type d) const{return unrotated(d);}

	static_array& operator=(static_array const& other){assert( extensions(other) == static_array::extensions() );
		adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements());
		return *this;
	}
	template<class TT, class... As>
	constexpr static_array& operator=(static_array<TT, static_array::dimensionality, As...> const& other)&{assert( extensions(other) == static_array::extensions() );
		adl_copy_n(other.data_elements(), other.num_elements(), this->data_elements());
		return *this;
	}

	constexpr operator basic_array<typename static_array::value_type, static_array::dimensionality, typename static_array::element_const_ptr, typename static_array::layout_t>()&{
		return this->template static_array_cast<typename static_array::value_type, typename static_array::element_const_ptr>();
	//	return static_array_cast<typename static_array::value_type, typename static_array::element_const_ptr>(*this);
	}

	template<class Archive>
	auto serialize(Archive& ar, const unsigned int version){
//		auto extensions = this->extensions();
//		using boost::serialization::make_nvp;
//		ar & make_nvp("extensions", extensions);
//		if(extensions != this->extensions()){clear(); this->reextent(extensions);}
//		assert(extensions == this->extensions());
		std::move(*this).ref::serialize(ar, version);
	}
};

template<typename T, class Alloc>
struct array<T, dimensionality_type{0}, Alloc>
	: static_array<T, 0, Alloc>
{
	using static_ = static_array<T, 0, Alloc>;
	using static_::static_;
	void reextent(typename array::extensions_type const&){}
};

template<class T, dimensionality_type D, class Alloc>
struct array : static_array<T, D, Alloc>,
	boost::multi::random_iterable<array<T, D, Alloc> >
{
	using static_ = static_array<T, D, Alloc>;
	static_assert(std::is_same<typename array::alloc_traits::value_type, T>{} or std::is_same<typename array::alloc_traits::value_type, void>{}, "!");
public:
//	array_ptr<T, D, typename array::element_const_ptr> operator&() const&{return {this->base(), this->extensions()};}
//	array_ptr<T, D, typename array::element_ptr> operator&() &{return {this->base(), this->extensions()};}
//	array_ptr<T, D, typename array::element_ptr> operator&() && = delete;
	template<class Archive>
	auto serialize(Archive& ar, const unsigned int version){
		auto extensions = this->extensions();
		ar & multi::archive_traits<Archive>::make_nvp("extensions", extensions);
		if(extensions != this->extensions()){clear(); this->reextent(extensions);}
		assert(extensions == this->extensions());
		static_::serialize(ar, version);
	}
	using static_::static_;
	using typename static_::value_type;
	array() = default;
	array(array const&) = default;
	array& reshape(typename array::extensions_type x) &{
		typename array::layout_t new_layout{x};
		assert( new_layout.num_elements() == this->num_elements() );
		static_cast<typename array::layout_t&>(*this)=new_layout;
		return *this;
	}
	using static_::clear;
	friend void clear(array& self) noexcept{self.clear();}

	friend auto data_elements(array const& self){return self.data_elements();}
	friend auto data_elements(array      & self){return self.data_elements();}
	friend auto data_elements(array     && self){return std::move(self).data_elements();}

	basic_array<typename array::element, array::dimensionality, multi::move_ptr<typename array::element> >
	move() &{
		basic_array<typename array::element, array::dimensionality, multi::move_ptr<typename array::element> >
		ret = multi::static_array_cast<typename array::element, multi::move_ptr<typename array::element>>(*this);
		layout_t<array::dimensionality>::operator=({});
		return ret;
	}
	friend
	basic_array<typename array::element, array::dimensionality, multi::move_ptr<typename array::element> >
	move(array& self){return self.move();}

//	explicit
//	array(array const& other)                                              // 5a
//	:	allocator_type{other}, ref{allocate(other.num_elements()), extensions(other)}{
//		uninitialized_copy_(other.data());
//	}
//	explicit
//s	using static_::static_array;
//	template<class Array> array(Array const& arr) : static_{arr}{}
//	using static_::static_;
//	template<class... As>
//	array(typename array::extensions_type x, As&&... as) : static_{x, std::forward<As>(as)...}{} //2
//	array(array const& other) : static_{static_cast<static_ const&>(other)}{}
	array(array&& o, typename array::allocator_type const& a) noexcept : static_{std::move(o), a}{}
	array(array&& o) noexcept : array{std::move(o), o.get_allocator()}{}
	friend typename array::allocator_type get_allocator(array const& self){return self.get_allocator();}
#if 0
	template<class A//, typename = std::enable_if_t<not std::is_base_of<array, std::decay_t<A>>{}>,
//		typename = std::enable_if_t<not std::is_convertible<std::decay_t<A>, typename array::element_type>{}>
	>
	array& operator=(A&& a){
		auto ext = extensions(a);
		if(ext==array::extensions()){
		//	const_cast<array const&>
			std::move(*this).static_::ref::operator=(std::forward<A>(a));
		}else{
			this->clear(); //	this->ref::layout_t::operator=(layout_t<D>{extensions(a)}); //			this->base_ = allocate(this->num_elements());
			this->base_ = this->allocate(static_cast<typename array::alloc_traits::size_type>(this->static_::ref::layout_t::operator=(layout_t<D>{extensions(a)}).num_elements()));
			using std::begin; using std::end;
		//	alloc_uninitialized_copy(this->alloc(), begin(std::forward<A>(a)), end(std::forward<A>(a)), array::begin()); //	recursive_uninitialized_copy<D>(alloc(), begin(std::forward<A>(a)), end(std::forward<A>(a)), array::begin());
			adl::alloc_uninitialized_copy(this->alloc(), begin(std::forward<A>(a)), end(std::forward<A>(a)), array::begin()); //	recursive_uninitialized_copy<D>(alloc(), begin(std::forward<A>(a)), end(std::forward<A>(a)), array::begin());
		}
		return *this;
	}
#endif
#if 0
	template<class... As>
	array& operator=(array<array::value_type, array::dimensionality, As...> const& o){
		if(o.extensions()==array::extensions()) return static_::operator=(o);
		return operator=(array{o});
	/*
		else{
			array::destroy();
			array::deallocate();
			this->static_::ref::layout_t::operator=(layout_t<D>{extensions(o)});
			array::allocate();//array::num_elements());
			array::uninitialized_copy_elements(data_elements(o));//adl::alloc_uninitialized_copy_n(data_elements(o), num_elements(o), array::data_elements());
		}*/
	//	return *this;
	}
	template<class OtherT, class... As>
	auto operator=(array<OtherT, array::dimensionality, As...> const& o) &{
		if(extensions(o)==array::extensions()){
			static_::operator=(o);
		}else{
			array::destroy(); array::deallocate();
			this->static_::ref::layout_t::operator=(layout_t<D>{extensions(o)});
			array::allocate(); array::uninitialized_copy_elements(data_elements(o));
		}
		return *this;
	}
//	array& operator=(array other) noexcept{swap(other);} //TODO consider this
#endif
	void swap(array& other) noexcept{
		using std::swap;
		swap(this->alloc(), other.alloc());
		swap(this->base_, other.base_);
		swap(
			static_cast<typename array::layout_t&>(*this),
			static_cast<typename array::layout_t&>(other)
		);
	}
#ifndef NOEXCEPT_ASSIGNMENT
	array& operator=(array&& other) noexcept{
		using std::exchange;
		clear();
		this->base_ = exchange(other.base_, nullptr);
		this->alloc() = std::move(other.alloc());
		static_cast<typename array::layout_t&>(*this) = exchange(static_cast<typename array::layout_t&>(other), {});
		return *this;
	}
	array& operator=(array const& o){
		if(array::extensions() == o.extensions()) static_::operator=(o);
		else operator=(array{o});
		return *this;
	}
#else
	array& operator=(array o) noexcept{return swap(o), *this;}
#endif
	template<class Range, class=std::enable_if_t<not std::is_base_of<array, std::decay_t<Range>>{}> >
	auto operator=(Range&& o) // check that LHS is not read-only
	->decltype(                                         static_::operator=(o)                     , std::declval<array&>()){
		return ((array::extensions() == o.extensions())?static_::operator=(o):operator=(array(o))), *this                 ;}

	array& operator=(basic_array<T, D, multi::move_ptr<typename array::element, typename array::element_ptr>>& other){
		if(other.layout() != this->layout()) return array::operator=(other.template static_array_cast<typename array::element, typename array::element_ptr>());
		if(this->base_ != other.base_) other.base_ = nullptr;
		return *this;
	}
	friend void swap(array& a, array& b){a.swap(b);}
	void assign(typename array::extensions_type x, typename array::element const& e){
		if(array::extensions()==x){
			fill_n(this->base_, this->num_elements(), e);
		}else{
			this->clear();
			this->layout_t<D>::operator=(layout_t<D>{x});
			this->base_ = this->allocate();
			uninitialized_fill_n(e);
		//	recursive_uninitialized_fill<dimensionality>(alloc(), begin(), end(), e);
		}
	}
//	template<class It, class Size> It assign_n(It first, Size n){
//		if(n == array::size() and multi::extensions(*first) == multi::extensions(*array::begin())){
//			return static_::ref::assign(first);
//		}
//		this->
//	}
	template<class It>
	array& assign(It first, It last){using std::next; using std::all_of;
	//	auto const s = adl::distance(first, last);
		if(adl_distance(first, last) == array::size()){// and multi::extensions(*first) == multi::extensions(*array::begin())){
			static_::ref::assign(first);
		}else{
			this->operator=(array(first, last));
		/*
			this->clear();
			this->layout_t<D>::operator=(layout_t<D>{std::tuple_cat(std::make_tuple(index_extension{array::extension().front(), array::extension().front() + distance(first, last)}), multi::extensions(*first))});
			using std::next;
			using std::all_of;
			if(first!=last) assert( all_of(next(first), last, [x=multi::extensions(*first)](auto& e){return extensions(e)==x;}) );
			this->base_ = this->allocate();
			multi::uninitialized_copy<D>(first, last, array::begin());
		*/
		}
		return *this;
	}
	void assign(std::initializer_list<typename array::value_type> il){assign(il.begin(), il.end());}
	template<class Range> auto assign(Range&& r) &
	->decltype(assign(adl_begin(r), adl_end(r))){
		return assign(adl_begin(r), adl_end(r));}
	array& operator=(std::initializer_list<typename array::value_type> il){assign(il.begin(), il.end()); return *this;}

	template<class TT, class... Args>
	bool operator==(basic_array<TT, D, Args...> const& other) const{
		return static_::operator==(other);
	}

	void reextent(typename array::extensions_type const& x){
		if(x == this->extensions()) return;
		array tmp(x, this->get_allocator());
		auto const is = intersection(this->extensions(), x);
		tmp.apply(is) = this->apply(is);
		swap(tmp);
	}
	void reextent(typename array::extensions_type const& x, typename array::element const& e){
		if(x == this->extensions()) return;
		array tmp(x, e, this->get_allocator());
		auto const is = intersection(this->extensions(), x);
		tmp.apply(is) = this->apply(is);
		swap(tmp);
	}
	template<class... Ts> constexpr array&& reindex(Ts... a)&&{array::layout_t::reindex(a...); return std::move(*this);}
	template<class... Ts> constexpr array&  reindex(Ts... a)& {array::layout_t::reindex(a...); return           *this ;}
	~array() noexcept = default;
};

#if defined(__cpp_deduction_guides)
// clang cannot recognize templated-using, so don't replace IL<IL<T>> by IL2<T>, etc
//#ifndef __clang__
//template<class T, dimensionality_type D, class A=std::allocator<T>> static_array(multi::initializer_list_t<T, D>, A={})->static_array<T, D, A>;
//template<class T, dimensionality_type D, class A=std::allocator<T>> array(multi::initializer_list_t<T, D>, A={})->array<T, D, A>;
//#else
#define IL std::initializer_list
	template<class T, class A=std::allocator<T>> static_array(IL<T>                , A={})->static_array<T,1,A>;
	template<class T, class A=std::allocator<T>> static_array(IL<IL<T>>            , A={})->static_array<T,2,A>;
	template<class T, class A=std::allocator<T>> static_array(IL<IL<IL<T>>>        , A={})->static_array<T,3,A>;
	template<class T, class A=std::allocator<T>> static_array(IL<IL<IL<IL<T>>>>    , A={})->static_array<T,4,A>;
	template<class T, class A=std::allocator<T>> static_array(IL<IL<IL<IL<IL<T>>>>>, A={})->static_array<T,5,A>;

//	template<class T, class A=std::allocator<T>> array(IL<T>                , A={})->array<T,1,A>;
	template<class T, class A=std::allocator<T>> array(IL<IL<T>>            , A={})->array<T,2,A>;
	template<class T, class A=std::allocator<T>> array(IL<IL<IL<T>>>        , A={})->array<T,3,A>;
	template<class T, class A=std::allocator<T>> array(IL<IL<IL<IL<T>>>>    , A={})->array<T,4,A>;
	template<class T, class A=std::allocator<T>> array(IL<IL<IL<IL<IL<T>>>>>, A={})->array<T,5,A>;

	template<class T> array(std::initializer_list<T>)->array<T, 1>;
#undef IL
//#endif

template<class T, class A=std::allocator<T>> array(T[]                  , A={})->array<T,1,A>;

//template<class Array, class E = typename multi::array_traits<Array>::element, class A=std::allocator<E>, class=std::enable_if_t<is_allocator<A>{}>> array(Array            , A={})->array<typename multi::array_traits<Array>::element, 1, A>;

template<dimensionality_type D, class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<D>, T)->array<T, D, std::allocator<T>>;
	template<class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<0>, T)->array<T,0, std::allocator<T>>;
	template<class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<1>, T)->array<T,1, std::allocator<T>>;
	template<class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<2>, T)->array<T,2, std::allocator<T>>;
	template<class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<3>, T)->array<T,3, std::allocator<T>>;
	template<class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<4>, T)->array<T,4, std::allocator<T>>;
	template<class T, class=std::enable_if_t<not is_allocator<T>{}> > array(iextensions<5>, T)->array<T,5, std::allocator<T>>;

template<dimensionality_type D, class T, class A, typename = std::enable_if_t<std::is_same<typename std::allocator_traits<A>::value_type, T>{}> > static_array(iextensions<D>, T, A)->static_array<T, D, A>;
	template<class T, class A, typename = std::enable_if_t<std::is_same<typename std::allocator_traits<A>::value_type, T>{}>> static_array(T, A)->static_array<T, 0, A>;
//	template<class T, class A, typename = std::enable_if_t<not is_allocator<T>{}> > static_array(iextensions<1>, T, A = {})->static_array<T, 1, A>;

template<dimensionality_type D, class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<D>, A)->array<T, D, A>;
	template<class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<0>, A)->array<T, 0, A>;
	template<class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<1>, A)->array<T, 1, A>;
	template<class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<2>, A)->array<T, 2, A>;
	template<class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<3>, A)->array<T, 3, A>;
	template<class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<4>, A)->array<T, 4, A>;
	template<class A, class=std::enable_if_t<is_allocator<A>{}>, typename T = typename std::allocator_traits<A>::value_type> array(iextensions<5>, A)->array<T, 5, A>;

	template<class T> array(iextensions<0>, T)->array<T, 0>;
	template<class T> array(iextensions<1>, T)->array<T, 1>; template<class T> array(multi::size_type, T)->array<T, 1>;
	template<class T> array(iextensions<2>, T)->array<T, 2>;
	template<class T> array(iextensions<3>, T)->array<T, 3>;

template<class T, class MR, class A=memory::allocator<T, MR>> array(iextensions<1>, T, MR*)->array<T, 1, A>;
template<class T, class MR, class A=memory::allocator<T, MR>> array(iextensions<2>, T, MR*)->array<T, 2, A>;
template<class T, class MR, class A=memory::allocator<T, MR>> array(iextensions<3>, T, MR*)->array<T, 3, A>;
template<class T, class MR, class A=memory::allocator<T, MR>> array(iextensions<4>, T, MR*)->array<T, 4, A>;
template<class T, class MR, class A=memory::allocator<T, MR>> array(iextensions<5>, T, MR*)->array<T, 5, A>;

template<class MatrixRef, class DT = typename MatrixRef::decay_type, class T = typename DT::element, dimensionality_type D = DT::dimensionality, class Alloc = typename DT::allocator_type>
array(MatrixRef)->array<T, D, Alloc>;

template<typename T, dimensionality_type D, typename P> array(basic_array<T, D, P>)->array<T, D>;
#endif

template <class T, std::size_t N>
multi::array<typename std::remove_all_extents<T[N]>::type, std::rank<T[N]>{}>
decay(const T(&t)[N]) noexcept{
	return multi::array_cref<typename std::remove_all_extents<T[N]>::type, std::rank<T[N]>{}>(data_elements(t), extensions(t));
}

template<class T, size_t N>
struct array_traits<T[N], void, void>{
	using reference = T&;
	using element = std::remove_all_extents_t<T[N]>;
	using decay_type = multi::array<T, 1>;
};

}}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

#if defined(__INCLUDE_LEVEL__) and not __INCLUDE_LEVEL__

#include<cassert>
#include<numeric> // iota
#include<iostream>
#include<algorithm>
#include<vector>

#include <random>
#include <boost/timer/timer.hpp>
#include<boost/multi_array.hpp>

using std::cout;
namespace multi = boost::multi;

#if 1
template<class Matrix, class Vector>
void solve(Matrix& m, Vector& y){
//	using std::size; // assert(size(m) == std::ptrdiff_t(size(y)));
	std::ptrdiff_t msize = size(m);
	for(auto r = 0; r != msize; ++r){ //	auto mr = m[r]; //  auto const mrr = mr[r];// assert( mrr != 0 ); // m[r][r] = 1;
		auto mr = m[r];
		auto mrr = mr[r];
		for(auto c = r + 1; c != msize; ++c) mr[c] /= mrr;
		auto yr = (y[r] /= mrr);
		for(auto r2 = r + 1; r2 != msize; ++r2){ //	auto mr2 = m[r2]; //	auto const mr2r = mr2[r]; // m[r2][r] = 0;
			auto mr2 = m[r2];
			auto const& mr2r = mr2[r];
			auto const& mr_cr = m[r];
			for(auto c = r + 1; c != msize; ++c) mr2[c] -= mr2r*mr_cr[c];
			y[r2] -= mr2r*yr;
		}
	}
	for(auto r = msize - 1; r > 0; --r){ //	auto mtr = m.rotated(1)[r];
		auto const& yr = y[r];
		for(auto r2 = r-1; r2 >=0; --r2)
			y[r2] -= yr*m[r2][r];
	}
}
#endif

void f(boost::multi::array<double, 4> const& A){
	A[1][2];
	auto&& a = A[1][2]; (void)a; // careful, a is a reference here, don't use auto,
	auto const& b = A[1][2]; (void)b; // use auto const& if possible
//	A[1][2][3][4] = 5; // fail, element is read-only
}

template<class C>
void set_99(C&& c){
	for(auto j : c.extension(0))
		for(auto k : c.extension(1))
				c[j][k] = 99.;
}

namespace multi = boost::multi;

template<class T> void fun(T const& t){
	std::cout << typeid(t).name() << std::endl;
}

template<class T> struct extension{};
template<class T> void gun(extension<T>){
	std::cout << typeid(T).name() << std::endl;
}

typedef double a1010[10][10];

struct A{
	double const* p;
	A(std::initializer_list<double> il){ p = &*(il.begin() + 1); };
};

template<class T> void what(T&&) = delete;

int main(){

{
	multi::array<double, 1> A1 = {1.,2.,3.};
	assert( A1.dimensionality==1 and A1.num_elements()==3 );

	multi::array<double, 2> A2 {
		 {1.,2.,3.},
		 {4.,5.,6.}
	};
	*A2.begin()->begin() = 99;
	assert(A2[0][0] == 99 );
}
{
	double A[2][3] = {{1.,2.,3.}, {4.,5.,6.}};
	using multi::decay;
	auto A_copy = decay(A);
}
	{
		multi::array<double, 2> A = {
			{1},
			{2},
			{3}
		};
		assert( size(A) == 3 );
		assert( size(rotated(A)) == 1 );
		assert( stride(A) == 1 );
		assert( stride(rotated(A)) == 1 );
		assert( A.extensions() );
	}
	{
		multi::array<double, 1> A = {1};
		assert( size(A) == 1 );
		assert( stride(A) == 1 );
	}
	{
		multi::array<double, 0> A = 3.;
	//	assert( stride(A) == 1 );
	}
	{
		double D3[3] = {0, 1, 2};
		multi::array<double, 1> A(D3);
		assert( A[1] == 1 );
	}
}
#endif
#endif