/*
 * Copyright (c) 2019-2021, 2024 Arm Limited.
 *
 * SPDX-License-Identifier: MIT
 *
 * 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.
 */
#include "arm_compute/runtime/RuntimeContext.h"

#include "arm_compute/runtime/NEON/functions/NEActivationLayer.h"
#include "arm_compute/runtime/SchedulerFactory.h"
#include "arm_compute/runtime/Tensor.h"
#include "tests/Globals.h"
#include "tests/NEON/Accessor.h"
#include "tests/Utils.h"
#include "tests/framework/Asserts.h"
#include "tests/framework/Macros.h"
#include "tests/validation/Validation.h"
#include "tests/validation/reference/ActivationLayer.h"

#include <memory>
#include <random>
#if !defined(BARE_METAL)
#include <thread>
#endif // !defined(BARE_METAL)

namespace arm_compute
{
namespace test
{
namespace validation
{
TEST_SUITE(NEON)
TEST_SUITE(UNIT)
#if defined(ARM_COMPUTE_OPENMP_SCHEDULER) && !defined(_WIN64) && !defined(BARE_METAL) && !defined(__APPLE__) && !defined(__OpenBSD__) && \
    (defined(__arm__) || defined(__aarch64__)) && defined(__ANDROID__)
TEST_CASE(CpuCapacity, framework::DatasetMode::ALL)
{
    CPUInfo& ci =  arm_compute::Scheduler::get().cpu_info();
    const uint32_t nonlittle_num_cpus = ci.get_cpu_num_excluding_little();
    const uint32_t num_threads = arm_compute::Scheduler::get().num_threads();

    ARM_COMPUTE_EXPECT(num_threads<=nonlittle_num_cpus , framework::LogLevel::ERRORS);
}
#endif /* defined(ARM_COMPUTE_OPENMP_SCHEDULER) && !defined(_WIN64) && !defined(BARE_METAL) && !defined(__APPLE__) && !defined(__OpenBSD__) && \
    (defined(__arm__) || defined(__aarch64__)) && defined(__ANDROID__)*/

TEST_SUITE(RuntimeContext)

TEST_CASE(Scheduler, framework::DatasetMode::ALL)
{
    using namespace arm_compute;
    // Create a runtime context object
    RuntimeContext ctx;

    // Check if it's been initialised properly
    ARM_COMPUTE_ASSERT(ctx.scheduler() != nullptr);
    ARM_COMPUTE_ASSERT(ctx.asset_manager() == nullptr);

    // Create a Scheduler
    auto scheduler = SchedulerFactory::create();
    ctx.set_scheduler(scheduler.get());
    // Check if the scheduler has been properly setup
    ARM_COMPUTE_ASSERT(ctx.scheduler() != nullptr);

    // Create a new activation function
    NEActivationLayer act_layer(&ctx);

    Tensor src = create_tensor<Tensor>(TensorShape(32, 32), DataType::F32, 1);
    Tensor dst = create_tensor<Tensor>(TensorShape(32, 32), DataType::F32, 1);

    act_layer.configure(&src, &dst, ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::LINEAR));

    ARM_COMPUTE_ASSERT(src.info()->is_resizable());
    ARM_COMPUTE_ASSERT(dst.info()->is_resizable());

    // Allocate tensors
    src.allocator()->allocate();
    dst.allocator()->allocate();

    ARM_COMPUTE_ASSERT(!src.info()->is_resizable());

    float min_bound = 0;
    float max_bound = 0;
    std::tie(min_bound, max_bound) = get_activation_layer_test_bounds<float>(ActivationLayerInfo::ActivationFunction::LINEAR, DataType::F32);
    std::uniform_real_distribution<> distribution(min_bound, max_bound);
    library->fill(Accessor(src), distribution, 0);

    // Compute function
    act_layer.run();
}

#if !defined(BARE_METAL)
// This test tries scheduling work concurrently from two independent threads
TEST_CASE(MultipleThreadedScheduller, framework::DatasetMode::ALL)
{
    // Create a runtime context object for thread 1
    RuntimeContext ctx1;

    // Create a runtime context object for thread 2
    RuntimeContext ctx2;

    // Create a new activation function
    NEActivationLayer act_layer_thread0(&ctx1);
    NEActivationLayer act_layer_thread1(&ctx2);

    const TensorShape   tensor_shape(128, 128);
    Tensor              src_t0 = create_tensor<Tensor>(tensor_shape, DataType::F32, 1);
    Tensor              dst_t0 = create_tensor<Tensor>(tensor_shape, DataType::F32, 1);
    Tensor              src_t1 = create_tensor<Tensor>(tensor_shape, DataType::F32, 1);
    Tensor              dst_t1 = create_tensor<Tensor>(tensor_shape, DataType::F32, 1);
    ActivationLayerInfo activation_info(ActivationLayerInfo::ActivationFunction::LINEAR);

    act_layer_thread0.configure(&src_t0, &dst_t0, activation_info);
    act_layer_thread1.configure(&src_t1, &dst_t1, activation_info);

    ARM_COMPUTE_ASSERT(src_t0.info()->is_resizable());
    ARM_COMPUTE_ASSERT(dst_t0.info()->is_resizable());
    ARM_COMPUTE_ASSERT(src_t1.info()->is_resizable());
    ARM_COMPUTE_ASSERT(dst_t1.info()->is_resizable());

    // Allocate tensors
    src_t0.allocator()->allocate();
    dst_t0.allocator()->allocate();
    src_t1.allocator()->allocate();
    dst_t1.allocator()->allocate();

    ARM_COMPUTE_ASSERT(!src_t0.info()->is_resizable());
    ARM_COMPUTE_ASSERT(!src_t1.info()->is_resizable());

    float min_bound = 0;
    float max_bound = 0;
    std::tie(min_bound, max_bound) = get_activation_layer_test_bounds<float>(ActivationLayerInfo::ActivationFunction::LINEAR, DataType::F32);
    std::uniform_real_distribution<> distribution(min_bound, max_bound);
    library->fill(Accessor(src_t0), distribution, 0);
    library->fill(Accessor(src_t1), distribution, 0);

    std::thread neon_thread1([&] { act_layer_thread0.run(); });
    std::thread neon_thread2([&] { act_layer_thread1.run(); });

    neon_thread1.join();
    neon_thread2.join();

    Window window;
    window.use_tensor_dimensions(dst_t0.info()->tensor_shape());
    Iterator t0_it(&dst_t0, window);
    Iterator t1_it(&dst_t1, window);
    execute_window_loop(window, [&](const Coordinates &)
    {
        const bool match = (*reinterpret_cast<float *>(t0_it.ptr()) == *reinterpret_cast<float *>(t1_it.ptr()));
        ARM_COMPUTE_EXPECT(match, framework::LogLevel::ERRORS);
    },
    t0_it, t1_it);
}
#endif // !defined(BARE_METAL)

TEST_SUITE_END() // RuntimeContext
TEST_SUITE_END() // UNIT
TEST_SUITE_END() // Neon
} // namespace validation
} // namespace test
} // namespace arm_compute
