//    This file is part of Motion Watch.

//    Motion Watch is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.

//    Motion Watch is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.

#include "mo_detect.h"

bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share)
{
    auto diff = 0;

    if (pixA > pixB) diff = pixA - pixB;
    if (pixB > pixA) diff = pixB - pixA;

    if (diff < share->colorThresh)
    {
        diff = 0;
    }

    return diff != 0;
}

void secDiff(Mat imgA, Mat imgB, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share)
{
    auto pnts = 0;

    for (auto y = rowOffs; y < rows; y++)
    {
        for (auto x = colOffs; x < cols; x++)
        {
            auto pixA = imgA.at<uchar>(Point(x, y));
            auto pixB = imgB.at<uchar>(Point(x, y));

            if (pixDiff(pixA, pixB, share))
            {
                pnts += 1;
            }
        }
    }

    struct sec_t res;

    res.x       = colOffs;
    res.y       = rowOffs;
    res.xSize   = cols;
    res.ySize   = rows;
    res.pixDiff = pnts;

    lock_guard<mutex> guard(*secMutex);

    results->push_back(res);
}

bool imgDiff(Mat prev, Mat next, Rect *block, shared_t *share)
{
    auto numOfXBlocks = prev.cols / share->blockX;
    auto numOfYBlocks = prev.rows / share->blockY;
    auto moInBlock    = false;

    vector<thread> threads;
    vector<sec_t>  results;
    mutex          secMutex;

    for (auto x = 0; x < numOfXBlocks; x += share->blockX)
    {
        // spawn all of the block motion detection threads.
        for (auto y = 0; y < numOfYBlocks; y += share->blockY)
        {
            threads.push_back(thread(secDiff, prev, next, share->blockY, share->blockX, y, x, &results, &secMutex, share));
        }
    }

    for (auto &&thr : threads)
    {
        // wait for all of the threads to finish.
        thr.join();
    }

    auto maxPixDiff = 0;
    auto blockPick  = 0;

    for (auto i = 0; i < results.size(); ++i)
    {
        // out of all of the results returned form the threads, pick
        // the block with the highest amount of pixDiff.
        share->stat << "block_thread:" << " x=" << results[i].x << " y=" << results[i].y << " pixdiff=" << results[i].pixDiff << endl;

        if ((results[i].pixDiff >= share->blockThresh) && (results[i].pixDiff > maxPixDiff))
        {
            maxPixDiff = results[i].pixDiff;
            blockPick  = i;
        }
    }

    if (maxPixDiff >= share->blockThresh)
    {
        // return true on this function with the block with the
        // high pixDiff value which should be at or exceeds
        // the block_threshold set by the conf file.
        auto res = results[blockPick];

        block->x      = res.x;
        block->y      = res.y;
        block->height = res.ySize;
        block->width  = res.xSize;

        moInBlock = true;
    }

    return moInBlock;
}

bool moDetect(const string &buffFile, Rect *block, Mat *img, shared_t *share)
{
    auto mod = false;

    VideoCapture capture(buffFile.c_str(), CAP_FFMPEG);

    if (capture.isOpened())
    {
        Mat prev;
        Mat next;

        while (capPair(prev, next, capture, share))
        {
            if (imgDiff(toGray(prev), toGray(next), block, share))
            {
                *img = next;
                mod  = true; break;
            }
        }
    }
    else
    {
        cerr << "err: Could not open buff file: " << buffFile << " for reading. check formatting/permissions." << endl;
        cerr << "     Also check if opencv was compiled with FFMPEG encoding enabled." << endl;
    }

    return mod;
}
