/*
 * 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.
 *
 * Copyright 2016, Blender Foundation.
 */

/** \file
 * \ingroup draw_engine
 */

#include "DNA_mesh_types.h"
#include "DNA_view3d_types.h"

#include "BKE_editmesh.h"
#include "BKE_global.h"
#include "BKE_object.h"
#include "BKE_paint.h"

#include "BLI_hash.h"

#include "GPU_shader.h"
#include "DRW_render.h"

#include "ED_view3d.h"

#include "draw_mode_engines.h"

#ifdef __APPLE__
#  define USE_GEOM_SHADER_WORKAROUND 1
#else
#  define USE_GEOM_SHADER_WORKAROUND 0
#endif

/* Structures */

typedef struct OVERLAY_DupliData {
  DRWShadingGroup *shgrp;
  struct GPUBatch *geom;
  short base_flag;
} OVERLAY_DupliData;

typedef struct OVERLAY_StorageList {
  struct OVERLAY_PrivateData *g_data;
} OVERLAY_StorageList;

typedef struct OVERLAY_PassList {
  struct DRWPass *face_orientation_pass;
  struct DRWPass *face_wireframe_pass;
  struct DRWPass *face_wireframe_only_pass;
  struct DRWPass *face_wireframe_xray_pass;
} OVERLAY_PassList;

typedef struct OVERLAY_Data {
  void *engine_type;
  DRWViewportEmptyList *fbl;
  DRWViewportEmptyList *txl;
  OVERLAY_PassList *psl;
  OVERLAY_StorageList *stl;
} OVERLAY_Data;

typedef struct OVERLAY_PrivateData {
  DRWShadingGroup *face_orientation_shgrp;
  DRWShadingGroup *face_wires_shgrp;
  DRWShadingGroup *face_wires_xray_shgrp;
  DRWView *view_wires;
  View3DOverlay overlay;
  float wire_step_param;
  bool clear_stencil;
  bool show_overlays;
} OVERLAY_PrivateData; /* Transient data */

typedef struct OVERLAY_Shaders {
  /* Face orientation shader */
  struct GPUShader *face_orientation;
  /* Wireframe shader */
  struct GPUShader *select_wireframe;
  struct GPUShader *face_wireframe;
} OVERLAY_Shaders;

/* *********** STATIC *********** */
static struct {
  OVERLAY_Shaders sh_data[GPU_SHADER_CFG_LEN];
} e_data = {{{NULL}}};

/* Shaders */
extern char datatoc_overlay_face_orientation_frag_glsl[];
extern char datatoc_overlay_face_orientation_vert_glsl[];

extern char datatoc_overlay_face_wireframe_vert_glsl[];
extern char datatoc_overlay_face_wireframe_geom_glsl[];
extern char datatoc_overlay_face_wireframe_frag_glsl[];
extern char datatoc_gpu_shader_depth_only_frag_glsl[];

extern char datatoc_common_view_lib_glsl[];

