#include "test_precomp.hpp"

using namespace cv;

struct CV_EXPORTS L2Fake : public L2<float>
{
    enum { normType = NORM_L2 };
};

class CV_BruteForceMatcherTest : public cvtest::BaseTest
{
public:
    CV_BruteForceMatcherTest() {}
protected:
    void run( int )
    {
        const int dimensions = 64;
        const int descriptorsNumber = 5000;

        Mat train = Mat( descriptorsNumber, dimensions, CV_32FC1);
        Mat query = Mat( descriptorsNumber, dimensions, CV_32FC1);

        Mat permutation( 1, descriptorsNumber, CV_32SC1 );
        for( int i=0;i<descriptorsNumber;i++ )
            permutation.at<int>( 0, i ) = i;

        //RNG rng = RNG( cvGetTickCount() );
        RNG rng;
        randShuffle( permutation, 1, &rng );

        float boundary =  500.f;
        for( int row=0;row<descriptorsNumber;row++ )
        {
            for( int col=0;col<dimensions;col++ )
            {
                int bit = rng( 2 );
                train.at<float>( permutation.at<int>( 0, row ), col ) = bit*boundary + rng.uniform( 0.f, boundary );
                query.at<float>( row, col ) = bit*boundary + rng.uniform( 0.f, boundary );
            }
        }

        vector<DMatch> specMatches, genericMatches;
        BruteForceMatcher<L2<float> > specMatcher;
        BruteForceMatcher<L2Fake > genericMatcher;

        int64 time0 = cvGetTickCount();
        specMatcher.match( query, train, specMatches );
        int64 time1 = cvGetTickCount();
        genericMatcher.match( query, train, genericMatches );
        int64 time2 = cvGetTickCount();

        float specMatcherTime = float(time1 - time0)/(float)cvGetTickFrequency();
        ts->printf( cvtest::TS::LOG, "Matching by matrix multiplication time s: %f, us per pair: %f\n",
                   specMatcherTime*1e-6, specMatcherTime/( descriptorsNumber*descriptorsNumber ) );

        float genericMatcherTime = float(time2 - time1)/(float)cvGetTickFrequency();
        ts->printf( cvtest::TS::LOG, "Matching without matrix multiplication time s: %f, us per pair: %f\n",
                   genericMatcherTime*1e-6, genericMatcherTime/( descriptorsNumber*descriptorsNumber ) );

        if( (int)specMatches.size() != descriptorsNumber || (int)genericMatches.size() != descriptorsNumber )
            ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
        for( int i=0;i<descriptorsNumber;i++ )
        {
            float epsilon = 0.01f;
            bool isEquiv = fabs( specMatches[i].distance - genericMatches[i].distance ) < epsilon &&
                           specMatches[i].queryIdx == genericMatches[i].queryIdx &&
                           specMatches[i].trainIdx == genericMatches[i].trainIdx;
            if( !isEquiv || specMatches[i].trainIdx != permutation.at<int>( 0, i ) )
            {
                ts->set_failed_test_info( cvtest::TS::FAIL_MISMATCH );
                break;
            }
        }


        //Test mask
        Mat mask( query.rows, train.rows, CV_8UC1 );
        rng.fill( mask, RNG::UNIFORM, 0, 2 );


        time0 = cvGetTickCount();
        specMatcher.match( query, train, specMatches, mask );
        time1 = cvGetTickCount();
        genericMatcher.match( query, train, genericMatches, mask );
        time2 = cvGetTickCount();

        specMatcherTime = float(time1 - time0)/(float)cvGetTickFrequency();
        ts->printf( cvtest::TS::LOG, "Matching by matrix multiplication time with mask s: %f, us per pair: %f\n",
                   specMatcherTime*1e-6, specMatcherTime/( descriptorsNumber*descriptorsNumber ) );

        genericMatcherTime = float(time2 - time1)/(float)cvGetTickFrequency();
        ts->printf( cvtest::TS::LOG, "Matching without matrix multiplication time with mask s: %f, us per pair: %f\n",
                   genericMatcherTime*1e-6, genericMatcherTime/( descriptorsNumber*descriptorsNumber ) );

        if( specMatches.size() != genericMatches.size() )
            ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );

        for( size_t i=0;i<specMatches.size();i++ )
        {
            //float epsilon = 1e-2;
            float epsilon = 10000000;
            bool isEquiv = fabs( specMatches[i].distance - genericMatches[i].distance ) < epsilon &&
                           specMatches[i].queryIdx == genericMatches[i].queryIdx &&
                           specMatches[i].trainIdx == genericMatches[i].trainIdx;
            if( !isEquiv )
            {
                ts->set_failed_test_info( cvtest::TS::FAIL_MISMATCH );
                break;
            }
        }
    }
};

TEST(Legacy_BruteForceMatcher, accuracy) { CV_BruteForceMatcherTest test; test.safe_run(); }
