/*
 * 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.h"

/* **************** OUTPUT ******************** */

static bNodeSocketTemplate sh_node_tex_image_in[] = {
    {SOCK_VECTOR, 1, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE},
    {-1, 0, ""},
};

static bNodeSocketTemplate sh_node_tex_image_out[] = {
    {SOCK_RGBA,
     0,
     N_("Color"),
     0.0f,
     0.0f,
     0.0f,
     0.0f,
     0.0f,
     1.0f,
     PROP_NONE,
     SOCK_NO_INTERNAL_LINK},
    {SOCK_FLOAT,
     0,
     N_("Alpha"),
     0.0f,
     0.0f,
     0.0f,
     0.0f,
     0.0f,
     1.0f,
     PROP_NONE,
     SOCK_NO_INTERNAL_LINK},
    {-1, 0, ""},
};

static void node_shader_init_tex_image(bNodeTree *UNUSED(ntree), bNode *node)
{
  NodeTexImage *tex = MEM_callocN(sizeof(NodeTexImage), "NodeTexImage");
  BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
  BKE_texture_colormapping_default(&tex->base.color_mapping);
  BKE_imageuser_default(&tex->iuser);

  node->storage = tex;
}

static int node_shader_gpu_tex_image(GPUMaterial *mat,
                                     bNode *node,
                                     bNodeExecData *UNUSED(execdata),
                                     GPUNodeStack *in,
                                     GPUNodeStack *out)
{
  static const char *names[] = {
      "node_tex_image_linear",
      "node_tex_image_nearest",
      "node_tex_image_cubic",
      "node_tex_image_smart",
  };
  static const char *names_tiled[] = {
      "node_tex_tile_linear",
      "node_tex_tile_nearest",
      "node_tex_tile_cubic",
      "node_tex_tile_smart",
  };
  static const char *names_box[] = {
      "tex_box_sample_linear",
      "tex_box_sample_nearest",
      "tex_box_sample_cubic",
      "tex_box_sample_smart",
  };
  static const char *names_clip[] = {
      "tex_clip_linear",
      "tex_clip_nearest",
      "tex_clip_cubic",
      "tex_clip_smart",
  };

  Image *ima = (Image *)node->id;
  NodeTexImage *tex = 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 = node_original->storage;
  ImageUser *iuser = &tex_original->iuser;

  const char *gpu_node_name = (tex->projection == SHD_PROJ_BOX) ? names_box[tex->interpolation] :
                                                                  names[tex->interpolation];
  bool do_texco_extend = (tex->extension != SHD_IMAGE_EXTENSION_REPEAT);
  const bool do_texco_clip = (tex->extension == SHD_IMAGE_EXTENSION_CLIP);

  if (do_texco_extend && (tex->projection != SHD_PROJ_BOX) &&
      ELEM(tex->interpolation, SHD_INTERP_CUBIC, SHD_INTERP_SMART)) {
    gpu_node_name = "node_tex_image_cubic_extend";
    /* We do it inside the sampling function */
    do_texco_extend = false;
  }

  GPUNodeLink *norm, *col1, *col2, *col3, *input_coords, *gpu_image;
  GPUNodeLink *vnor, *ob_mat, *blend;
  GPUNodeLink **texco = &in[0].link;

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

  if (!*texco) {
    *texco = GPU_attribute(CD_MTFACE, "");
    node_shader_gpu_bump_tex_coord(mat, node, texco);
  }

  node_shader_gpu_tex_mapping(mat, node, in, out);

  if (ima->source == IMA_SRC_TILED) {
    /* The tiled shader needs both the tile array itself as well as the mapping from tile to array
     * position. Which of these to allocate is automatically decided based on the shader argument
     * type, so here the first GPU_image(ima, iuser) will resolve to the array and the second to
     * the mapping since the third argument in the shader has type sampler2DArray while
     * the fourth is sampler1DArray.
     */
    GPU_stack_link(mat,
                   node,
                   names_tiled[tex->interpolation],
                   in,
                   out,
                   GPU_image(ima, iuser),
                   GPU_image(ima, iuser));
  }
  else {
    switch (tex->projection) {
      case SHD_PROJ_FLAT:
        if (do_texco_clip) {
          /* This seems redundant, but is required to ensure the texco link
           * is not freed by GPU_link, as it is still needed for GPU_stack_link.
           * Intermediate links like this can only be used once and are then
           * freed immediately, but if we make it the output link of a set_rgb
           * node it will be kept and can be used multiple times. */
          GPU_link(mat, "set_rgb", *texco, texco);
          GPU_link(mat, "set_rgb", *texco, &input_coords);
        }
        if (do_texco_extend) {
          GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
        }
        GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
        break;

      case SHD_PROJ_BOX:
        vnor = GPU_builtin(GPU_WORLD_NORMAL);
        ob_mat = GPU_builtin(GPU_OBJECT_MATRIX);
        blend = GPU_uniform(&tex->projection_blend);
        gpu_image = GPU_image(ima, iuser);

        /* equivalent to normal_world_to_object */
        GPU_link(mat, "normal_transform_transposed_m4v3", vnor, ob_mat, &norm);
        GPU_link(mat, gpu_node_name, *texco, norm, GPU_image(ima, iuser), &col1, &col2, &col3);
        GPU_stack_link(
            mat, node, "node_tex_image_box", in, out, norm, col1, col2, col3, gpu_image, blend);
        break;

      case SHD_PROJ_SPHERE:
        GPU_link(mat, "point_texco_remap_square", *texco, texco);
        GPU_link(mat, "point_map_to_sphere", *texco, texco);
        if (do_texco_clip) {
          /* See SHD_PROJ_FLAT for explanation. */
          GPU_link(mat, "set_rgb", *texco, texco);
          GPU_link(mat, "set_rgb", *texco, &input_coords);
        }
        if (do_texco_extend) {
          GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
        }
        GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
        break;

      case SHD_PROJ_TUBE:
        GPU_link(mat, "point_texco_remap_square", *texco, texco);
        GPU_link(mat, "point_map_to_tube", *texco, texco);
        if (do_texco_clip) {
          /* See SHD_PROJ_FLAT for explanation. */
          GPU_link(mat, "set_rgb", *texco, texco);
          GPU_link(mat, "set_rgb", *texco, &input_coords);
        }
        if (do_texco_extend) {
          GPU_link(mat, "point_texco_clamp", *texco, GPU_image(ima, iuser), texco);
        }
        GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser));
        break;
    }

    if (tex->projection != SHD_PROJ_BOX) {
      if (do_texco_clip) {
        gpu_node_name = names_clip[tex->interpolation];
        in[0].link = input_coords;
        GPU_stack_link(mat, node, gpu_node_name, in, out, GPU_image(ima, iuser), out[0].link);
      }
    }
  }

  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 {
      /* Output premultiplied alpha depending on alpha socket usage. This makes
       * it so that if we blend the color with a transparent shader using alpha as
       * a factor, we don't multiply alpha into the color twice. And if we do
       * not, then there will be no artifacts from zero alpha areas. */
      if (ima->alpha_mode == IMA_ALPHA_PREMUL) {
        if (out[1].hasoutput) {
          GPU_link(mat, "color_alpha_unpremultiply", out[0].link, &out[0].link);
        }
        else {
          GPU_link(mat, "color_alpha_clear", out[0].link, &out[0].link);
        }
      }
      else {
        if (out[1].hasoutput) {
          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;
}

/* node type definition */
void register_node_type_sh_tex_image(void)
{
  static bNodeType ntype;

  sh_node_type_base(&ntype, SH_NODE_TEX_IMAGE, "Image Texture", NODE_CLASS_TEXTURE, 0);
  node_type_socket_templates(&ntype, sh_node_tex_image_in, sh_node_tex_image_out);
  node_type_init(&ntype, node_shader_init_tex_image);
  node_type_storage(
      &ntype, "NodeTexImage", node_free_standard_storage, node_copy_standard_storage);
  node_type_gpu(&ntype, node_shader_gpu_tex_image);
  node_type_label(&ntype, node_image_label);
  node_type_size_preset(&ntype, NODE_SIZE_LARGE);

  nodeRegisterType(&ntype);
}
