/*
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2005 Blender Foundation.
 * All rights reserved.
 */

#include "node_shader_util.hh"

namespace blender::nodes::node_shader_tex_environment_cc {

static void node_declare(NodeDeclarationBuilder &b)
{
  b.add_input<decl::Vector>(N_("Vector")).hide_value();
  b.add_output<decl::Color>(N_("Color")).no_muted_links();
}

static void node_shader_init_tex_environment(bNodeTree *UNUSED(ntree), bNode *node)
{
  NodeTexEnvironment *tex = MEM_cnew<NodeTexEnvironment>("NodeTexEnvironment");
  BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
  BKE_texture_colormapping_default(&tex->base.color_mapping);
  tex->projection = SHD_PROJ_EQUIRECTANGULAR;
  BKE_imageuser_default(&tex->iuser);

  node->storage = tex;
}

static int node_shader_gpu_tex_environment(GPUMaterial *mat,
                                           bNode *node,
                                           bNodeExecData *UNUSED(execdata),
                                           GPUNodeStack *in,
                                           GPUNodeStack *out)
{
  Image *ima = (Image *)node->id;
  NodeTexEnvironment *tex = (NodeTexEnvironment *)node->storage;

  /* We get the image user from the original node, since GPU image keeps
   * a pointer to it and the dependency refreshes the original. */
  bNode *node_original = node->original ? node->original : node;
  NodeTexImage *tex_original = (NodeTexImage *)node_original->storage;
  ImageUser *iuser = &tex_original->iuser;
  eGPUSamplerState sampler = GPU_SAMPLER_REPEAT | GPU_SAMPLER_ANISO | GPU_SAMPLER_FILTER;
  /* TODO(fclem): For now assume mipmap is always enabled. */
  if (true) {
    sampler |= GPU_SAMPLER_MIPMAP;
  }

  GPUNodeLink *outalpha;

  if (!ima) {
    return GPU_stack_link(mat, node, "node_tex_environment_empty", in, out);
  }

  if (!in[0].link) {
    GPU_link(mat, "node_tex_environment_texco", GPU_builtin(GPU_VIEW_POSITION), &in[0].link);
    node_shader_gpu_bump_tex_coord(mat, node, &in[0].link);
  }

  node_shader_gpu_tex_mapping(mat, node, in, out);

  /* Compute texture coordinate. */
  if (tex->projection == SHD_PROJ_EQUIRECTANGULAR) {
    GPU_link(mat, "node_tex_environment_equirectangular", in[0].link, &in[0].link);
    /* To fix pole issue we clamp the v coordinate. */
    sampler &= ~GPU_SAMPLER_REPEAT_T;
    /* Force the highest mipmap and don't do anisotropic filtering.
     * This is to fix the artifact caused by derivatives discontinuity. */
    sampler &= ~(GPU_SAMPLER_MIPMAP | GPU_SAMPLER_ANISO);
  }
  else {
    GPU_link(mat, "node_tex_environment_mirror_ball", in[0].link, &in[0].link);
    /* Fix pole issue. */
    sampler &= ~GPU_SAMPLER_REPEAT;
  }

  const char *gpu_fn;
  static const char *names[] = {
      "node_tex_image_linear",
      "node_tex_image_cubic",
  };

  switch (tex->interpolation) {
    case SHD_INTERP_LINEAR:
      gpu_fn = names[0];
      break;
    case SHD_INTERP_CLOSEST:
      sampler &= ~(GPU_SAMPLER_FILTER | GPU_SAMPLER_MIPMAP);
      gpu_fn = names[0];
      break;
    default:
      gpu_fn = names[1];
      break;
  }

  /* Sample texture with correct interpolation. */
  GPU_link(mat, gpu_fn, in[0].link, GPU_image(mat, ima, iuser, sampler), &out[0].link, &outalpha);

  if (out[0].hasoutput) {
    if (ELEM(ima->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED) ||
        IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name)) {
      /* Don't let alpha affect color output in these cases. */
      GPU_link(mat, "color_alpha_clear", out[0].link, &out[0].link);
    }
    else {
      /* Always output with premultiplied alpha. */
      if (ima->alpha_mode == IMA_ALPHA_PREMUL) {
        GPU_link(mat, "color_alpha_clear", out[0].link, &out[0].link);
      }
      else {
        GPU_link(mat, "color_alpha_premultiply", out[0].link, &out[0].link);
      }
    }
  }

  return true;
}

}  // namespace blender::nodes::node_shader_tex_environment_cc

/* node type definition */
void register_node_type_sh_tex_environment()
{
  namespace file_ns = blender::nodes::node_shader_tex_environment_cc;

  static bNodeType ntype;

  sh_node_type_base(&ntype, SH_NODE_TEX_ENVIRONMENT, "Environment Texture", NODE_CLASS_TEXTURE);
  ntype.declare = file_ns::node_declare;
  node_type_init(&ntype, file_ns::node_shader_init_tex_environment);
  node_type_storage(
      &ntype, "NodeTexEnvironment", node_free_standard_storage, node_copy_standard_storage);
  node_type_gpu(&ntype, file_ns::node_shader_gpu_tex_environment);
  ntype.labelfunc = node_image_label;
  node_type_size_preset(&ntype, NODE_SIZE_LARGE);

  nodeRegisterType(&ntype);
}