/* Functions */
static void overlay_engine_init(void *vedata)
{
  OVERLAY_Data *data = vedata;
  OVERLAY_StorageList *stl = data->stl;

  const DRWContextState *draw_ctx = DRW_context_state_get();
  OVERLAY_Shaders *sh_data = &e_data.sh_data[draw_ctx->sh_cfg];

  if (!stl->g_data) {
    /* Alloc transient pointers */
    stl->g_data = MEM_callocN(sizeof(*stl->g_data), __func__);
  }

  stl->g_data->clear_stencil = (draw_ctx->v3d->shading.type > OB_SOLID);

  const GPUShaderConfigData *sh_cfg_data = &GPU_shader_cfg_data[draw_ctx->sh_cfg];

  if (!sh_data->face_orientation) {
    /* Face orientation */
    sh_data->face_orientation = GPU_shader_create_from_arrays({
        .vert = (const char *[]){sh_cfg_data->lib,
                                 datatoc_common_view_lib_glsl,
                                 datatoc_overlay_face_orientation_vert_glsl,
                                 NULL},
        .frag = (const char *[]){datatoc_overlay_face_orientation_frag_glsl, NULL},
        .defs = (const char *[]){sh_cfg_data->def, NULL},
    });
  }

  if (!sh_data->face_wireframe) {
    sh_data->select_wireframe = GPU_shader_create_from_arrays({
        .vert = (const char *[]){sh_cfg_data->lib,
                                 datatoc_common_view_lib_glsl,
                                 datatoc_overlay_face_wireframe_vert_glsl,
                                 NULL},
        .geom = (const char *[]){sh_cfg_data->lib, datatoc_overlay_face_wireframe_geom_glsl, NULL},
        .frag = (const char *[]){datatoc_gpu_shader_depth_only_frag_glsl, NULL},
        .defs = (const char *[]){sh_cfg_data->def, "#define SELECT_EDGES\n", NULL},
    });
#if USE_GEOM_SHADER_WORKAROUND
    /* Apple drivers does not support wide wires. Use geometry shader as a workaround. */
    sh_data->face_wireframe = GPU_shader_create_from_arrays({
        .vert = (const char *[]){sh_cfg_data->lib,
                                 datatoc_common_view_lib_glsl,
                                 datatoc_overlay_face_wireframe_vert_glsl,
                                 NULL},
        .geom = (const char *[]){sh_cfg_data->lib, datatoc_overlay_face_wireframe_geom_glsl, NULL},
        .frag = (const char *[]){datatoc_overlay_face_wireframe_frag_glsl, NULL},
        .defs = (const char *[]){sh_cfg_data->def, "#define USE_GEOM\n", NULL},
    });
#else
    sh_data->face_wireframe = GPU_shader_create_from_arrays({
        .vert = (const char *[]){sh_cfg_data->lib,
                                 datatoc_common_view_lib_glsl,
                                 datatoc_overlay_face_wireframe_vert_glsl,
                                 NULL},
        .frag = (const char *[]){datatoc_overlay_face_wireframe_frag_glsl, NULL},
        .defs = (const char *[]){sh_cfg_data->def, NULL},
    });
#endif
  }

  stl->g_data->view_wires = DRW_view_create_with_zoffset(draw_ctx->rv3d, 0.5f);
}

static void geometry_shader_uniforms(DRWShadingGroup *shgrp)
{
  DRW_shgroup_uniform_float_copy(shgrp, "wireSize", U.pixelsize * 0.5f);
  DRW_shgroup_uniform_vec2(shgrp, "viewportSize", DRW_viewport_size_get(), 1);
  DRW_shgroup_uniform_vec2(shgrp, "viewportSizeInv", DRW_viewport_invert_size_get(), 1);
}

