/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include <iostream>
#include <type_traits>
#include <vector>

#include "atlas/array.h"
#include "atlas/array/ArrayUtil.h"
#include "atlas/array/ArrayView.h"
#include "atlas/array/DataType.h"
#include "atlas/array/MakeView.h"
#include "atlas/array/gridtools/GridToolsArrayHelpers.h"
#include "atlas/array/gridtools/GridToolsDataStore.h"
#include "atlas/array/gridtools/GridToolsTraits.h"
#include "atlas/array/helpers/ArrayInitializer.h"
#include "atlas/array_fwd.h"
#include "atlas/runtime/Log.h"

#if ATLAS_HAVE_ACC
#include "atlas_acc_support/atlas_acc_map_data.h"
#endif

//------------------------------------------------------------------------------

using namespace atlas::array::gridtools;
using namespace atlas::array::helpers;

namespace atlas {
namespace array {

//------------------------------------------------------------------------------

namespace gridtools {

//------------------------------------------------------------------------------

template <int Rank>
using UintSequence = std::make_integer_sequence<unsigned int, Rank>;

template <typename Value, template <class> class Storage, typename StorageInfo>
static Array* wrap_array( ::gridtools::data_store<Storage<Value>, StorageInfo>* ds, const ArraySpec& spec ) {
    assert( ds );
    using data_store_t         = typename std::remove_pointer<decltype( ds )>::type;
    ArrayDataStore* data_store = new GridToolsDataStore<data_store_t>( ds );
    return new ArrayT<Value>( data_store, spec );
}

//------------------------------------------------------------------------------

}  // namespace gridtools

template <typename Value>
class ArrayT_impl {
public:
    ArrayT_impl( ArrayT<Value>& array ) : array_( array ) {}

    template <typename... UInts, typename = ::gridtools::is_all_integral<UInts...>>
    void construct( UInts... dims ) {
        static_assert( sizeof...( UInts ) > 0, "1" );
        auto gt_storage    = create_gt_storage<Value, typename default_layout_t<sizeof...( dims )>::type>( dims... );
        using data_store_t = typename std::remove_pointer<decltype( gt_storage )>::type;
        array_.data_store_ = std::unique_ptr<ArrayDataStore>( new GridToolsDataStore<data_store_t>( gt_storage ) );
        array_.spec_       = make_spec( gt_storage, dims... );
    }

    template <int Alignment, typename... UInts, typename = ::gridtools::is_all_integral<UInts...>>
    void construct_aligned( UInts... dims ) {
        static_assert( sizeof...( UInts ) > 0, "1" );
        auto gt_storage    = create_gt_storage<Value, typename default_layout_t<sizeof...( dims )>::type,
                                            ::gridtools::alignment<Alignment>>( dims... );
        using data_store_t = typename std::remove_pointer<decltype( gt_storage )>::type;
        array_.data_store_ = std::unique_ptr<ArrayDataStore>( new GridToolsDataStore<data_store_t>( gt_storage ) );
        array_.spec_       = make_spec( gt_storage, dims... );
    }

    template <typename... UInts, typename = ::gridtools::is_all_integral<UInts...>>
    void construct( ArrayAlignment alignment, UInts... dims ) {
        static_assert( sizeof...( UInts ) > 0, "1" );
        switch ( int( alignment ) ) {
            case 1:
                construct( dims... );
                break;
            case 2:
                construct_aligned<2>( dims... );
                break;
            case 4:
                construct_aligned<4>( dims... );
                break;
            case 8:
                construct_aligned<8>( dims... );
                break;
            case 16:
                construct_aligned<16>( dims... );
                break;
            case 32:
                construct_aligned<32>( dims... );
                break;
            case 64:
                construct_aligned<64>( dims... );
                break;
            default:
                ATLAS_NOTIMPLEMENTED;
        }
    }


    void construct( const ArrayShape& shape ) {
        assert( shape.size() > 0 );
        switch ( shape.size() ) {
            case 1:
                return construct( shape[0] );
            case 2:
                return construct( shape[0], shape[1] );
            case 3:
                return construct( shape[0], shape[1], shape[2] );
            case 4:
                return construct( shape[0], shape[1], shape[2], shape[3] );
            case 5:
                return construct( shape[0], shape[1], shape[2], shape[3], shape[4] );
            default: {
                std::stringstream err;
                err << "shape not recognized";
                throw_Exception( err.str(), Here() );
            }
        }
    }

