#ifndef BOOST_MULTI_ARRAY_GENERATIVE_TESTS_HPP
#define BOOST_MULTI_ARRAY_GENERATIVE_TESTS_HPP

// Copyright 2002 The Trustees of Indiana University.

// Use, modification and distribution is subject to the Boost Software 
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  Boost.MultiArray Library
//  Authors: Ronald Garcia
//           Jeremy Siek
//           Andrew Lumsdaine
//  See http://www.boost.org/libs/multi_array for documentation.

//
// generative-tests.hpp - Framework for running tests on all the types
//   of multi_array
//
//  In order to create a set of tests, you must define the following two
//  function signatures:
//   template <typename Array>
//   void access(Array& A, const mutable_array_tag&);
//
//   template <typename Array>
//   void access(Array& A, const const_array_tag&);
//
//  The framework will always pass 2x3x4 arrays into these functions.
//  The const_array_tag version of access must NOT attempt to modify 
//  the array.  Assume that the passed array has constness in this case.
// 
//  The mutable_array_tag version of access should pass the array to the
//  assign() function in order to set its values before running tests.
//
//  If you wish to write your own code to assign data to the array
//  (ie. test the iterators by assigning data with them), you must
//  #define MULTIARRAY_TEST_ASSIGN before including this file.
//  assign() will call this function.
//
//  If you wish to know how many tests were run, you must increment
//  the global variable 'tests_run' somewhere in your test code.
//
//  Since generative-tests uses the Boost.Test framework, you must
//  define at least the following:
//
//  int test_main(int,char*[]) { return run_generative_tests(); }
//
#include <boost/multi_array.hpp>

#include <boost/core/lightweight_test.hpp>

#include <boost/config.hpp> /* BOOST_NO_SFINAE */
#include <algorithm>
#include <iostream>
#include <vector>

namespace {
  unsigned int tests_run = 0;
} // empty namespace 

struct mutable_array_tag { };
struct const_array_tag { };

template <typename Array>
void assign_if_not_const(Array&, const const_array_tag&) {
  // do nothing
}

template <typename Array>
void assign_if_not_const(Array& A, const mutable_array_tag&);

#ifndef MULTIARRAY_TEST_ASSIGN
template <typename Array>
void assign_if_not_const(Array& A, const mutable_array_tag&) {

  typedef typename Array::index index;

  const index idx0 = A.index_bases()[0];
  const index idx1 = A.index_bases()[1];
  const index idx2 = A.index_bases()[2];


  int num = 0;
  for (index i = idx0; i != idx0 + 2; ++i)
    for (index j = idx1; j != idx1 + 3; ++j)
      for (index k = idx2; k != idx2 + 4; ++k) 
        A[i][j][k] = num++;
}
#endif // MULTIARRAY_TEST_ASSIGN

template <typename Array>
void assign(Array& A) {
  assign_if_not_const(A,mutable_array_tag());
}

template <typename Array>
void access(Array& A, const mutable_array_tag&);

template <typename Array>
void access(Array& A, const const_array_tag&);

