/*
    This file is part of darktable,
    Copyright (C) 2010-2026 darktable developers.

    darktable 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.

    darktable 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.

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/

DT_OMP_DECLARE_SIMD(aligned(in, out:64))
static void demosaic_ppg(float *const out,
                         const float *const in,
                         const int width,
                         const int height,
                         const uint32_t filters,
                         const float thrs,
                         const int margin)
{
  // preliminary border interpolate for outermost 3 pixels
  float sum[8];
  for(int j = 0; j < height; j++)
    for(int i = 0; i < width; i++)
    {
      if(i == 3 && j >= 3 && j < height - 3) i = width - 3;
      if(i == width) break;
      memset(sum, 0, sizeof(float) * 8);
      for(int y = j - 1; y != j + 2; y++)
        for(int x = i - 1; x != i + 2; x++)
        {
          if((y >= 0) && (x >= 0) && (y < height) && (x < width))
          {
            const int f = FC(y, x, filters);
            sum[f] += in[(size_t)y * width + x];
            sum[f + 4]++;
          }
        }
      const int f = FC(j, i, filters);
      for(int c = 0; c < 3; c++)
      {
        if(c != f && sum[c + 4] > 0.0f)
          out[4 * ((size_t)j * width + i) + c] = fmaxf(0.0f, sum[c] / sum[c + 4]);
        else
          out[4 * ((size_t)j * width + i) + c]
              = fmaxf(0.0f, in[(size_t)j * width + i]);
      }
    }
  const gboolean median = thrs > 0.0f;
  const float *input = in;
  if(median)
  {
    float *med_in = dt_alloc_align_float((size_t)height * width);
    pre_median(med_in, in, width, height, filters, 1, thrs);
    input = med_in;
  }

  const int border = margin + 3;
  // for all pixels except those in the 3 pixel border:
  // interpolate green from input into out float array, or copy color.
  // avoid processing if outside of given border
  DT_OMP_FOR()
  for(int j = 3; j < height - 3; j++)
  {
    float *buf = out + (size_t)4 * width * j + 4 * 3;
    const float *buf_in = input + (size_t)width * j + 3;
    for(int i = 3; i < width - 3; i++)
    {
      if(i == border && j >= border && j < height - border)
      {
        i = width - border;
        buf = out + (size_t)4 * width * j + 4 * i;
        buf_in = in + (size_t)width * j + i;
      }
      if(i == width) break;

      const int c = FC(j, i, filters);
      dt_aligned_pixel_t color;
      const float pc = buf_in[0];
      // if(__builtin_expect(c == 0 || c == 2, 1))
      if(c == 0 || c == 2)
      {
        color[c] = pc;
        // get stuff (hopefully from cache)
        const float pym   = buf_in[-width * 1];
        const float pym2  = buf_in[-width * 2];
        const float pym3  = buf_in[-width * 3];
        const float pyM   = buf_in[width * 1];
        const float pyM2  = buf_in[width * 2];
        const float pyM3  = buf_in[width * 3];
        const float pxm   = buf_in[-1];
        const float pxm2  = buf_in[-2];
        const float pxm3  = buf_in[-3];
        const float pxM   = buf_in[+1];
        const float pxM2  = buf_in[+2];
        const float pxM3  = buf_in[+3];

        const float guessx = (pxm + pc + pxM) * 2.0f - pxM2 - pxm2;
        const float diffx = (fabsf(pxm2 - pc) + fabsf(pxM2 - pc) + fabsf(pxm - pxM)) * 3.0f
                          + (fabsf(pxM3 - pxM) + fabsf(pxm3 - pxm)) * 2.0f;
        const float guessy = (pym + pc + pyM) * 2.0f - pyM2 - pym2;
        const float diffy = (fabsf(pym2 - pc) + fabsf(pyM2 - pc) + fabsf(pym - pyM)) * 3.0f
                          + (fabsf(pyM3 - pyM) + fabsf(pym3 - pym)) * 2.0f;
        if(diffx > diffy)
        {
          // use guessy
          const float m = fminf(pym, pyM);
          const float M = fmaxf(pym, pyM);
          color[1] = fmaxf(fminf(guessy * .25f, M), m);
        }
        else
        {
          const float m = fminf(pxm, pxM);
          const float M = fmaxf(pxm, pxM);
          color[1] = fmaxf(fminf(guessx * .25f, M), m);
        }
      }
      else
        color[1] = pc;

      color[3] = 0.0f;

      for_each_channel(k,aligned(buf,color:16)
        dt_omp_nontemporal(buf)) buf[k] = fmaxf(0.0f, color[k]);

      buf += 4;
      buf_in++;
    }
  }

  DT_OMP_FOR()
  for(int j = 0; j < height; j++)
  {
    float *buf = out + (size_t)4 * width * j;
    for(int i = 0; i < width; i++)
    {
      if(i == margin && j >= margin && j < height - margin)
      {
        i = width - margin;
        buf = out + (size_t)4 * (width * j + i);
      }
      // also prefetch direct nbs top/bottom
      dt_aligned_pixel_t color = { buf[0], buf[1], buf[2], buf[3] };

      // for all pixels except the outermost row/column:
      // interpolate colors using out as input into float out array
      // the outermost are untouched
      // but both final results avoid negatives
      if(j > 0 && i > 0 && i < width - 1 && j < height - 1)
      {
        const int c = FC(j, i, filters);
        // fill all four pixels with correctly interpolated stuff: r/b for green1/2
        // b for r and r for b
        if(__builtin_expect(c & 1, 1)) // c == 1 || c == 3)
        {
          // calculate red and blue for green pixels:
          // need 4-nbhood:
          const float *nt = buf - 4 * width;
          const float *nb = buf + 4 * width;
          const float *nl = buf - 4;
          const float *nr = buf + 4;
          if(FC(j, i + 1, filters) == 0) // red nb in same row
          {
            color[2] = (nt[2] + nb[2] + 2.0f * color[1] - nt[1] - nb[1]) * .5f;
            color[0] = (nl[0] + nr[0] + 2.0f * color[1] - nl[1] - nr[1]) * .5f;
          }
          else
          {
            // blue nb
            color[0] = (nt[0] + nb[0] + 2.0f * color[1] - nt[1] - nb[1]) * .5f;
            color[2] = (nl[2] + nr[2] + 2.0f * color[1] - nl[1] - nr[1]) * .5f;
          }
        }
        else
        {
          // get 4-star-nbhood:
          const float *ntl = buf - 4 - 4 * width;
          const float *ntr = buf + 4 - 4 * width;
          const float *nbl = buf - 4 + 4 * width;
          const float *nbr = buf + 4 + 4 * width;
          if(c == 0)
          {
            // red pixel, fill blue:
            const float diff1 = fabsf(ntl[2] - nbr[2]) + fabsf(ntl[1] - color[1]) + fabsf(nbr[1] - color[1]);
            const float guess1 = ntl[2] + nbr[2] + 2.0f * color[1] - ntl[1] - nbr[1];
            const float diff2 = fabsf(ntr[2] - nbl[2]) + fabsf(ntr[1] - color[1]) + fabsf(nbl[1] - color[1]);
            const float guess2 = ntr[2] + nbl[2] + 2.0f * color[1] - ntr[1] - nbl[1];
            if(diff1 > diff2)
              color[2] = guess2 * .5f;
            else if(diff1 < diff2)
              color[2] = guess1 * .5f;
            else
              color[2] = (guess1 + guess2) * .25f;
          }
          else // c == 2, blue pixel, fill red:
          {
            const float diff1 = fabsf(ntl[0] - nbr[0]) + fabsf(ntl[1] - color[1]) + fabsf(nbr[1] - color[1]);
            const float guess1 = ntl[0] + nbr[0] + 2.0f * color[1] - ntl[1] - nbr[1];
            const float diff2 = fabsf(ntr[0] - nbl[0]) + fabsf(ntr[1] - color[1]) + fabsf(nbl[1] - color[1]);
            const float guess2 = ntr[0] + nbl[0] + 2.0f * color[1] - ntr[1] - nbl[1];
            if(diff1 > diff2)
              color[0] = guess2 * .5f;
            else if(diff1 < diff2)
              color[0] = guess1 * .5f;
            else
              color[0] = (guess1 + guess2) * .25f;
          }
        }
      }
      for_each_channel(k,aligned(buf,color:16)
        dt_omp_nontemporal(buf)) buf[k] = fmaxf(0.0f, color[k]);

      buf += 4;
    }
  }
  // _mm_sfence();
  if(median) dt_free_align((float *)input);
}

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on