    void construct( const ArraySpec& spec ) {
        auto& shape = spec.shape();
        assert( shape.size() > 0 );
        switch ( shape.size() ) {
            case 1:
                return construct( spec.alignment(), shape[0] );
            case 2:
                return construct( spec.alignment(), shape[0], shape[1] );
            case 3:
                return construct( spec.alignment(), shape[0], shape[1], shape[2] );
            case 4:
                return construct( spec.alignment(), shape[0], shape[1], shape[2], shape[3] );
            case 5:
                return construct( spec.alignment(), shape[0], shape[1], shape[2], shape[3], shape[4] );
            default: {
                std::stringstream err;
                err << "shape not recognized";
                throw_Exception( err.str(), Here() );
            }
        }
    }

    void construct( const ArrayShape& shape, const ArrayLayout& layout ) {
        ATLAS_ASSERT( shape.size() > 0 );
        ATLAS_ASSERT( shape.size() == layout.size() );
        switch ( shape.size() ) {
            case 1:
                return construct( shape[0] );
            case 2: {
                if ( layout[0] == 0 && layout[1] == 1 )
                    return construct_with_layout<::gridtools::layout_map<0, 1>>( shape[0], shape[1] );
                if ( layout[0] == 1 && layout[1] == 0 )
                    return construct_with_layout<::gridtools::layout_map<1, 0>>( shape[0], shape[1] );
            }
            case 3: {
                if ( layout[0] == 0 && layout[1] == 1 && layout[2] == 2 )
                    return construct_with_layout<::gridtools::layout_map<0, 1, 2>>( shape[0], shape[1], shape[2] );
                if ( layout[0] == 0 && layout[1] == 2 && layout[2] == 1 )
                    return construct_with_layout<::gridtools::layout_map<0, 2, 1>>( shape[0], shape[1], shape[2] );
                if ( layout[0] == 1 && layout[1] == 0 && layout[2] == 2 )
                    return construct_with_layout<::gridtools::layout_map<1, 0, 2>>( shape[0], shape[1], shape[2] );
                if ( layout[0] == 1 && layout[1] == 2 && layout[2] == 0 )
                    return construct_with_layout<::gridtools::layout_map<1, 2, 0>>( shape[0], shape[1], shape[2] );
                if ( layout[0] == 2 && layout[1] == 0 && layout[2] == 1 )
                    return construct_with_layout<::gridtools::layout_map<2, 0, 1>>( shape[0], shape[1], shape[2] );
                if ( layout[0] == 2 && layout[1] == 1 && layout[2] == 0 )
                    return construct_with_layout<::gridtools::layout_map<2, 1, 0>>( shape[0], shape[1], shape[2] );
            }
            case 4: {
                if ( layout[0] == 0 && layout[1] == 1 && layout[2] == 2 && layout[3] == 3 )
                    return construct_with_layout<::gridtools::layout_map<0, 1, 2, 3>>( shape[0], shape[1], shape[2],
                                                                                       shape[3] );
                if ( layout[0] == 3 && layout[1] == 2 && layout[2] == 1 && layout[3] == 0 )
                    return construct_with_layout<::gridtools::layout_map<3, 2, 1, 0>>( shape[0], shape[1], shape[2],
                                                                                       shape[3] );
            }
            case 5: {
                if ( layout[0] == 0 && layout[1] == 1 && layout[2] == 2 && layout[3] == 3 && layout[4] == 4 )
                    return construct_with_layout<::gridtools::layout_map<0, 1, 2, 3, 4>>( shape[0], shape[1], shape[2],
                                                                                          shape[3], shape[4] );
                if ( layout[0] == 4 && layout[1] == 3 && layout[2] == 2 && layout[3] == 1 && layout[4] == 0 )
                    return construct_with_layout<::gridtools::layout_map<4, 3, 2, 1, 0>>( shape[0], shape[1], shape[2],
                                                                                          shape[3], shape[4] );
            }
            default: {
                std::stringstream err;
                if ( shape.size() > 5 )
                    err << "shape not recognized";
                else {
                    err << "Layout < ";
                    for ( size_t j = 0; j < layout.size(); ++j )
                        err << layout[j] << " ";
                    err << "> not implemented in Atlas.";
                }
                throw_Exception( err.str(), Here() );
            }
        }
    }

