/* @file blur_box_alpha_.c
 * Defines the following functions:
 * _box_blur_alpha_horiz_step
 * _box_blur_alpha_vert_step
 */

#include "evas_filter_private.h"

static inline void
_box_blur_alpha_horiz_step(const uint8_t* restrict srcdata, int src_stride EINA_UNUSED,
                           uint8_t* restrict dstdata, int dst_stride EINA_UNUSED,
                           const int* restrict const radii,
                           Eina_Rectangle region)
{
   const int len = region.w;
   const int loops = region.h;

   const DATA8* restrict src;
   DATA8* restrict dst;
   DATA8* restrict span1;
   DATA8* restrict span2;

#if DIV_USING_BITSHIFT
   int pow2_shifts[6] = {0};
   int numerators[6] = {0};
   for (int run = 0; radii[run]; run++)
     {
        const int div = radii[run] * 2 + 1;
        pow2_shifts[run] = evas_filter_smallest_pow2_larger_than(div << 10);
        numerators[run] = (1 << pow2_shifts[run]) / (div);
     }
#endif

   span1 = alloca(len);
   span2 = alloca(len);
   memset(span1, 0, len);
   memset(span2, 0, len);

   // For each line, apply as many blurs as requested
   for (int l = 0; l < loops; l++)
     {
        int run;

        // New line: reset source & destination pointers
        src = srcdata + len * l;
        if (!radii[1]) // Only one run
          dst = dstdata + len * l;
        else
          dst = span1;

        // Apply blur with current radius
        for (run = 0; radii[run]; run++)
          {
             const int radius = radii[run];
             const int left = MIN(radius, len);

#if DIV_USING_BITSHIFT
             const int pow2 = pow2_shifts[run];
             const int numerator = numerators[run];
#else
             const int divider = 2 * radius + 1;
#endif

             const DATA8* restrict sl = src;
             const DATA8* restrict sr = src;
             const DATA8* restrict sre = src + len;
             const DATA8* restrict sle = src + len - radius;
             DATA8* restrict d = dst;
             int acc = 0, count = 0;

             // Read-ahead & accumulate
             for (int x = left; x > 0; x--)
               {
                  acc += *sr++;
                  count++;
               }

             // Left edge
             for (int x = left; x > 0; x--)
               {
                  if (sr < sre)
                    {
                       acc += *sr++;
                       count++;
                    }

                  *d++ = acc / count;
               }

             // Middle part, normal blur
             while (sr < sre)
               {
                  acc += *sr++;
                  *d++ = DIVIDE(acc);
                  acc -= *sl++;
               }

             // Right edge
             count = 2 * radius + 1;
             while (sl < sle)
               {
                  *d++ = acc / (--count);
                  acc -= *sl++;
               }

             // More runs to go: swap spans
             if (radii[run + 1])
               {
                  src = dst;
                  if (radii[run + 2])
                    {
                       // Two more runs: swap
                       DATA8* swap = span1;
                       span1 = span2;
                       span2 = swap;
                       dst = span1;
                    }
                  else
                    {
                       // Last run: write directly to dstdata
                       dst = dstdata + len * l;
                    }
               }
          }
     }
}

// ATTENTION: Make sure the below code's inner loop is the SAME as above.

static inline void
_box_blur_alpha_vert_step(const uint8_t* restrict srcdata, int src_stride  EINA_UNUSED,
                          uint8_t* restrict dstdata, int dst_stride EINA_UNUSED,
                          const int* restrict const radii,
                          Eina_Rectangle region)
{
   /* Note: This function tries to optimize cache hits by working on
    * contiguous horizontal spans.
    */

   const int len = region.h;
   const int loops = region.w;

   const int step = loops;
   DATA8* restrict src;
   DATA8* restrict dst;
   DATA8* restrict span1;
   DATA8* restrict span2;

#if DIV_USING_BITSHIFT
   int pow2_shifts[6] = {0};
   int numerators[6] = {0};
   for (int run = 0; radii[run]; run++)
     {
        const int div = radii[run] * 2 + 1;
        pow2_shifts[run] = evas_filter_smallest_pow2_larger_than(div << 10);
        numerators[run] = (1 << pow2_shifts[run]) / (div);
     }
#endif

   span1 = alloca(len);
   span2 = alloca(len);

   // For each line, apply as many blurs as requested
   for (int l = 0; l < loops; l++)
     {
        int run;

        // Rotate input into work span
        const DATA8* srcptr = srcdata + l;
        DATA8* s = span1;
        for (int k = len; k; --k)
          {
             *s++ = *srcptr;
             srcptr += step;
          }

        src = span1;
        dst = span2;

        // Apply blur with current radius
        for (run = 0; radii[run]; run++)
          {
             const int radius = radii[run];
             const int left = MIN(radius, len);

#if DIV_USING_BITSHIFT
             const int pow2 = pow2_shifts[run];
             const int numerator = numerators[run];
#else
             const int divider = 2 * radius + 1;
#endif

             const DATA8* restrict sl = src;
             const DATA8* restrict sr = src;
             const DATA8* restrict sre = src + len;
             const DATA8* restrict sle = src + len - radius;
             DATA8* restrict d = dst;
             int acc = 0, count = 0;

             // Read-ahead & accumulate
             for (int x = left; x > 0; x--)
               {
                  acc += *sr++;
                  count++;
               }

             // Left edge
             for (int x = left; x > 0; x--)
               {
                  if (sr < sre)
                    {
                       acc += *sr++;
                       count++;
                    }

                  *d++ = acc / count;
               }

             // Middle part, normal blur
             while (sr < sre)
               {
                  acc += *sr++;
                  *d++ = DIVIDE(acc);
                  acc -= *sl++;
               }

             // Right edge
             count = 2 * radius + 1;
             while (sl < sle)
               {
                  *d++ = acc / (--count);
                  acc -= *sl++;
               }

             // More runs to go: swap spans
             if (radii[run + 1])
               {
                  DATA8* swap = src;
                  src = dst;
                  dst = swap;
               }
          }

        // Last run: rotate & copy back to destination
        DATA8* restrict dstptr = dstdata + l;
        for (int k = len; k; --k)
          {
             *dstptr = *dst++;
             dstptr += step;
          }
     }
}
