/**
 * Looking Glass
 * Copyright © 2017-2026 The Looking Glass Authors
 * https://looking-glass.io
 *
 * This program 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 2 of the License, or (at your option)
 * any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "effect.h"

#include "filter.h"
#include "framebuffer.h"
#include "state.h"
#include "texture.h"

#include "common/debug.h"

#include <stdlib.h>

struct EGL_EffectPass
{
  EGL_Shader * shader;
  EGL_Framebuffer * framebuffer;
  GLuint sampler;

  EGL_Uniform * uTransform;
  EGL_Uniform * uDesktopSize;

  GLenum minFilter;
  GLenum magFilter;
  GLenum wrapS;
  GLenum wrapT;

  bool enabled;
  bool configured;
  enum EGL_PixelFormat pixFmt;
  unsigned int width;
  unsigned int height;

  struct EGL_EffectPass * next;
};

struct EGL_Effect
{
  EGL_EffectPass * passes;
  EGL_EffectPass * passesTail;
};

bool egl_effectInit(EGL_Effect ** effect)
{
  *effect = calloc(1, sizeof(**effect));
  if (!*effect)
  {
    DEBUG_ERROR("Failed to allocate EGL effect");
    return false;
  }

  return true;
}

void egl_effectFree(EGL_Effect ** effect)
{
  if (!*effect)
    return;

  bool deletedSampler = false;
  EGL_EffectPass * pass = (*effect)->passes;
  while(pass)
  {
    EGL_EffectPass * next = pass->next;
    glDeleteSamplers(1, &pass->sampler);
    deletedSampler = true;
    egl_framebufferFree(&pass->framebuffer);
    free(pass);
    pass = next;
  }

  if (deletedSampler)
    egl_stateInvalidateShared();

  free(*effect);
  *effect = NULL;
}

void egl_effectPassSetShader(EGL_EffectPass * pass, EGL_Shader * shader)
{
  if (pass->shader == shader)
    return;

  pass->shader = shader;
  if (!shader)
  {
    pass->uTransform = NULL;
    pass->uDesktopSize = NULL;
    return;
  }

  pass->uTransform   = egl_shaderGetUniform(shader, "transform");
  pass->uDesktopSize = egl_shaderGetUniform(shader, "desktopSize");
  egl_shaderAssocTextures(shader, 1);
}

bool egl_effectAddPass(EGL_Effect * effect, EGL_Shader * shader,
    EGL_EffectPass ** result)
{
  EGL_EffectPass * pass = calloc(1, sizeof(*pass));
  if (!pass)
  {
    DEBUG_ERROR("Failed to allocate EGL effect pass");
    return false;
  }

  if (!egl_framebufferInit(&pass->framebuffer))
  {
    free(pass);
    return false;
  }

  glGenSamplers(1, &pass->sampler);
  glSamplerParameteri(pass->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glSamplerParameteri(pass->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glSamplerParameteri(pass->sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glSamplerParameteri(pass->sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  pass->minFilter = GL_LINEAR;
  pass->magFilter = GL_LINEAR;
  pass->wrapS = GL_CLAMP_TO_EDGE;
  pass->wrapT = GL_CLAMP_TO_EDGE;

  pass->enabled = true;
  egl_effectPassSetShader(pass, shader);

  if (effect->passesTail)
    effect->passesTail->next = pass;
  else
    effect->passes = pass;
  effect->passesTail = pass;

  if (result)
    *result = pass;
  return true;
}

void egl_effectPassSetEnabled(EGL_EffectPass * pass, bool enabled)
{
  pass->enabled = enabled;
}

void egl_effectPassSetFilter(EGL_EffectPass * pass, GLenum minFilter,
    GLenum magFilter)
{
  if (pass->minFilter != minFilter)
  {
    glSamplerParameteri(pass->sampler, GL_TEXTURE_MIN_FILTER, minFilter);
    pass->minFilter = minFilter;
  }

  if (pass->magFilter != magFilter)
  {
    glSamplerParameteri(pass->sampler, GL_TEXTURE_MAG_FILTER, magFilter);
    pass->magFilter = magFilter;
  }
}

void egl_effectPassSetWrap(EGL_EffectPass * pass, GLenum wrapS, GLenum wrapT)
{
  if (pass->wrapS != wrapS)
  {
    glSamplerParameteri(pass->sampler, GL_TEXTURE_WRAP_S, wrapS);
    pass->wrapS = wrapS;
  }

  if (pass->wrapT != wrapT)
  {
    glSamplerParameteri(pass->sampler, GL_TEXTURE_WRAP_T, wrapT);
    pass->wrapT = wrapT;
  }
}

bool egl_effectPassSetup(EGL_EffectPass * pass, enum EGL_PixelFormat pixFmt,
    unsigned int width, unsigned int height)
{
  if (pass->configured && pass->pixFmt == pixFmt &&
      pass->width == width && pass->height == height)
    return true;

  if (!egl_framebufferSetup(pass->framebuffer, pixFmt, width, height))
    return false;

  pass->pixFmt = pixFmt;
  pass->width = width;
  pass->height = height;
  pass->configured = true;
  return true;
}

EGL_Texture * egl_effectPassGetTexture(EGL_EffectPass * pass)
{
  return pass->configured ? egl_framebufferGetTexture(pass->framebuffer) : NULL;
}

EGL_Texture * egl_effectPassRun(EGL_EffectPass * pass, EGL_FilterRects * rects,
    EGL_Texture * const * textures, unsigned int textureCount)
{
  if (!pass->configured || !pass->shader || !textureCount)
  {
    DEBUG_ERROR("Attempted to run an unconfigured EGL effect pass");
    return NULL;
  }

  egl_framebufferBind(pass->framebuffer);

  for (unsigned int i = 0; i < textureCount; ++i)
    if (egl_textureBindUnitWithSampler(textures[i], i, pass->sampler) !=
        EGL_TEX_STATUS_OK)
      return NULL;

  egl_uniformMatrix3x2fv(pass->uTransform, 1, GL_FALSE, rects->matrix);
  egl_uniform2f(pass->uDesktopSize, rects->width, rects->height);
  egl_shaderUse(pass->shader);
  egl_desktopRectsRender(rects->rects);

  return egl_framebufferGetTexture(pass->framebuffer);
}

EGL_Texture * egl_effectRun(EGL_Effect * effect, EGL_FilterRects * rects,
    EGL_Texture * texture)
{
  for(EGL_EffectPass * pass = effect->passes; pass; pass = pass->next)
  {
    if (!pass->enabled)
      continue;

    texture = egl_effectPassRun(pass, rects, &texture, 1);
    if (!texture)
      return NULL;
  }

  return texture;
}