    template <typename Layout, typename... UInts, typename = ::gridtools::is_all_integral<UInts...>>
    void construct_with_layout( UInts... dims ) {
        auto gt_data_store_ptr = create_gt_storage<Value, Layout>( dims... );
        using data_store_t     = typename std::remove_pointer<decltype( gt_data_store_ptr )>::type;
        array_.data_store_ =
            std::unique_ptr<ArrayDataStore>( new GridToolsDataStore<data_store_t>( gt_data_store_ptr ) );
        array_.spec_ = make_spec( gt_data_store_ptr, dims... );
    }

    template <typename... Ints>
    void resize_variadic( Ints... c ) {
        if ( sizeof...( c ) != array_.rank() ) {
            std::stringstream err;
            err << "Trying to resize an array of Rank " << array_.rank() << " by dimensions with Rank "
                << sizeof...( c ) << std::endl;
            throw_Exception( err.str(), Here() );
        }

        if ( array_.valid() )
            array_.syncHostDevice();

        Array* resized = Array::create<Value>( ArrayShape{(idx_t)c...} );

        array_initializer<sizeof...( c )>::apply( array_, *resized );
        array_.replace( *resized );
        delete resized;
    }

    template <typename UInt, UInt... Indices>
    void apply_resize( const ArrayShape& shape, std::integer_sequence<UInt, Indices...> ) {
        return resize_variadic( shape[Indices]... );
    }

private:
    ArrayT<Value>& array_;
};

//------------------------------------------------------------------------------

template <typename Value>
Array* Array::create( idx_t dim0 ) {
    return new ArrayT<Value>( dim0 );
}
template <typename Value>
Array* Array::create( idx_t dim0, idx_t dim1 ) {
    return new ArrayT<Value>( dim0, dim1 );
}
template <typename Value>
Array* Array::create( idx_t dim0, idx_t dim1, idx_t dim2 ) {
    return new ArrayT<Value>( dim0, dim1, dim2 );
}
template <typename Value>
Array* Array::create( idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3 ) {
    return new ArrayT<Value>( dim0, dim1, dim2, dim3 );
}
template <typename Value>
Array* Array::create( idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3, idx_t dim4 ) {
    return new ArrayT<Value>( dim0, dim1, dim2, dim3, dim4 );
}
template <typename Value>
Array* Array::create( const ArrayShape& shape ) {
    return new ArrayT<Value>( shape );
}
template <typename Value>
Array* Array::create( const ArrayShape& shape, const ArrayLayout& layout ) {
    return new ArrayT<Value>( shape, layout );
}
template <typename Value>
Array* Array::wrap( Value* data, const ArraySpec& spec ) {
    ArrayShape const& shape     = spec.shape();
    ArrayStrides const& strides = spec.strides();

    assert( shape.size() > 0 );

    switch ( shape.size() ) {
        case 1:
            return wrap_array( wrap_gt_storage<Value, 1>( data, get_array_from_vector<1>( shape ),
                                                          get_array_from_vector<1>( strides ) ),
                               spec );
        case 2:
            return wrap_array( wrap_gt_storage<Value, 2>( data, get_array_from_vector<2>( shape ),
                                                          get_array_from_vector<2>( strides ) ),
                               spec );
        case 3:
            return wrap_array( wrap_gt_storage<Value, 3>( data, get_array_from_vector<3>( shape ),
                                                          get_array_from_vector<3>( strides ) ),
                               spec );
        case 4:
            return wrap_array( wrap_gt_storage<Value, 4>( data, get_array_from_vector<4>( shape ),
                                                          get_array_from_vector<4>( strides ) ),
                               spec );
        case 5:
            return wrap_array( wrap_gt_storage<Value, 5>( data, get_array_from_vector<5>( shape ),
                                                          get_array_from_vector<5>( strides ) ),
                               spec );
        case 6:
            return wrap_array( wrap_gt_storage<Value, 6>( data, get_array_from_vector<6>( shape ),
                                                          get_array_from_vector<6>( strides ) ),
                               spec );
        case 7:
            return wrap_array( wrap_gt_storage<Value, 7>( data, get_array_from_vector<7>( shape ),
                                                          get_array_from_vector<7>( strides ) ),
                               spec );
        case 8:
            return wrap_array( wrap_gt_storage<Value, 8>( data, get_array_from_vector<8>( shape ),
                                                          get_array_from_vector<8>( strides ) ),
                               spec );
        case 9:
            return wrap_array( wrap_gt_storage<Value, 9>( data, get_array_from_vector<9>( shape ),
                                                          get_array_from_vector<9>( strides ) ),
                               spec );
        default: {
            std::stringstream err;
            err << "shape not recognized";
            throw_Exception( err.str(), Here() );
        }
    }
}
template <typename Value>
Array* Array::wrap( Value* data, const ArrayShape& shape ) {
    return wrap( data, ArraySpec( shape ) );
}

Array* Array::create( DataType datatype, const ArrayShape& shape ) {
    switch ( datatype.kind() ) {
        case DataType::KIND_REAL64:
            return create<double>( shape );
        case DataType::KIND_REAL32:
            return create<float>( shape );
        case DataType::KIND_INT32:
            return create<int>( shape );
        case DataType::KIND_INT64:
            return create<long>( shape );
        case DataType::KIND_UINT64:
            return create<unsigned long>( shape );
        default: {
            std::stringstream err;
            err << "data kind " << datatype.kind() << " not recognised.";
            throw_Exception( err.str(), Here() );
        }
    }
}

Array* Array::create( DataType datatype, const ArrayShape& shape, const ArrayLayout& layout ) {
    switch ( datatype.kind() ) {
        case DataType::KIND_REAL64:
            return create<double>( shape, layout );
        case DataType::KIND_REAL32:
            return create<float>( shape, layout );
        case DataType::KIND_INT32:
            return create<int>( shape, layout );
        case DataType::KIND_INT64:
            return create<long>( shape, layout );
        case DataType::KIND_UINT64:
            return create<unsigned long>( shape, layout );

        default: {
            std::stringstream err;
            err << "data kind " << datatype.kind() << " not recognised.";
            throw_Exception( err.str(), Here() );
        }
    }
}

Array* Array::create( DataType datatype, ArraySpec&& spec ) {
    switch ( datatype.kind() ) {
        case DataType::KIND_REAL64:
            return new ArrayT<double>( std::move( spec ) );
        case DataType::KIND_REAL32:
            return new ArrayT<float>( std::move( spec ) );
        case DataType::KIND_INT32:
            return new ArrayT<int>( std::move( spec ) );
        case DataType::KIND_INT64:
            return new ArrayT<long>( std::move( spec ) );
        case DataType::KIND_UINT64:
            return new ArrayT<unsigned long>( std::move( spec ) );
        default: {
            std::stringstream err;
            err << "data kind " << datatype.kind() << " not recognised.";
            throw_NotImplemented( err.str(), Here() );
        }
    }
}

//------------------------------------------------------------------------------

Array::~Array() {}

//------------------------------------------------------------------------------

template <typename Value>
size_t ArrayT<Value>::footprint() const {
    size_t size = sizeof( *this );
    size += bytes();
    return size;
}

//------------------------------------------------------------------------------

template <typename Value>
bool ArrayT<Value>::accMap() const {
    if ( not acc_map_ ) {
#if ATLAS_GRIDTOOLS_STORAGE_BACKEND_CUDA && ATLAS_HAVE_ACC
        atlas_acc_map_data( (void*)host_data<Value>(), (void*)device_data<Value>(),
                            spec_.allocatedSize() * sizeof( Value ) );
        acc_map_ = true;
#endif
    }
    return acc_map_;
}

//------------------------------------------------------------------------------

template <typename Value>
void ArrayT<Value>::insert( idx_t idx1, idx_t size1 ) {
    // if( hostNeedsUpdate() ) {
    //    updateHost();
    //}
    if ( not hasDefaultLayout() )
        ATLAS_NOTIMPLEMENTED;

    ArrayShape nshape = shape();
    if ( idx1 > nshape[0] ) {
        throw_Exception( "can not insert into an array at a position beyond its size", Here() );
    }
    nshape[0] += size1;

    Array* resized = Array::create<Value>( nshape );

    array_initializer_partitioned<0>::apply( *this, *resized, idx1, size1 );

    replace( *resized );
    delete resized;
}

//------------------------------------------------------------------------------

template <typename Value>
void ArrayT<Value>::resize( idx_t dim0 ) {
    ArrayT_impl<Value>( *this ).resize_variadic( dim0 );
}

template <typename Value>
void ArrayT<Value>::resize( idx_t dim0, idx_t dim1 ) {
    ArrayT_impl<Value>( *this ).resize_variadic( dim0, dim1 );
}

template <typename Value>
void ArrayT<Value>::resize( idx_t dim0, idx_t dim1, idx_t dim2 ) {
    ArrayT_impl<Value>( *this ).resize_variadic( dim0, dim1, dim2 );
}

template <typename Value>
void ArrayT<Value>::resize( idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3 ) {
    ArrayT_impl<Value>( *this ).resize_variadic( dim0, dim1, dim2, dim3 );
}

template <typename Value>
void ArrayT<Value>::resize( idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3, idx_t dim4 ) {
    ArrayT_impl<Value>( *this ).resize_variadic( dim0, dim1, dim2, dim3, dim4 );
}

template <typename Value>
void ArrayT<Value>::dump( std::ostream& out ) const {
    switch ( rank() ) {
        case 1:
            make_host_view<Value, 1>( *this ).dump( out );
            break;
        case 2:
            make_host_view<Value, 2>( *this ).dump( out );
            break;
        case 3:
            make_host_view<Value, 3>( *this ).dump( out );
            break;
        case 4:
            make_host_view<Value, 4>( *this ).dump( out );
            break;
        case 5:
            make_host_view<Value, 5>( *this ).dump( out );
            break;
        case 6:
            make_host_view<Value, 6>( *this ).dump( out );
            break;
        case 7:
            make_host_view<Value, 7>( *this ).dump( out );
            break;
        case 8:
            make_host_view<Value, 8>( *this ).dump( out );
            break;
        case 9:
            make_host_view<Value, 9>( *this ).dump( out );
            break;
        default:
            ATLAS_NOTIMPLEMENTED;
    }
}

template <typename Value>
void ArrayT<Value>::resize( const ArrayShape& shape ) {
    assert( shape.size() > 0 );
    switch ( shape.size() ) {
        case 1:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<1>() );
        case 2:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<2>() );
        case 3:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<3>() );
        case 4:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<4>() );
        case 5:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<5>() );
        case 6:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<6>() );
        case 7:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<7>() );
        case 8:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<8>() );
        case 9:
            return ArrayT_impl<Value>( *this ).apply_resize( shape, UintSequence<9>() );
        default: {
            std::stringstream err;
            err << "shape not recognized";
            throw_Exception( err.str(), Here() );
        }
    }
}