template <typename StorageOrder3,typename StorageOrder4,typename Modifier>
void run_configuration(const StorageOrder3& so3,
                      const StorageOrder4& so4,
                      const Modifier& modifier) {
  // multi_array
  {
    typedef boost::multi_array<int,3> array;
    typename array::extent_gen extents;
    {
      array A(extents[2][3][4],so3);
      modifier.modify(A);
      access(A,mutable_array_tag());
    }
  }
  // multi_array_ref
  {
    typedef boost::multi_array_ref<int,3> array_ref;
    typename array_ref::extent_gen extents;
    {
      int local[24];
      array_ref A(local,extents[2][3][4],so3);
      modifier.modify(A);
      access(A,mutable_array_tag());
    }
  }
  // const_multi_array_ref
  {
    typedef boost::multi_array_ref<int,3> array_ref;
    typedef boost::const_multi_array_ref<int,3> const_array_ref;
    typename array_ref::extent_gen extents;
    {
      int local[24];
      array_ref A(local,extents[2][3][4],so3);
      modifier.modify(A);
      assign(A);

      const_array_ref B = A;
      access(B,const_array_tag());
    }
  }
  // sub_array
  {
    typedef boost::multi_array<int,4> array;
    typename array::extent_gen extents;
    {
      array A(extents[2][2][3][4],so4);
      modifier.modify(A);
      typename array::template subarray<3>::type B = A[1];
      access(B,mutable_array_tag());
    }
  }
  // const_sub_array
  {
    typedef boost::multi_array<int,4> array;
    typename array::extent_gen extents;
    {
      array A(extents[2][2][3][4],so4);
      modifier.modify(A);
      typename array::template subarray<3>::type B = A[1];
      assign(B);

      typename array::template const_subarray<3>::type C = B;
      access(C,const_array_tag());
    }
  }
  // array_view
  {
    typedef boost::multi_array<int,3> array;
    typedef typename array::index_range range;
    typename array::index_gen indices;
    typename array::extent_gen extents;
    {
      typedef typename array::index index;

      array A(extents[4][5][6],so3);
      modifier.modify(A);
      const index idx0 = A.index_bases()[0];
      const index idx1 = A.index_bases()[1];
      const index idx2 = A.index_bases()[2];

      typename array::template array_view<3>::type B =A[
        indices[range(idx0+1,idx0+3)]
               [range(idx1+1,idx1+4)]
               [range(idx2+1,idx2+5)]
      ];
      access(B,mutable_array_tag());
    }
  }
  // const_array_view
  {
    typedef boost::multi_array<int,3> array;
    typedef typename array::index_range range;
    typename array::index_gen indices;
    typename array::extent_gen extents;
    {
      typedef typename array::index index;

      array A(extents[4][5][6],so3);
      modifier.modify(A);
      const index idx0 = A.index_bases()[0];
      const index idx1 = A.index_bases()[1];
      const index idx2 = A.index_bases()[2];

      typename array::template array_view<3>::type B =A[
        indices[range(idx0+1,idx0+3)]
               [range(idx1+1,idx1+4)]
               [range(idx2+1,idx2+5)]
      ];
      assign(B);

      typename array::template const_array_view<3>::type C = B;
      access(C,const_array_tag());
    }
  }
}

template <typename ArrayModifier>
void run_storage_tests(const ArrayModifier& modifier) {
  run_configuration(boost::c_storage_order(),
                    boost::c_storage_order(),modifier);
  run_configuration(boost::fortran_storage_order(),
                    boost::fortran_storage_order(),modifier);
  
  std::size_t ordering[] = {2,0,1,3};
  bool ascending[] = {false,true,true,true};
  run_configuration(boost::general_storage_order<3>(ordering,ascending),
                    boost::general_storage_order<4>(ordering,ascending),
                    modifier); 
}

struct null_modifier {
  template <typename Array>
  void modify(Array&) const { }
};

struct set_index_base_modifier {
  template <typename Array>
  void modify(Array& A) const {
#ifdef BOOST_NO_SFINAE
    typedef boost::multi_array_types::index index;
    A.reindex(index(1));
#else
    A.reindex(1);
#endif 
  }
};

struct reindex_modifier {
  template <typename Array>
  void modify(Array& A) const {
    boost::array<int,4> bases = {{1,2,3,4}};
    A.reindex(bases);
 }
};

struct reshape_modifier {
  template <typename Array>
  void modify(Array& A) const {
    typedef typename Array::size_type size_type;
    std::vector<size_type> old_shape(A.num_dimensions());
    std::vector<size_type> new_shape(A.num_dimensions());

    std::copy(A.shape(),A.shape()+A.num_dimensions(),old_shape.begin());
    std::copy(old_shape.rbegin(),old_shape.rend(),new_shape.begin());

    A.reshape(new_shape);
    A.reshape(old_shape);
  }
};

int run_generative_tests() {

  run_storage_tests(null_modifier());
  run_storage_tests(set_index_base_modifier());
  run_storage_tests(reindex_modifier());
  run_storage_tests(reshape_modifier());
  std::cout << "Total Tests Run: " << tests_run << '\n';
  return boost::report_errors();
}

#endif