static void overlay_cache_init(void *vedata)
{
  OVERLAY_Data *data = vedata;
  OVERLAY_PassList *psl = data->psl;
  OVERLAY_StorageList *stl = data->stl;
  OVERLAY_PrivateData *g_data = stl->g_data;

  const DRWContextState *draw_ctx = DRW_context_state_get();
  RegionView3D *rv3d = draw_ctx->rv3d;
  View3D *v3d = draw_ctx->v3d;
  OVERLAY_Shaders *sh_data = &e_data.sh_data[draw_ctx->sh_cfg];

  if (v3d) {
    g_data->overlay = v3d->overlay;
    g_data->show_overlays = (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0;
  }
  else {
    memset(&g_data->overlay, 0, sizeof(g_data->overlay));
    g_data->show_overlays = false;
  }

  if (g_data->show_overlays == false) {
    g_data->overlay.flag = 0;
  }

  if (v3d->shading.type == OB_WIRE) {
    g_data->overlay.flag |= V3D_OVERLAY_WIREFRAMES;
  }

  {
    /* Face Orientation Pass */
    DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_BLEND_ALPHA;
    psl->face_orientation_pass = DRW_pass_create("Face Orientation", state);
    g_data->face_orientation_shgrp = DRW_shgroup_create(sh_data->face_orientation,
                                                        psl->face_orientation_pass);
    if (rv3d->rflag & RV3D_CLIPPING) {
      DRW_shgroup_state_enable(g_data->face_orientation_shgrp, DRW_STATE_CLIP_PLANES);
    }
  }

  {
    /* Wireframe */
    const bool use_select = (DRW_state_is_select() || DRW_state_is_depth());
    GPUShader *face_wires_sh = use_select ? sh_data->select_wireframe : sh_data->face_wireframe;

    DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL |
                     DRW_STATE_STENCIL_EQUAL | DRW_STATE_FIRST_VERTEX_CONVENTION;
    psl->face_wireframe_pass = DRW_pass_create("Wires", state);
    g_data->face_wires_shgrp = DRW_shgroup_create(face_wires_sh, psl->face_wireframe_pass);

    DRWState state_xray = DRW_STATE_WRITE_DEPTH | DRW_STATE_WRITE_STENCIL |
                          DRW_STATE_DEPTH_GREATER_EQUAL | DRW_STATE_STENCIL_NEQUAL |
                          DRW_STATE_FIRST_VERTEX_CONVENTION;
    psl->face_wireframe_xray_pass = DRW_pass_create("Wires Only Xray", state_xray);
    g_data->face_wires_xray_shgrp = DRW_shgroup_create(face_wires_sh,
                                                       psl->face_wireframe_xray_pass);

    if (use_select || USE_GEOM_SHADER_WORKAROUND) {
      geometry_shader_uniforms(g_data->face_wires_shgrp);
      geometry_shader_uniforms(g_data->face_wires_xray_shgrp);
    }
    g_data->wire_step_param = stl->g_data->overlay.wireframe_threshold - 254.0f / 255.0f;
  }
}

static void overlay_wire_color_get(const View3D *v3d,
                                   const Object *ob,
                                   const bool use_coloring,
                                   float **rim_col,
                                   float **wire_col)
{
#ifndef NDEBUG
  *rim_col = NULL;
  *wire_col = NULL;
#endif
  const DRWContextState *draw_ctx = DRW_context_state_get();

  if (UNLIKELY(ob->base_flag & BASE_FROM_SET)) {
    *rim_col = G_draw.block.colorDupli;
    *wire_col = G_draw.block.colorDupli;
  }
  else if (UNLIKELY(ob->base_flag & BASE_FROM_DUPLI)) {
    if (ob->base_flag & BASE_SELECTED) {
      if (G.moving & G_TRANSFORM_OBJ) {
        *rim_col = G_draw.block.colorTransform;
      }
      else {
        *rim_col = G_draw.block.colorDupliSelect;
      }
    }
    else {
      *rim_col = G_draw.block.colorDupli;
    }
    *wire_col = G_draw.block.colorDupli;
  }
  else if ((ob->base_flag & BASE_SELECTED) && use_coloring) {
    if (G.moving & G_TRANSFORM_OBJ) {
      *rim_col = G_draw.block.colorTransform;
    }
    else if (ob == draw_ctx->obact) {
      *rim_col = G_draw.block.colorActive;
    }
    else {
      *rim_col = G_draw.block.colorSelect;
    }
    *wire_col = G_draw.block.colorWire;
  }
  else {
    *rim_col = G_draw.block.colorWire;
    *wire_col = G_draw.block.colorBackground;
  }

  if (v3d->shading.type == OB_WIRE) {
    if (ELEM(v3d->shading.wire_color_type, V3D_SHADING_OBJECT_COLOR, V3D_SHADING_RANDOM_COLOR)) {
      /* Theses stays valid until next call. So we need to copy them when using them as uniform. */
      static float wire_col_val[3], rim_col_val[3];
      *wire_col = wire_col_val;
      *rim_col = rim_col_val;

      if (v3d->shading.wire_color_type == V3D_SHADING_OBJECT_COLOR) {
        linearrgb_to_srgb_v3_v3(*wire_col, ob->color);
        mul_v3_fl(*wire_col, 0.5f);
        copy_v3_v3(*rim_col, *wire_col);
      }
      else {
        uint hash = BLI_ghashutil_strhash_p_murmur(ob->id.name);
        if (ob->id.lib) {
          hash = (hash * 13) ^ BLI_ghashutil_strhash_p_murmur(ob->id.lib->name);
        }

        float hue = BLI_hash_int_01(hash);
        float hsv[3] = {hue, 0.75f, 0.8f};
        hsv_to_rgb_v(hsv, *wire_col);
        copy_v3_v3(*rim_col, *wire_col);
      }

      if ((ob->base_flag & BASE_SELECTED) && use_coloring) {
        /* "Normalize" color. */
        add_v3_fl(*wire_col, 1e-4f);
        float brightness = max_fff((*wire_col)[0], (*wire_col)[1], (*wire_col)[2]);
        mul_v3_fl(*wire_col, (0.5f / brightness));
        add_v3_fl(*rim_col, 0.75f);
      }
      else {
        mul_v3_fl(*rim_col, 0.5f);
        add_v3_fl(*wire_col, 0.5f);
      }
    }
  }
  BLI_assert(*rim_col && *wire_col);
}

static void overlay_cache_populate(void *vedata, Object *ob)
{
  OVERLAY_Data *data = vedata;
  OVERLAY_StorageList *stl = data->stl;
  OVERLAY_PrivateData *pd = stl->g_data;
  const DRWContextState *draw_ctx = DRW_context_state_get();
  View3D *v3d = draw_ctx->v3d;

  if ((ob->dt < OB_WIRE) || (!DRW_object_is_renderable(ob) && (ob->dt != OB_WIRE))) {
    return;
  }

  if (DRW_object_is_renderable(ob) && pd->overlay.flag & V3D_OVERLAY_FACE_ORIENTATION) {
    struct GPUBatch *geom = DRW_cache_object_surface_get(ob);
    if (geom) {
      DRW_shgroup_call(pd->face_orientation_shgrp, geom, ob);
    }
  }

  if ((pd->overlay.flag & V3D_OVERLAY_WIREFRAMES) || (v3d->shading.type == OB_WIRE) ||
      (ob->dtx & OB_DRAWWIRE) || (ob->dt == OB_WIRE)) {

    /* Fast path for duplis. */
    OVERLAY_DupliData **dupli_data = (OVERLAY_DupliData **)DRW_duplidata_get(vedata);
    if (dupli_data) {
      if (*dupli_data == NULL) {
        *dupli_data = MEM_callocN(sizeof(OVERLAY_DupliData), "OVERLAY_DupliData");
      }
      else {
        if ((*dupli_data)->shgrp && (*dupli_data)->geom) {
          if ((*dupli_data)->base_flag == ob->base_flag) {
            DRW_shgroup_call((*dupli_data)->shgrp, (*dupli_data)->geom, ob);
          }
          else {
            /* Continue and create a new Shgroup. */
          }
        }
        else {
          return;
        }
      }
    }

    const bool is_edit_mode = BKE_object_is_in_editmode(ob);
    bool has_edit_mesh_cage = false;
    if (ob->type == OB_MESH) {
      /* TODO: Should be its own function. */
      Mesh *me = (Mesh *)ob->data;
      BMEditMesh *embm = me->edit_mesh;
      if (embm) {
        has_edit_mesh_cage = embm->mesh_eval_cage &&
                             (embm->mesh_eval_cage != embm->mesh_eval_final);
      }
    }

    /* Don't do that in edit Mesh mode, unless there is a modifier preview. */
    if ((!pd->show_overlays) ||
        (((ob != draw_ctx->object_edit) && !is_edit_mode) || has_edit_mesh_cage) ||
        ob->type != OB_MESH) {
      const bool use_sculpt_pbvh = BKE_sculptsession_use_pbvh_draw(ob, draw_ctx->v3d) &&
                                   !DRW_state_is_image_render();
      const bool all_wires = (ob->dtx & OB_DRAW_ALL_EDGES);
      const bool is_wire = (ob->dt < OB_SOLID);
      const bool is_xray = (ob->dtx & OB_DRAWXRAY);
      const bool use_coloring = (pd->show_overlays && !is_edit_mode && !use_sculpt_pbvh &&
                                 !has_edit_mesh_cage);
      DRWShadingGroup *shgrp = NULL;

      struct GPUBatch *geom;
      geom = DRW_cache_object_face_wireframe_get(ob);

      if (geom || use_sculpt_pbvh) {
        if (is_wire && is_xray) {
          shgrp = DRW_shgroup_create_sub(pd->face_wires_xray_shgrp);
        }
        else {
          shgrp = DRW_shgroup_create_sub(pd->face_wires_shgrp);
        }

        if (draw_ctx->rv3d->rflag & RV3D_CLIPPING) {
          DRW_shgroup_state_enable(shgrp, DRW_STATE_CLIP_PLANES);
        }

        float wire_step_param = 10.0f;
        if (!use_sculpt_pbvh) {
          wire_step_param = (all_wires) ? 1.0f : pd->wire_step_param;
        }
        DRW_shgroup_uniform_float_copy(shgrp, "wireStepParam", wire_step_param);

        if (!(DRW_state_is_select() || DRW_state_is_depth())) {
          float *rim_col, *wire_col;
          overlay_wire_color_get(v3d, ob, use_coloring, &rim_col, &wire_col);
          DRW_shgroup_uniform_vec3_copy(shgrp, "wireColor", wire_col);
          DRW_shgroup_uniform_vec3_copy(shgrp, "rimColor", rim_col);
          DRW_shgroup_stencil_mask(shgrp,
                                   (is_xray && (is_wire || !pd->clear_stencil)) ? 0x00 : 0xFF);
        }

        if (use_sculpt_pbvh) {
          DRW_shgroup_call_sculpt(shgrp, ob, true, false, false);
        }
        else {
          DRW_shgroup_call(shgrp, geom, ob);
        }
      }

      if (dupli_data) {
        (*dupli_data)->shgrp = shgrp;
        (*dupli_data)->geom = geom;
        (*dupli_data)->base_flag = ob->base_flag;
      }
    }
  }
}

static void overlay_draw_scene(void *vedata)
{
  OVERLAY_Data *data = vedata;
  OVERLAY_PassList *psl = data->psl;
  OVERLAY_StorageList *stl = data->stl;
  DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
  DefaultTextureList *dtxl = DRW_viewport_texture_list_get();

  if (DRW_state_is_fbo()) {
    GPU_framebuffer_bind(dfbl->default_fb);

    /* In material/rendered mode, Stencil has not be rendered correctly.
     * Clear it to avoid problems.*/
    if (stl->g_data->clear_stencil) {
      GPU_framebuffer_clear_stencil(dfbl->default_fb, 0xFF);
    }
  }
  DRW_draw_pass(psl->face_orientation_pass);

  /* Do a depth prepass to lower the depth where the xray wire objects can pass through */
  DRW_draw_pass(psl->face_wireframe_xray_pass);

  /* This is replaced by the next code block  */
  // MULTISAMPLE_SYNC_ENABLE(dfbl, dtxl);

  if (dfbl->multisample_fb != NULL && DRW_state_is_fbo()) {
    DRW_stats_query_start("Multisample Blit");
    GPU_framebuffer_bind(dfbl->multisample_fb);
    GPU_framebuffer_clear_color(dfbl->multisample_fb, (const float[4]){0.0f});
    /* Special blit: we need the original depth and stencil
     * in the Multisample buffer. */
    GPU_framebuffer_blit(
        dfbl->default_fb, 0, dfbl->multisample_fb, 0, GPU_DEPTH_BIT | GPU_STENCIL_BIT);
    DRW_stats_query_end();

    /* Redo the prepass so we ge nice AA lines. */
    DRW_draw_pass(psl->face_wireframe_xray_pass);
  }

  DRW_view_set_active(stl->g_data->view_wires);

  DRW_pass_state_remove(psl->face_wireframe_xray_pass,
                        DRW_STATE_DEPTH_GREATER_EQUAL | DRW_STATE_STENCIL_NEQUAL);
  DRW_pass_state_add(psl->face_wireframe_xray_pass,
                     DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS_EQUAL |
                         DRW_STATE_STENCIL_ALWAYS);
  DRW_draw_pass(psl->face_wireframe_xray_pass);

  DRW_draw_pass(psl->face_wireframe_pass);

  DRW_view_set_active(NULL);

  /* TODO(fclem): find a way to unify the multisample pass together
   * (non meshes + armature + wireframe) */
  MULTISAMPLE_SYNC_DISABLE(dfbl, dtxl);
}

static void overlay_engine_free(void)
{
  for (int sh_data_index = 0; sh_data_index < ARRAY_SIZE(e_data.sh_data); sh_data_index++) {
    OVERLAY_Shaders *sh_data = &e_data.sh_data[sh_data_index];
    GPUShader **sh_data_as_array = (GPUShader **)sh_data;
    for (int i = 0; i < (sizeof(OVERLAY_Shaders) / sizeof(GPUShader *)); i++) {
      DRW_SHADER_FREE_SAFE(sh_data_as_array[i]);
    }
  }
}

static const DrawEngineDataSize overlay_data_size = DRW_VIEWPORT_DATA_SIZE(OVERLAY_Data);

DrawEngineType draw_engine_overlay_type = {
    NULL,
    NULL,
    N_("OverlayEngine"),
    &overlay_data_size,
    &overlay_engine_init,
    &overlay_engine_free,
    &overlay_cache_init,
    &overlay_cache_populate,
    NULL,
    NULL,
    &overlay_draw_scene,
    NULL,
    NULL,
    NULL,
};
