/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved.
// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// @Authors
//    Fangfang Bai, fangfang@multicorewareinc.com
//    Jin Ma,       jin@multicorewareinc.com
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors as is and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "../perf_precomp.hpp"
#include "opencv2/ts/ocl_perf.hpp"

#ifdef HAVE_OPENCL

namespace opencv_test {
namespace ocl {

#undef VISUALIZE_FLOW
//#define VISUALIZE_FLOW 1

#ifdef VISUALIZE_FLOW
// [TODO] move it to opencv_video/tracking.hpp
// Standard visualization of the optical flow, according to Scharstein/Middlebury (ICCV 2007).
// flow — input flow of CV_32FC2 type (field of motion vectors)
// dst  — output visual optical flow representation of CV_8UC3 type
// maxFlow — max flow magnitude or (if <=0, it's computed automatically; use with care,
//                                  because it 'boosts' flow when there is almost no one).
//           pixels where the motion vector magnitude is equal or exceeds maxFlow will be
//           painted with maximum saturation.
// eps — maxFlow threshold for 'no motion/still scene' cases
// computedMaxFlow — the optional output value to hold the computed maxFlow, to let user
//                   gradually calibrate maxFlow parameter.
static void tsVisualizeFlow(InputArray flowarr, OutputArray dstarr,
                            float maxFlow0 = -1.f, float eps = 1e-3f,
                            float* computedMaxFlow=nullptr)
{
    Mat flow = flowarr.getMat();
    CV_Assert(flow.type() == CV_32FC2);

    // --- color ring (55 colors, RGB) ---
    // sectors: RY=15, YG=6, GC=4, CB=11, BM=13, MR=6
    constexpr int RY=15, YG=6, GC=4, CB=11, BM=13, MR=6;
    constexpr int NCOLS = RY+YG+GC+CB+BM+MR; // 55

    std::array<Vec3b, NCOLS+1> cwheel_; // RGB
    int k = 0;
    for (int i=0;i<RY;i++,k++) cwheel_[k]=Vec3b(0, uint8_t(255*i/RY), 255);
    for (int i=0;i<YG;i++,k++) cwheel_[k]=Vec3b(0, 255, uint8_t(255-255*i/YG));
    for (int i=0;i<GC;i++,k++) cwheel_[k]=Vec3b(uint8_t(255*i/GC), 255, 0);
    for (int i=0;i<CB;i++,k++) cwheel_[k]=Vec3b(255, uint8_t(255-255*i/CB), 0);
    for (int i=0;i<BM;i++,k++) cwheel_[k]=Vec3b(255, 0, uint8_t(255*i/BM));
    for (int i=0;i<MR;i++,k++) cwheel_[k]=Vec3b(uint8_t(255-255*i/MR), 0, 255);
    cwheel_[NCOLS] = cwheel_[0];

    std::vector<float> maxvals(flow.rows);

    // --- compute max flow automatically ---
    if (maxFlow0 <= 0.0f) {
        parallel_for_(cv::Range(0, flow.rows), [&](const cv::Range& range) {
            for (int y = range.start; y < range.end; y++) {
                float maxval = 0.f;
                const cv::Vec2f* row = flow.ptr<cv::Vec2f>(y);
                for (int x = 0; x < flow.cols; x++) {
                    float dx = row[x][0], dy = row[x][1];
                    float mag = std::hypot(dx, dy);
                    maxval = std::max(maxval, mag);
                }
                maxvals[y] = maxval;
            }
        });
        maxFlow0 = 0.f;
        for (int y = 0; y < flow.rows; y++)
            maxFlow0 = std::max(maxFlow0, maxvals[y]);
    }

    maxFlow0 = maxFlow0 > eps ? maxFlow0 : 1.f;
    dstarr.create(flow.size(), CV_8UC3);
    Mat dst = dstarr.getMat();

    // paint the optical flow map
    parallel_for_(cv::Range(0, flow.rows), [&](const cv::Range& range) {
        const Vec3b* cwheel = cwheel_.data();
        float maxval = 0.f, maxflow = maxFlow0;
        for (int y = range.start; y < range.end; y++) {
            const cv::Vec2f* src = flow.ptr<cv::Vec2f>(y);
            cv::Vec3b*       out = dst.ptr<cv::Vec3b>(y);

            for (int x = 0; x < flow.cols; x++) {
                float dx = src[x][0];
                float dy = src[x][1];
                float mag = std::hypot(dx, dy);
                maxval = std::max(maxval, mag);
                mag = std::min(mag / maxflow, 1.f);

                // compute the color from angle
                float a  = std::atan2(-dy, -dx) / static_cast<float>(M_PI);
                float f = (a + 1.0f) * 0.5f * (NCOLS - 1);
                int idx = (int)f;
                f -= idx;

                // 'white' means no motion,
                // the stronger the motion the more saturated the corresponding pixel is.
                Vec3b clr;
                for (int c = 0; c < 3; c++) {
                    float chval = cwheel[idx][c] * (1.f - f) + cwheel[idx + 1][c] * f;
                    chval = 255.f - mag * (255.f - chval);
                    clr[c] = saturate_cast<uint8_t>(chval);
                }
                out[x] = clr;
            }
            maxvals[y] = maxval;
        }
    });

    if (computedMaxFlow) {
        maxFlow0 = 0.f;
        for (int y = 0; y < flow.rows; y++)
            maxFlow0 = std::max(maxFlow0, maxvals[y]);
        *computedMaxFlow = maxFlow0;
    }
}
#endif

///////////// OpticalFlow Dual TVL1 ////////////////////////
typedef tuple< tuple<int, double>, bool> OpticalFlowDualTVL1Params;
typedef TestBaseWithParam<OpticalFlowDualTVL1Params> OpticalFlowDualTVL1Fixture;

OCL_PERF_TEST_P(OpticalFlowDualTVL1Fixture, OpticalFlowDualTVL1,
            ::testing::Combine(
                        ::testing::Values(make_tuple<int, double>(-1, 0.3),
                                          make_tuple<int, double>(3, 0.5)),
                        ::testing::Bool()
                                )
            )
    {
        Mat frame0 = imread(getDataPath("cv/optflow/RubberWhale1.png"), IMREAD_GRAYSCALE);
        ASSERT_FALSE(frame0.empty()) << "can't load RubberWhale1.png";

        Mat frame1 = imread(getDataPath("cv/optflow/RubberWhale2.png"), IMREAD_GRAYSCALE);
        ASSERT_FALSE(frame1.empty()) << "can't load RubberWhale2.png";

        const Size srcSize = frame0.size();

        const OpticalFlowDualTVL1Params params = GetParam();
        const tuple<int, double> filteringScale = get<0>(params);
            const int medianFiltering = get<0>(filteringScale);
            const double scaleStep = get<1>(filteringScale);
        const bool useInitFlow = get<1>(params);
        //double eps = 0.9;

        UMat uFrame0; frame0.copyTo(uFrame0);
        UMat uFrame1; frame1.copyTo(uFrame1);
        UMat uFlow(srcSize, CV_32FC2);
        declare.in(uFrame0, uFrame1, WARMUP_READ).out(uFlow, WARMUP_READ);

        //create algorithm
        Ptr<DualTVL1OpticalFlow> alg = createOptFlow_DualTVL1();

        //set parameters
        alg->setScaleStep(scaleStep);
        alg->setMedianFiltering(medianFiltering);

        if (useInitFlow)
        {
            //calculate initial flow as result of optical flow
            alg->calc(uFrame0, uFrame1, uFlow);
        }

        //set flag to use initial flow
        alg->setUseInitialFlow(useInitFlow);
        OCL_TEST_CYCLE()
            alg->calc(uFrame0, uFrame1, uFlow);

    #ifdef VISUALIZE_FLOW
        imshow("frame0", uFrame0);
        UMat framediff;
        absdiff(uFrame0, uFrame1, framediff);
        imshow("framediff", framediff);

        Mat flow8u;
        tsVisualizeFlow(uFlow, flow8u);
        imshow("uFlow", flow8u);
        waitKey();
    #endif

        //SANITY_CHECK(uFlow, eps, ERROR_RELATIVE);
        SANITY_CHECK_NOTHING();
    }
}

} // namespace opencv_test::ocl

#endif // HAVE_OPENCL
