// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#include <opencv2/core/async.hpp>
#include <opencv2/core/detail/async_promise.hpp>

#include <opencv2/core/bindings_utils.hpp>

#if defined(CV_CXX11) && !defined(OPENCV_DISABLE_THREAD_SUPPORT)
#include <thread>
#include <chrono>
#endif

namespace opencv_test { namespace {

TEST(Core_Async, BasicCheck)
{
    Mat m(3, 3, CV_32FC1, Scalar::all(5.0f));
    AsyncPromise p;
    AsyncArray r = p.getArrayResult();
    EXPECT_TRUE(r.valid());

    // Follow the limitations of std::promise::get_future
    // https://en.cppreference.com/w/cpp/thread/promise/get_future
    EXPECT_THROW(AsyncArray r2 = p.getArrayResult(), cv::Exception);

    p.setValue(m);

    Mat m2;
    r.get(m2);
    EXPECT_EQ(0, cvtest::norm(m, m2, NORM_INF));

    // Follow the limitations of std::future::get
    // https://en.cppreference.com/w/cpp/thread/future/get
    EXPECT_FALSE(r.valid());
    Mat m3;
    EXPECT_THROW(r.get(m3), cv::Exception);
}

TEST(Core_Async, ExceptionCheck)
{
    Mat m(3, 3, CV_32FC1, Scalar::all(5.0f));
    AsyncPromise p;
    AsyncArray r = p.getArrayResult();
    EXPECT_TRUE(r.valid());

    try
    {
        CV_Error(Error::StsOk, "Test: Generated async error");
    }
    catch (const cv::Exception& e)
    {
        p.setException(e);
    }

    try {
        Mat m2;
        r.get(m2);
        FAIL() << "Exception is expected";
    }
    catch (const cv::Exception& e)
    {
        EXPECT_EQ(Error::StsOk, e.code) << e.what();
    }

    // Follow the limitations of std::future::get
    // https://en.cppreference.com/w/cpp/thread/future/get
    EXPECT_FALSE(r.valid());
}


TEST(Core_Async, LikePythonTest)
{
    Mat m(3, 3, CV_32FC1, Scalar::all(5.0f));
    AsyncArray r = cv::utils::testAsyncArray(m);
    EXPECT_TRUE(r.valid());
    Mat m2;
    r.get(m2);
    EXPECT_EQ(0, cvtest::norm(m, m2, NORM_INF));

    // Follow the limitations of std::future::get
    // https://en.cppreference.com/w/cpp/thread/future/get
    EXPECT_FALSE(r.valid());
}


#if defined(CV_CXX11) && !defined(OPENCV_DISABLE_THREAD_SUPPORT)

TEST(Core_Async, AsyncThread_Simple)
{
    Mat m(3, 3, CV_32FC1, Scalar::all(5.0f));
    AsyncPromise p;
    AsyncArray r = p.getArrayResult();

    std::thread t([&]{
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        try {
            p.setValue(m);
        } catch (const std::exception& e) {
            std::cout << e.what() << std::endl;
        } catch (...) {
            std::cout << "Unknown C++ exception" << std::endl;
        }
    });

    try
    {
        Mat m2;
        r.get(m2);
        EXPECT_EQ(0, cvtest::norm(m, m2, NORM_INF));

        t.join();
    }
    catch (...)
    {
        t.join();
        throw;
    }
}

TEST(Core_Async, AsyncThread_DetachedResult)
{
    Mat m(3, 3, CV_32FC1, Scalar::all(5.0f));
    AsyncPromise p;
    {
        AsyncArray r = p.getArrayResult();
        r.release();
    }

    bool exception_ok = false;

    std::thread t([&]{
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        try {
            p.setValue(m);
        } catch (const cv::Exception& e) {
            if (e.code == Error::StsError)
                exception_ok = true;
            else
                std::cout << e.what() << std::endl;
        } catch (const std::exception& e) {
            std::cout << e.what() << std::endl;
        } catch (...) {
            std::cout << "Unknown C++ exception" << std::endl;
        }
    });
    t.join();

    EXPECT_TRUE(exception_ok);
}

#endif

}} // namespace
