/**
 * \file dnn/test/common/relayout.cpp
 * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
 *
 * Copyright (c) 2014-2020 Megvii Inc. All rights reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

#include "megdnn/oprs/general.h"

#include "src/common/relayout_helper.h"
#include "test/common/benchmarker.h"
#include "test/common/relayout.h"
#include "test/common/checker.h"

using namespace megdnn;
using namespace test;
using namespace megdnn::relayout;
using namespace test::relayout;

namespace {
TestArg generate_transpose_args(size_t batch, size_t m, size_t n,
                                       size_t c, DType dtype) {
    TestArg arg;
    arg.src = TensorLayout(
        TensorShape{batch, n, m, c},
        {static_cast<std::ptrdiff_t>(n * m * c), static_cast<std::ptrdiff_t>(c),
         static_cast<std::ptrdiff_t>(n * c), 1},
        dtype);
    arg.dst = TensorLayout(TensorShape{batch, n, m, c}, dtype);
    return arg;
}
} // anonymous namespace

namespace megdnn {
namespace test {
namespace relayout {

void run_test_cv(Handle* handle, size_t CH) {
    std::vector<TestArg> args;

    for (size_t M = 124; M <= 130; ++M) {
        for (size_t N = 124; N <= 130; ++N) {
            args.push_back(
                    generate_transpose_args(1, M, N, CH, dtype::Uint8()));
            args.push_back(
                    generate_transpose_args(1, M, N, CH, dtype::Int32()));
            args.push_back(
                    generate_transpose_args(1, M, N, CH, dtype::Float32()));
            args.push_back(
                    generate_transpose_args(3, M, N, CH, dtype::Float32()));
        }
    }

    Checker<Relayout> checker(handle);

    for (auto&& arg : args) {
        checker.execl({arg.src, arg.dst});
    }
}

#define DEF_TEST(name) \
template<> \
void run_test<name>(Handle *handle)

DEF_TEST(cv) {
    run_test_cv(handle, 1);
}

DEF_TEST(cv_ch3) {
    run_test_cv(handle, 3);
}

DEF_TEST(cv_ch5) {
    run_test_cv(handle, 5);
}

DEF_TEST(broadcast) {
    std::vector<TestArg> args;
    TensorLayout src{{2, 3, 4}, dtype::Float32()},
                 dst{{2, 3, 4}, dtype::Float32()};

    src.stride[0] = 4;
    src.stride[1] = 0;
    args.emplace_back(src, dst);

    // last stride contiguous
    args.emplace_back(TensorLayout({3, 100, 2}, {2, 0, 1}, dtype::Float16()),
                      TensorLayout({3, 100, 2}, {200, 2, 1}, dtype::Float16()));
    Checker<Relayout> checker(handle);

    for (auto &&arg : args) {
        checker.execl({arg.src, arg.dst});
    }
}

DEF_TEST(negative) {
    TensorLayout src{{7, 8, 10}, dtype::Float32()},
                 dst{{7, 8, 10}, dtype::Float32()};

    src.stride[0] *= -1;

    Checker<Relayout> checker(handle);
    checker.execl({src, dst});
}

DEF_TEST(transpose) {
    Checker<Relayout> checker(handle);
    {
        TensorLayout sl({8, 10}, dtype::Int32()), dl({10, 8}, dtype::Int32());
        sl = sl.dimshuffle({1, 0});
        checker.execl({sl, dl});
        checker.execl({dl, sl});
    }
    {
        TensorLayout sl({8, 10, 2}, dtype::Int32()),
                     dl({2, 8, 10}, dtype::Int32());
        sl = sl.dimshuffle({2, 0, 1});
        checker.execl({sl, dl});
        checker.execl({dl, sl});
    }
}

#undef DEF_TEST

} // namespace relayout
} // namespace test
} // namespace megdnn

void test::relayout::run_cv_benchmark(Handle* handle) {
    auto handle_naive = create_cpu_handle(2);
    std::vector<TestArg> args;

    args.push_back(generate_transpose_args(1, 255, 256, 1, dtype::Int32()));
    args.push_back(generate_transpose_args(1, 513, 1025, 3, dtype::Int32()));

    args.push_back(generate_transpose_args(1, 255, 256, 1, dtype::Uint8()));
    args.push_back(generate_transpose_args(1, 513, 1025, 3, dtype::Uint8()));

    args.push_back(generate_transpose_args(1, 255, 256, 3, dtype::Float32()));
    args.push_back(generate_transpose_args(1, 513, 1025, 1, dtype::Float32()));

    args.push_back(generate_transpose_args(2, 987, 573, 6, dtype::Float32()));

    Benchmarker<Relayout> benchmarker(handle);
    Benchmarker<Relayout> benchmarker_naive(handle_naive.get());

    Checker<Relayout> checker(handle);
    benchmarker_naive.set_times(1).set_display(false);
    benchmarker.set_times(1).set_display(false);
    for (auto&& arg : args) {
        checker.execl({arg.src, arg.dst});
        auto t0 = benchmarker.execl({arg.src, arg.dst});
        auto t1 = benchmarker_naive.execl({arg.src, arg.dst});
        double k = arg.dst.span().dist_byte() * 1e3 / (1024 * 1024 * 1024);
        printf("cur=%7.3fms,%5.2fGiB/s naive=%7.3fms,%5.2fGiB/s %s %s\n", t0,
               k / t0, t1, k / t1, arg.dst.TensorShape::to_string().c_str(),
               arg.dst.dtype.name());
    }
}
TEST(RELAYOUT, TRANSPOSE_DET) {
    auto run = [](const TensorShape& shape,
                  const std::vector<size_t>& dimshuffle,
                  bool expect_is_transpose, const TransposeParam& p = {}) {
        TensorLayout src{shape, dtype::Float32{}};
        src = src.dimshuffle(dimshuffle).collapse_contiguous();
        TensorLayout dst{TensorShape{src.total_nr_elems()}, src.dtype};
        TransposeParam p_get;
        bool succ = is_transpose(src, dst, p_get);
        ASSERT_EQ(expect_is_transpose, succ);
        if (succ) {
            ASSERT_EQ(p_get.batch, p.batch);
            ASSERT_EQ(p_get.m, p.m);
            ASSERT_EQ(p_get.n, p.n);
            ASSERT_EQ(p_get.c, p.c);
        }
        // swap m, n
        succ = is_transpose(dst, src, p_get);
        ASSERT_EQ(expect_is_transpose, succ);
        if (succ) {
            ASSERT_EQ(p_get.batch, p.batch);
            ASSERT_EQ(p_get.m, p.n);
            ASSERT_EQ(p_get.n, p.m);
            ASSERT_EQ(p_get.c, p.c);
        }
    };
    run({2, 3}, {1, 0}, true, {1, 2, 3, 1});
    run({2, 3, 5}, {1, 0, 2}, true, {1, 2, 3, 5});
    run({2, 3, 5}, {0, 2, 1}, true, {2, 3, 5, 1});
    run({3, 2, 3, 5}, {0, 2, 1, 3}, true, {3, 2, 3, 5});
    run({3, 2, 3, 5}, {0, 1, 3, 2}, true, {6, 3, 5, 1});
    run({2, 3, 5}, {2, 1, 0}, false);
    run({3, 2, 3, 5}, {3, 2, 1, 0}, false);
}
// vim: syntax=cpp.doxygen