//------------------------------------------------------------------------------

template <typename Value>
ArrayT<Value>::ArrayT( ArrayDataStore* ds, const ArraySpec& spec ) {
    data_store_ = std::unique_ptr<ArrayDataStore>( ds );
    spec_       = spec;
}

template <typename Value>
ArrayT<Value>::ArrayT( idx_t dim0 ) {
    ArrayT_impl<Value>( *this ).construct( dim0 );
}

template <typename Value>
ArrayT<Value>::ArrayT( idx_t dim0, idx_t dim1 ) {
    ArrayT_impl<Value>( *this ).construct( dim0, dim1 );
}
template <typename Value>
ArrayT<Value>::ArrayT( idx_t dim0, idx_t dim1, idx_t dim2 ) {
    ArrayT_impl<Value>( *this ).construct( dim0, dim1, dim2 );
}
template <typename Value>
ArrayT<Value>::ArrayT( idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3 ) {
    ArrayT_impl<Value>( *this ).construct( dim0, dim1, dim2, dim3 );
}
template <typename Value>
ArrayT<Value>::ArrayT( idx_t dim0, idx_t dim1, idx_t dim2, idx_t dim3, idx_t dim4 ) {
    ArrayT_impl<Value>( *this ).construct( dim0, dim1, dim2, dim3, dim4 );
}

