/*
 *  Copyright (C) 2005-2020 Team Kodi (https://kodi.tv)
 *  Copyright (C) 2012 Marcel Ebmer
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSE.md for more information.
 */

#include "fische_internal.h"

#include <chrono>
#include <thread>

#ifdef DEBUG
#include <stdio.h>
#endif

void blur_worker (struct _fische__blurworker_* params)
{
    uint_fast16_t const width = params->width;
    uint_fast16_t const width_x2 = 2 * width;
    uint_fast16_t const y_start = params->y_start;
    uint_fast16_t const y_end = params->y_end;

    uint32_t source_component[4];

    uint_fast16_t const two_lines = 2 * width;
    uint_fast16_t const one_line = width;
    uint_fast16_t const two_columns	= 2;

    uint_fast16_t x, y;
    int_fast8_t vector_x, vector_y;

    while (!params->kill) {

        if (!params->work) {
            std::this_thread::sleep_for(std::chrono::microseconds(1));
            continue;
        }

        uint32_t* source = params->source;
        uint32_t* source_pixel;

        uint32_t* destination = params->destination;
        uint32_t* destination_pixel = destination + y_start * width;

        int8_t* vectors = (int8_t*) params->vectors;
        int8_t*	vector_pointer = vectors + y_start * width_x2;

        // vertical loop
        for (y = y_start; y < y_end; y ++) {
            // horizontal loop
            for (x = 0; x < width; x ++) {

                // read the motion vector (actually its opposite)
                vector_x = * (vector_pointer + 0);
                vector_y = * (vector_pointer + 1);

                // point to the pixel at [present + motion vector]
                source_pixel = source + (y + vector_y) * width + x + vector_x;

                // read the pixels at [source + (2,1)]   [source + (-2,1)]   [source + (0,-2)]
                // shift them right by 2 and remove the bits that overflow each byte
                source_component[0] = (* (source_pixel + one_line - two_columns) >> 2) & 0x3f3f3f3f;
                source_component[1] = (* (source_pixel + one_line + two_columns) >> 2) & 0x3f3f3f3f;
                source_component[2] = (* (source_pixel - two_lines) >> 2) & 0x3f3f3f3f;
                source_component[3] = (* (source_pixel) >> 2) & 0x3f3f3f3f;

                // add those four components and write to the destination
                // increment destination pointer
                * (destination_pixel ++) = source_component[0]
                                           + source_component[1]
                                           + source_component[2]
                                           + source_component[3];

                // increment vector source pointer
                vector_pointer += 2;
            }
        }

        // mark work as done
        params->work = 0;
    }

    return;
}

struct fische__blurengine*
fische__blurengine_new (struct fische* parent) {

    struct fische__blurengine* retval = static_cast<fische__blurengine*>(malloc (sizeof (struct fische__blurengine)));
    retval->priv = static_cast<_fische__blurengine_*>(malloc (sizeof (struct _fische__blurengine_)));
    struct _fische__blurengine_* P = retval->priv;

    P->fische = parent;
    P->width = parent->width;
    P->height = parent->height;
    P->threads = parent->used_cpus;
    P->sourcebuffer = FISCHE_PRIVATE(P)->screenbuffer->pixels;
    P->destinationbuffer = static_cast<uint32_t*>(malloc (P->width * P->height * sizeof (uint32_t)));

    uint_fast8_t i;
    for (i = 0; i < P->threads; ++ i) {
        P->worker[i].source = P->sourcebuffer;
        P->worker[i].destination = P->destinationbuffer;
        P->worker[i].vectors = 0;
        P->worker[i].width = P->width;
        P->worker[i].y_start = (i * P->height) / P->threads;
        P->worker[i].y_end = ( (i + 1) * P->height) / P->threads;
        P->worker[i].kill = 0;
        P->worker[i].work = 0;

        P->worker[i].thread = new std::thread(blur_worker, &P->worker[i]);
    }

    return retval;
}

void
fische__blurengine_free (struct fische__blurengine* self)
{
    if (!self)
        return;

    struct _fische__blurengine_* P = self->priv;

    uint_fast8_t i;
    for (i = 0; i < P->threads; ++ i) {
        P->worker[i].kill = 1;
        P->worker[i].thread->join();
        delete P->worker[i].thread;
        P->worker[i].thread = nullptr;
    }

    free (self->priv->destinationbuffer);
    free (self->priv);
    free (self);
}

void
fische__blurengine_blur (struct fische__blurengine* self, uint16_t* vectors)
{
    struct _fische__blurengine_* P = self->priv;
    uint_fast8_t i;
    for (i = 0; i < P->threads; ++ i) {
        P->worker[i].source = P->sourcebuffer;
        P->worker[i].destination = P->destinationbuffer;
        P->worker[i].vectors = vectors;
        P->worker[i].work = 1;
    }
}

void
fische__blurengine_swapbuffers (struct fische__blurengine* self)
{
    struct _fische__blurengine_* P = self->priv;

    // wait for all workers to finish
    uint_fast8_t work = 1;
    while (work) {

        work = 0;
        uint_fast8_t i;
        for (i = 0; i < P->threads; ++ i) {
            work += P->worker[i].work;
        }

        if (work)
            std::this_thread::sleep_for(std::chrono::microseconds(1));
    }

    uint32_t* t = P->destinationbuffer;
    P->destinationbuffer = P->sourcebuffer;
    P->sourcebuffer = t;
    FISCHE_PRIVATE(P)->screenbuffer->pixels = t;
}
