/*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) 2015, University of Ostrava, Institute for Research and Applications of Fuzzy Modeling,
// Pavel Vlasanek, all rights reserved. Third party copyrights are property of their respective owners.
//
// 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 "precomp.hpp"

using namespace cv;

void ft::FT02D_FL_process(InputArray matrix, const int radius, OutputArray output)
{
    CV_Assert(matrix.channels() == 3);

    int borderPadding = 2 * radius + 1;
    Mat imagePadded;

    copyMakeBorder(matrix, imagePadded, radius, borderPadding, radius, borderPadding, BORDER_CONSTANT, Scalar(0));

    Mat channel[3];
    split(imagePadded, channel);

    uchar *im_r = channel[2].data;
    uchar *im_g = channel[1].data;
    uchar *im_b = channel[0].data;

    int width = imagePadded.cols;
    int height = imagePadded.rows;
    int n_width = width / radius + 1;
    int n_height = height / radius + 1;

    std::vector<uchar> c_r(n_width * n_height);
    std::vector<uchar> c_g(n_width * n_height);
    std::vector<uchar> c_b(n_width * n_height);

    int sum_r, sum_g, sum_b, num, c_wei;
    int c_pos, pos, pos2, wy;
    int cy = 0;
    float num_f;

    std::vector<int> wei(radius + 1);

    for (int i = 0; i <= radius; i++)
    {
        wei[i] = radius - i;
    }

    for (int y = radius; y < height - radius; y += radius)
    {
        c_pos = cy;

        for (int x = radius; x < width - radius; x += radius)
        {
            num = sum_r = sum_g = sum_b = 0;

            for (int y1 = y - radius; y1 <= y + radius; y1++)
            {
                pos = y1 * width;
                wy = wei[abs(y1 - y)];

                for (int x1 = x - radius; x1 <= x + radius; x1++)
                {
                    c_wei = wei[abs(x1 - x)] * wy;
                    pos2 = pos + x1;
                    sum_r += im_r[pos2] * c_wei;
                    sum_g += im_g[pos2] * c_wei;
                    sum_b += im_b[pos2] * c_wei;
                    num += c_wei;
                }
            }

            num_f = 1.0f / (float)num;

            c_r[c_pos] = (uchar)cvRound(sum_r * num_f);
            c_g[c_pos] = (uchar)cvRound(sum_g * num_f);
            c_b[c_pos] = (uchar)cvRound(sum_b * num_f);

            c_pos++;
        }

        cy += n_width;
    }

    int p1, p2, p3, p4, yw, w1, w2, w3, w4, lx, ly, lx1, ly1, pos_iFT;
    float num_iFT;

    int output_height = matrix.rows();
    int output_width = matrix.cols();

    Mat compR(output_height, output_width, CV_8UC1);
    Mat compG(output_height, output_width, CV_8UC1);
    Mat compB(output_height, output_width, CV_8UC1);

    uchar *img_r = compR.ptr();
    uchar *img_g = compG.ptr();
    uchar *img_b = compB.ptr();

    for (int y = 0; y < output_height; y++)
    {
        ly1 = (y % radius);
        ly = radius - ly1;
        yw = y / radius * n_width;
        pos_iFT = y * output_width;

        for (int x = 0; x < output_width; x++)
        {
            lx1 = (x % radius);
            lx = radius - lx1;

            p1 = x / radius + yw;
            p2 = p1 + 1;
            p3 = p1 + n_width;
            p4 = p3 + 1;

            w1 = lx * ly;
            w2 = lx1 * ly;
            w3 = lx * ly1;
            w4 = lx1 * ly1;

            num_iFT = 1.0f / (float)(w1 + w2 + w3 + w4);

            img_r[pos_iFT] = (uchar)((c_r[p1] * w1 + c_r[p2] * w2 + c_r[p3] * w3 + c_r[p4] * w4) * num_iFT);
            img_g[pos_iFT] = (uchar)((c_g[p1] * w1 + c_g[p2] * w2 + c_g[p3] * w3 + c_g[p4] * w4) * num_iFT);
            img_b[pos_iFT] = (uchar)((c_b[p1] * w1 + c_b[p2] * w2 + c_b[p3] * w3 + c_b[p4] * w4) * num_iFT);

            pos_iFT++;
        }
    }

    std::vector<Mat> oComp;

    oComp.push_back(compB);
    oComp.push_back(compG);
    oComp.push_back(compR);

    merge(oComp, output);
}

void ft::FT02D_FL_process_float(InputArray matrix, const int radius, OutputArray output)
{
    CV_Assert(matrix.channels() == 3);

    int borderPadding = 2 * radius + 1;
    Mat imagePadded;

    copyMakeBorder(matrix, imagePadded, radius, borderPadding, radius, borderPadding, BORDER_CONSTANT, Scalar(0));

    Mat channel[3];
    split(imagePadded, channel);

    uchar *im_r = channel[2].data;
    uchar *im_g = channel[1].data;
    uchar *im_b = channel[0].data;

    int width = imagePadded.cols;
    int height = imagePadded.rows;
    int n_width = width / radius + 1;
    int n_height = height / radius + 1;

    std::vector<float> c_r(n_width * n_height);
    std::vector<float> c_g(n_width * n_height);
    std::vector<float> c_b(n_width * n_height);

    int sum_r, sum_g, sum_b, num, c_wei;
    int c_pos, pos, pos2, wy;
    int cy = 0;
    float num_f;

    std::vector<int> wei(radius + 1);

    for (int i = 0; i <= radius; i++)
    {
        wei[i] = radius - i;
    }

    for (int y = radius; y < height - radius; y += radius)
    {
        c_pos = cy;

        for (int x = radius; x < width - radius; x += radius)
        {
            num = sum_r = sum_g = sum_b = 0;

            for (int y1 = y - radius; y1 <= y + radius; y1++)
            {
                pos = y1 * width;
                wy = wei[abs(y1 - y)];

                for (int x1 = x - radius; x1 <= x + radius; x1++)
                {
                    c_wei = wei[abs(x1 - x)] * wy;
                    pos2 = pos + x1;
                    sum_r += im_r[pos2] * c_wei;
                    sum_g += im_g[pos2] * c_wei;
                    sum_b += im_b[pos2] * c_wei;
                    num += c_wei;
                }
            }

            num_f = 1.0f / (float)num;

            c_r[c_pos] = sum_r * num_f;
            c_g[c_pos] = sum_g * num_f;
            c_b[c_pos] = sum_b * num_f;

            c_pos++;
        }

        cy += n_width;
    }

    int p1, p2, p3, p4, yw, w1, w2, w3, w4, lx, ly, lx1, ly1, pos_iFT;
    float num_iFT;

    int output_height = matrix.rows();
    int output_width = matrix.cols();

    Mat compR(output_height, output_width, CV_32FC1);
    Mat compG(output_height, output_width, CV_32FC1);
    Mat compB(output_height, output_width, CV_32FC1);

    float *img_r = compR.ptr<float>();
    float *img_g = compG.ptr<float>();
    float *img_b = compB.ptr<float>();

    for (int y = 0; y < output_height; y++)
    {
        ly1 = (y % radius);
        ly = radius - ly1;
        yw = y / radius * n_width;
        pos_iFT = y * output_width;

        for (int x = 0; x < output_width; x++)
        {
            lx1 = (x % radius);
            lx = radius - lx1;

            p1 = x / radius + yw;
            p2 = p1 + 1;
            p3 = p1 + n_width;
            p4 = p3 + 1;

            w1 = lx * ly;
            w2 = lx1 * ly;
            w3 = lx * ly1;
            w4 = lx1 * ly1;

            num_iFT = 1.0f / (float)(w1 + w2 + w3 + w4);

            img_r[pos_iFT] = (c_r[p1] * w1 + c_r[p2] * w2 + c_r[p3] * w3 + c_r[p4] * w4) * num_iFT;
            img_g[pos_iFT] = (c_g[p1] * w1 + c_g[p2] * w2 + c_g[p3] * w3 + c_g[p4] * w4) * num_iFT;
            img_b[pos_iFT] = (c_b[p1] * w1 + c_b[p2] * w2 + c_b[p3] * w3 + c_b[p4] * w4) * num_iFT;

            pos_iFT++;
        }
    }

    std::vector<Mat> oComp;

    oComp.push_back(compB);
    oComp.push_back(compG);
    oComp.push_back(compR);

    merge(oComp, output);
}

void ft::FT02D_components(InputArray matrix, InputArray kernel, OutputArray components, InputArray mask)
{
    CV_Assert(matrix.channels() == kernel.channels());

    Mat inputMask;

    if (mask.getMat().empty())
    {
        inputMask = Mat::ones(matrix.size(), CV_8U);
    }
    else
    {
        CV_Assert(mask.channels() == 1);

        inputMask = mask.getMat();
    }

    int radiusX = (kernel.cols() - 1) / 2;
    int radiusY = (kernel.rows() - 1) / 2;
    int An = matrix.cols() / radiusX + 1;
    int Bn = matrix.rows() / radiusY + 1;

    Mat matrixPadded;
    Mat maskPadded;

    copyMakeBorder(matrix, matrixPadded, radiusY, kernel.rows(), radiusX, kernel.cols(), BORDER_CONSTANT, Scalar(0));
    copyMakeBorder(inputMask, maskPadded, radiusY, kernel.rows(), radiusX, kernel.cols(), BORDER_CONSTANT, Scalar(0));

    components.create(Bn, An, CV_MAKETYPE(CV_32F, matrix.channels()));

    Mat componentsMat = components.getMat();

    for (int i = 0; i < An; i++)
    {
        for (int o = 0; o < Bn; o++)
        {
            int centerX = (i * radiusX) + radiusX;
            int centerY = (o * radiusY) + radiusY;
            Rect area(centerX - radiusX, centerY - radiusY, kernel.cols(), kernel.rows());

            Mat roiImage(matrixPadded, area);
            Mat roiMask(maskPadded, area);
            Mat kernelMasked;

            kernel.copyTo(kernelMasked, roiMask);

            Mat numerator;
            multiply(roiImage, kernelMasked, numerator, 1, CV_32F);

            Scalar value;
            divide(sum(numerator), sum(kernelMasked), value, 1, CV_32F);

            componentsMat.row(o).col(i).setTo(value);
        }
    }
}

void ft::FT02D_inverseFT(InputArray components, InputArray kernel, OutputArray output, int width, int height)
{
    CV_Assert(components.channels() == 1 && kernel.channels() == 1);

    Mat componentsMat = components.getMat();

    int radiusX = (kernel.cols() - 1) / 2;
    int radiusY = (kernel.rows() - 1) / 2;
    int outputWidthPadded = radiusX + width + kernel.cols();
    int outputHeightPadded = radiusY + height + kernel.rows();

    output.create(height, width, CV_32F);

    Mat outputZeroes(outputHeightPadded, outputWidthPadded, CV_32F, Scalar(0));

    for (int i = 0; i < componentsMat.cols; i++)
    {
        for (int o = 0; o < componentsMat.rows; o++)
        {
            int centerX = (i * radiusX) + radiusX;
            int centerY = (o * radiusY) + radiusY;
            Rect area(centerX - radiusX, centerY - radiusY, kernel.cols(), kernel.rows());

            float component = componentsMat.at<float>(o, i);

            Mat inverse;
            multiply(kernel, component, inverse, 1, CV_32F);

            Mat roiOutput(outputZeroes, area);
            add(roiOutput, inverse, roiOutput);
        }
    }

    outputZeroes(Rect(radiusX, radiusY, width, height)).copyTo(output);
}

void ft::FT02D_process(InputArray matrix, InputArray kernel, OutputArray output, InputArray mask)
{
    CV_Assert(matrix.channels() == kernel.channels());

    Mat inputMask;

    if (mask.getMat().empty())
    {
        inputMask = Mat::ones(matrix.size(), CV_8U);
    }
    else
    {
        CV_Assert(mask.channels() == 1);

        inputMask = mask.getMat();
    }

    int radiusX = (kernel.cols() - 1) / 2;
    int radiusY = (kernel.rows() - 1) / 2;
    int An = matrix.cols() / radiusX + 1;
    int Bn = matrix.rows() / radiusY + 1;
    int outputWidthPadded = radiusX + matrix.cols() + kernel.cols();
    int outputHeightPadded = radiusY + matrix.rows() + kernel.rows();

    Mat matrixPadded;
    Mat maskPadded;

    output.create(matrix.size(), CV_MAKETYPE(CV_32F, matrix.channels()));

    Mat outputZeroes(outputHeightPadded, outputWidthPadded, output.type(), Scalar(0));

    copyMakeBorder(matrix, matrixPadded, radiusY, kernel.rows(), radiusX, kernel.cols(), BORDER_CONSTANT, Scalar(0));
    copyMakeBorder(inputMask, maskPadded, radiusY, kernel.rows(), radiusX, kernel.cols(), BORDER_CONSTANT, Scalar(0));

    for (int i = 0; i < An; i++)
    {
        for (int o = 0; o < Bn; o++)
        {
            int centerX = (i * radiusX) + radiusX;
            int centerY = (o * radiusY) + radiusY;
            Rect area(centerX - radiusX, centerY - radiusY, kernel.cols(), kernel.rows());

            Mat roiMatrix(matrixPadded, area);
            Mat roiMask(maskPadded, area);
            Mat kernelMasked;

            kernel.copyTo(kernelMasked, roiMask);

            Mat numerator;
            multiply(roiMatrix, kernelMasked, numerator, 1, CV_32F);

            Scalar component;
            divide(sum(numerator), sum(kernelMasked), component, 1, CV_32F);

            Mat inverse;
            multiply(kernel, component, inverse, 1, CV_32F);

            Mat roiOutput(outputZeroes, area);
            add(roiOutput, inverse, roiOutput);
        }
    }

    outputZeroes(Rect(radiusX, radiusY, matrix.cols(), matrix.rows())).copyTo(output);
}

int ft::FT02D_iteration(InputArray matrix, InputArray kernel, OutputArray output, InputArray mask, OutputArray maskOutput, bool firstStop)
{
    CV_Assert(matrix.channels() == kernel.channels() && mask.channels() == 1);

    int radiusX = (kernel.cols() - 1) / 2;
    int radiusY = (kernel.rows() - 1) / 2;
    int An = matrix.cols() / radiusX + 1;
    int Bn = matrix.rows() / radiusY + 1;
    int outputWidthPadded = radiusX + matrix.cols() + kernel.cols();
    int outputHeightPadded = radiusY + matrix.rows() + kernel.rows();
    int undefinedComponents = 0;

    output.create(matrix.size(), CV_MAKETYPE(CV_32F, matrix.channels()));
    output.setTo(0);

    if (maskOutput.needed())
    {
        maskOutput.create(mask.rows(), mask.cols(), CV_8UC1);
        maskOutput.setTo(1);
    }

    Mat matrixOutputMat = Mat::zeros(outputHeightPadded, outputWidthPadded, CV_MAKETYPE(CV_32F, matrix.channels()));
    Mat maskOutputMat = Mat::ones(outputHeightPadded, outputWidthPadded, CV_8UC1);

    Mat matrixPadded;
    Mat maskPadded;

    copyMakeBorder(matrix, matrixPadded, radiusY, kernel.rows(), radiusX, kernel.cols(), BORDER_CONSTANT, Scalar(0));
    copyMakeBorder(mask, maskPadded, radiusY, kernel.rows(), radiusX, kernel.cols(), BORDER_CONSTANT, Scalar(0));

    for (int i = 0; i < An; i++)
    {
        for (int o = 0; o < Bn; o++)
        {
            int centerX = (i * radiusX) + radiusX;
            int centerY = (o * radiusY) + radiusY;
            Rect area(centerX - radiusX, centerY - radiusY, kernel.cols(), kernel.rows());

            Mat roiMatrix(matrixPadded, area);
            Mat roiMask(maskPadded, area);
            Mat kernelMasked;

            kernel.copyTo(kernelMasked, roiMask);

            Mat numerator;
            multiply(roiMatrix, kernelMasked, numerator, 1, CV_32F);

            Scalar denominator = sum(kernelMasked);

            if (denominator[0] == 0)
            {
                if (firstStop)
                {
                    matrixOutputMat = matrixPadded(Rect(radiusX, radiusY, matrix.cols(), matrix.rows()));
                    maskOutputMat = maskPadded(Rect(radiusX, radiusY, matrix.cols(), matrix.rows()));

                    return -1;
                }
                else
                {
                    undefinedComponents++;

                    Mat roiMaskOutput(maskOutputMat, Rect(centerX - radiusX + 1, centerY - radiusY + 1, kernel.cols() - 2, kernel.rows() - 2));
                    roiMaskOutput.setTo(0);

                    continue;
                }
            }

            Scalar component;
            divide(sum(numerator), denominator, component, 1, CV_32F);

            Mat inverse;
            multiply(kernel, component, inverse, 1, CV_32F);

            Mat roiMatrixOutput(matrixOutputMat, area);
            add(roiMatrixOutput, inverse, roiMatrixOutput);
        }
    }

    matrixOutputMat(Rect(radiusX, radiusY, matrix.cols(), matrix.rows())).copyTo(output);

    if (maskOutput.needed())
    {
        maskOutputMat(Rect(radiusX, radiusY, matrix.cols(), matrix.rows())).copyTo(maskOutput);
    }

    return undefinedComponents;
}