template <typename Value>
ArrayT<Value>::ArrayT( const ArrayShape& shape ) {
    ArrayT_impl<Value>( *this ).construct( shape );
}

template <typename Value>
ArrayT<Value>::ArrayT( const ArrayShape& shape, const ArrayAlignment& alignment ) {
    ArrayT_impl<Value>( *this ).construct( ArraySpec( shape, alignment ) );
}

template <typename Value>
ArrayT<Value>::ArrayT( const ArrayShape& shape, const ArrayLayout& layout ) {
    ArrayT_impl<Value>( *this ).construct( shape, layout );
}

template <typename Value>
ArrayT<Value>::ArrayT( ArraySpec&& spec ) {
    ArrayT_impl<Value>( *this ).construct( spec );
}

//------------------------------------------------------------------------------

}  // namespace array
}  // namespace atlas

//------------------------------------------------------------------------------
// Explicit template instantiations
namespace atlas {
namespace array {

template class ArrayT<int>;
template class ArrayT<long>;
template class ArrayT<float>;
template class ArrayT<double>;
template class ArrayT<unsigned long>;

template Array* Array::create<int>( idx_t );
template Array* Array::create<long>( idx_t );
template Array* Array::create<float>( idx_t );
template Array* Array::create<double>( idx_t );
template Array* Array::create<long unsigned>( idx_t );

template Array* Array::create<int>( idx_t, idx_t );
template Array* Array::create<long>( idx_t, idx_t );
template Array* Array::create<float>( idx_t, idx_t );
template Array* Array::create<double>( idx_t, idx_t );
template Array* Array::create<long unsigned>( idx_t, idx_t );

template Array* Array::create<int>( idx_t, idx_t, idx_t );
template Array* Array::create<long>( idx_t, idx_t, idx_t );
template Array* Array::create<float>( idx_t, idx_t, idx_t );
template Array* Array::create<double>( idx_t, idx_t, idx_t );
template Array* Array::create<long unsigned>( idx_t, idx_t, idx_t );

template Array* Array::create<int>( idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<long>( idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<float>( idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<double>( idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<long unsigned>( idx_t, idx_t, idx_t, idx_t );

template Array* Array::create<int>( idx_t, idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<long>( idx_t, idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<float>( idx_t, idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<double>( idx_t, idx_t, idx_t, idx_t, idx_t );
template Array* Array::create<long unsigned>( idx_t, idx_t, idx_t, idx_t, idx_t );

template Array* Array::create<int>( const ArrayShape& );
template Array* Array::create<long>( const ArrayShape& );
template Array* Array::create<float>( const ArrayShape& );
template Array* Array::create<double>( const ArrayShape& );
template Array* Array::create<long unsigned>( const ArrayShape& );

template Array* Array::create<int>( const ArrayShape&, const ArrayLayout& );
template Array* Array::create<long>( const ArrayShape&, const ArrayLayout& );
template Array* Array::create<float>( const ArrayShape&, const ArrayLayout& );
template Array* Array::create<double>( const ArrayShape&, const ArrayLayout& );
template Array* Array::create<long unsigned>( const ArrayShape&, const ArrayLayout& );

template Array* Array::wrap<int>( int*, const ArrayShape& );
template Array* Array::wrap<long>( long*, const ArrayShape& );
template Array* Array::wrap<float>( float*, const ArrayShape& );
template Array* Array::wrap<double>( double*, const ArrayShape& );
template Array* Array::wrap<long unsigned>( long unsigned*, const ArrayShape& );

template Array* Array::wrap<int>( int*, const ArraySpec& );
template Array* Array::wrap<long>( long*, const ArraySpec& );
template Array* Array::wrap<float>( float*, const ArraySpec& );
template Array* Array::wrap<double>( double*, const ArraySpec& );
template Array* Array::wrap<long unsigned>( long unsigned*, const ArraySpec& );

}  // namespace array
}  // namespace atlas
