/*
 * (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 "atlas/array.h"
#include "atlas/array/MakeView.h"
#include "atlas/array/helpers/ArraySlicer.h"
#include "atlas/library/config.h"

#include "tests/AtlasTestEnvironment.h"

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

template <typename Value, int Rank>
struct Slice {
    using type = typename std::conditional<( Rank == 0 ), Value, std::array<Value, Rank>>::type;
};

struct Slicer {
    template <int Rank, typename... Args>
    static typename Slice<double, SliceRank_impl<Rank, Args...>::value>::type apply( Args... args ) {
        typename Slice<double, SliceRank_impl<Rank, Args...>::value>::type a;
    }
};

namespace atlas {
namespace test {

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

CASE( "test_SliceRank" ) {
    static_assert( SliceRank_impl<1, int>::value == 0, "failed" );
    static_assert( SliceRank_impl<1, Range>::value == 1, "failed" );

    static_assert( SliceRank_impl<2, int, int>::value == 0, "failed" );
    static_assert( SliceRank_impl<2, Range, int>::value == 1, "failed" );
    static_assert( SliceRank_impl<2, Range, Range>::value == 2, "failed" );

    static_assert( SliceRank_impl<3, int, int, int>::value == 0, "failed" );
    static_assert( SliceRank_impl<3, Range, int, int>::value == 1, "failed" );
    static_assert( SliceRank_impl<3, Range, Range, int>::value == 2, "failed" );
    static_assert( SliceRank_impl<3, int, int, Range>::value == 1, "failed" );
    static_assert( SliceRank_impl<3, Range, int, Range>::value == 2, "failed" );
    static_assert( SliceRank_impl<3, Range, Range, Range>::value == 3, "failed" );
    static_assert( SliceRank_impl<3, Range, int, Range>::value == 2, "failed" );
    static_assert( SliceRank_impl<3, int, int, Range>::value == 1, "failed" );
    static_assert( SliceRank_impl<3, int, Range, Range>::value == 2, "failed" );
}

#if 1
CASE( "test_array_slicer_1d" ) {
    ArrayT<double> arr( 10 );
    auto view = make_view<double, 1>( arr );
    view.assign( {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} );

    using View = decltype( view );
    ArraySlicer<View> slicer( view );

    {
        auto slice = slicer.apply( Range{0, 2} );
        static_assert( std::is_same<decltype( slice ), LocalView<double, 1>>::value, "failed" );
        EXPECT( slice.rank() == 1 );
        EXPECT( slice.shape( 0 ) == 2 );
        EXPECT( slice( 0 ) == 0 );
        EXPECT( slice( 1 ) == 1 );
    }

    {
        auto slice = slicer.apply( Range{5, 10} );
        static_assert( std::is_same<decltype( slice ), LocalView<double, 1>>::value, "failed" );
        EXPECT( slice.rank() == 1 );
        EXPECT( slice.shape( 0 ) == 5 );
        EXPECT( slice( 0 ) == 5 );
        EXPECT( slice( 1 ) == 6 );
        EXPECT( slice( 2 ) == 7 );
        EXPECT( slice( 3 ) == 8 );
        EXPECT( slice( 4 ) == 9 );
    }

    {
        auto slice = slicer.apply( 5 );
        EXPECT( slice == 5 );
        static_assert( std::is_same<decltype( slice ), Reference<double>>::value, "failed" );
    }

    {
        auto slice = slicer.apply( Range::all(), Range::dummy() );
        static_assert( std::is_same<decltype( slice ), LocalView<double, 2>>::value, "failed" );
        EXPECT( slice.rank() == 2 );
        EXPECT( slice.shape( 0 ) == 10 );
        EXPECT( slice.shape( 1 ) == 1 );
        EXPECT( slice( 0, 0 ) == 0 );
        EXPECT( slice( 1, 0 ) == 1 );
    }
}
#endif
CASE( "test_array_slicer_2d" ) {
    ArrayT<double> arr( 3, 5 );
    auto view = make_view<double, 2>( arr );
    view.assign( {
        11,
        12,
        13,
        14,
        15,
        21,
        22,
        23,
        24,
        25,
        31,
        32,
        33,
        34,
        35,
    } );
    using View = decltype( view );
    ArraySlicer<View> slicer( view );

    //  ArraySlicer<double,2,Intent::ReadWrite> slicer(view);

    {
        auto slice = slicer.apply( Range{0, 2}, 2 );
        EXPECT( slice.rank() == 1 );
        EXPECT( slice.shape( 0 ) == 2 );
        EXPECT( slice( 0 ) == 13 );
        EXPECT( slice( 1 ) == 23 );
    }

    {
        const double& slice = slicer.apply( 1, 3 );
        EXPECT( slice == 24 );
        // static_assert( std::is_same< decltype(slice) , double& >::value, "failed"
        // );
    }

    {
        auto slice = slicer.apply( Range{0, 2}, Range::dummy(), Range{1, 3} );
        static_assert( std::is_same<decltype( slice ), LocalView<double, 3>>::value, "failed" );
        EXPECT( slice.rank() == 3 );
        EXPECT( slice.shape( 0 ) == 2 );
        EXPECT( slice.shape( 1 ) == 1 );
        EXPECT( slice.shape( 2 ) == 2 );
        EXPECT( slice( 0, 0, 0 ) == 12 );
        EXPECT( slice( 0, 0, 1 ) == 13 );
        EXPECT( slice( 1, 0, 0 ) == 22 );
        EXPECT( slice( 1, 0, 1 ) == 23 );
    }
}

CASE( "test_array_slicer_3d" ) {
    ArrayT<double> arr( 2, 3, 5 );
    auto view = make_view<double, 3>( arr );
    view.assign( {111, 112, 113, 114, 115, 121, 122, 123, 124, 125, 131, 132, 133, 134, 135,

                  211, 212, 213, 214, 215, 221, 222, 223, 224, 225, 231, 232, 233, 234, 235} );

    using View = decltype( view );
    ArraySlicer<View> slicer( view );

    {
        auto slice = slicer.apply( Range{0, 2}, 2, Range{2, 5} );
        EXPECT( slice.rank() == 2 );
        EXPECT( slice.shape( 0 ) == 2 );
        EXPECT( slice.shape( 1 ) == 3 );
        EXPECT( slice( 0, 0 ) == 133 );
        EXPECT( slice( 0, 1 ) == 134 );
        EXPECT( slice( 0, 2 ) == 135 );
        EXPECT( slice( 1, 0 ) == 233 );
        EXPECT( slice( 1, 1 ) == 234 );
        EXPECT( slice( 1, 2 ) == 235 );
    }

    {
        auto slice = slicer.apply( 0, 0, Range::from( 3 ) );
        EXPECT( slice.rank() == 1 );
        EXPECT( slice.shape( 0 ) == 2 );
        EXPECT( slice( 0 ) == 114 );
        EXPECT( slice( 1 ) == 115 );
    }

    {
        auto slice = slicer.apply( 1, Range::all(), 3 );
        EXPECT( slice.rank() == 1 );
        EXPECT( slice.shape( 0 ) == 3 );
        EXPECT( slice( 0 ) == 214 );
        EXPECT( slice( 1 ) == 224 );
        EXPECT( slice( 2 ) == 234 );
    }

    {
        const double& slice = slicer.apply( 1, 2, 3 );
        EXPECT( slice == 234 );
    }
}

CASE( "test_array_slicer_of_slice" ) {
    ArrayT<double> arr( 2, 3, 5 );
    auto view = make_view<double, 3>( arr );
    view.assign( {111, 112, 113, 114, 115, 121, 122, 123, 124, 125, 131, 132, 133, 134, 135,

                  211, 212, 213, 214, 215, 221, 222, 223, 224, 225, 231, 232, 233, 234, 235} );

    using View = decltype( view );
    ArraySlicer<View> slicer( view );

    auto slice = slicer.apply( Range{0, 2}, 2, Range{2, 5} );

    using Slice = decltype( slice );
    ArraySlicer<Slice> subslicer( slice );

    auto subslice1 = subslicer.apply( 0, 0 );
    auto subslice2 = subslicer.apply( Range::all(), Range::all() );
    auto subslice3 = subslicer.apply( Range::all(), 0 );
    auto subslice4 = subslicer.apply( 0, Range::to( 2 ) );

    EXPECT( is_approximately_equal( double( subslice1 ), 133. ) );
    EXPECT( subslice2.size() == slice.size() );
    EXPECT( subslice3.size() == slice.shape( 0 ) );
    EXPECT( subslice4.size() == 2 );
}

CASE( "test_arrayview_slice_type" ) {
    ArrayT<double> arr( 2, 3, 5 );

    // Assign
    {
        auto view = make_view<double, 3>( arr );
        view.assign( {111, 112, 113, 114, 115, 121, 122, 123, 124, 125, 131, 132, 133, 134, 135,

                      211, 212, 213, 214, 215, 221, 222, 223, 224, 225, 231, 232, 233, 234, 235} );
    }

    {
        auto read_write_view = make_view<double, 3>( arr );

        auto slice1 = read_write_view.slice( Range{0, 2}, 2, Range{2, 5} );

        EXPECT( slice1( 0, 0 ) == 133 );
        EXPECT( slice1( 0, 1 ) == 134 );
        EXPECT( slice1( 0, 2 ) == 135 );
        EXPECT( slice1( 1, 0 ) == 233 );
        EXPECT( slice1( 1, 1 ) == 234 );
        EXPECT( slice1( 1, 2 ) == 235 );

        auto slice2 = read_write_view.slice( Range::all(), Range::to( 2 ), Range::from( 3 ) );

        EXPECT( slice2( 0, 0, 0 ) == 114 );
        EXPECT( slice2( 0, 0, 1 ) == 115 );
        EXPECT( slice2( 0, 1, 0 ) == 124 );
        EXPECT( slice2( 0, 1, 1 ) == 125 );
        EXPECT( slice2( 1, 0, 0 ) == 214 );
        EXPECT( slice2( 1, 0, 1 ) == 215 );
        EXPECT( slice2( 1, 1, 0 ) == 224 );
        EXPECT( slice2( 1, 1, 1 ) == 225 );

        auto slice3 = read_write_view.slice( 0, 1, 2 );

        EXPECT( slice3 == 123 );

        static_assert( std::is_same<decltype( slice1 ), LocalView<double, 2>>::value, "failed" );
        static_assert( std::is_same<decltype( slice2 ), LocalView<double, 3>>::value, "failed" );
        static_assert( std::is_same<decltype( slice3 ), Reference<double>>::value, "failed" );
    }

    {
        auto read_only_view = make_view<const double, 3>( arr );

        auto slice1 = read_only_view.slice( Range{0, 2}, 2, Range{2, 5} );

        EXPECT( slice1( 0, 0 ) == 133 );
        EXPECT( slice1( 0, 1 ) == 134 );
        EXPECT( slice1( 0, 2 ) == 135 );
        EXPECT( slice1( 1, 0 ) == 233 );
        EXPECT( slice1( 1, 1 ) == 234 );
        EXPECT( slice1( 1, 2 ) == 235 );

        auto slice2 = read_only_view.slice( Range::all(), Range::to( 2 ), Range::from( 3 ) );

        EXPECT( slice2( 0, 0, 0 ) == 114 );
        EXPECT( slice2( 0, 0, 1 ) == 115 );
        EXPECT( slice2( 0, 1, 0 ) == 124 );
        EXPECT( slice2( 0, 1, 1 ) == 125 );
        EXPECT( slice2( 1, 0, 0 ) == 214 );
        EXPECT( slice2( 1, 0, 1 ) == 215 );
        EXPECT( slice2( 1, 1, 0 ) == 224 );
        EXPECT( slice2( 1, 1, 1 ) == 225 );

        auto slice3 = read_only_view.slice( 0, 1, 2 );

        EXPECT( slice3 == 123 );

        const double& slice4 = read_only_view.slice( 0, 1, 2 );

        EXPECT( slice4 == 123 );

        static_assert( std::is_same<decltype( slice1 ), LocalView<const double, 2>>::value, "failed" );
        static_assert( std::is_same<decltype( slice2 ), LocalView<const double, 3>>::value, "failed" );
        static_assert( std::is_same<decltype( slice3 ), Reference<const double>>::value, "failed" );
        static_assert( std::is_same<decltype( slice4 ), double const&>::value, "failed" );
    }

    {
        const auto const_read_write_view = make_view<double, 3>( arr );

        auto slice1 = const_read_write_view.slice( Range{0, 2}, 2, Range{2, 5} );

        EXPECT( slice1( 0, 0 ) == 133 );
        EXPECT( slice1( 0, 1 ) == 134 );
        EXPECT( slice1( 0, 2 ) == 135 );
        EXPECT( slice1( 1, 0 ) == 233 );
        EXPECT( slice1( 1, 1 ) == 234 );
        EXPECT( slice1( 1, 2 ) == 235 );

        auto slice2 = const_read_write_view.slice( Range::all(), Range::to( 2 ), Range::from( 3 ) );

        EXPECT( slice2( 0, 0, 0 ) == 114 );
        EXPECT( slice2( 0, 0, 1 ) == 115 );
        EXPECT( slice2( 0, 1, 0 ) == 124 );
        EXPECT( slice2( 0, 1, 1 ) == 125 );
        EXPECT( slice2( 1, 0, 0 ) == 214 );
        EXPECT( slice2( 1, 0, 1 ) == 215 );
        EXPECT( slice2( 1, 1, 0 ) == 224 );
        EXPECT( slice2( 1, 1, 1 ) == 225 );

        auto slice3 = const_read_write_view.slice( 0, 1, 2 );

        EXPECT( slice3 == 123 );

        static_assert( std::is_same<decltype( slice1 ), LocalView<const double, 2>>::value, "failed" );
        static_assert( std::is_same<decltype( slice2 ), LocalView<const double, 3>>::value, "failed" );
        static_assert( std::is_same<decltype( slice3 ), Reference<double const>>::value, "failed" );
    }
}

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

}  // namespace test
}  // namespace atlas

int main( int argc, char** argv ) {
    return atlas::test::run( argc, argv );
}
