/* SPDX-License-Identifier: GPL-2.0-or-later
 * Copyright 2001-2002 NaN Holding BV. All rights reserved. */

/** \file
 * \ingroup spview3d
 */

#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"

#include "BLI_math_vector.h"

#include "BKE_DerivedMesh.h"
#include "BKE_customdata.h"
#include "BKE_editmesh.h"
#include "BKE_global.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_object.h"

#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"

#include "GPU_batch.h"
#include "GPU_immediate.h"
#include "GPU_shader.h"
#include "GPU_state.h"

#include "ED_mesh.h"

#include "UI_resources.h"

#include "DRW_engine.h"

#include "view3d_intern.h" /* bad level include */

#ifdef VIEW3D_CAMERA_BORDER_HACK
uchar view3d_camera_border_hack_col[3];
bool view3d_camera_border_hack_test = false;
#endif

/* ***************** BACKBUF SEL (BBS) ********* */

void ED_draw_object_facemap(Depsgraph *depsgraph,
                            Object *ob,
                            const float col[4],
                            const int facemap)
{
  /* happens on undo */
  if (ob->type != OB_MESH || !ob->data) {
    return;
  }

  const Mesh *me = ob->data;
  {
    Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
    const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval);
    if (me_eval != NULL) {
      me = me_eval;
    }
  }

  GPU_front_facing(ob->transflag & OB_NEG_SCALE);

  /* Just to create the data to pass to immediate mode! (sigh) */
  const int *facemap_data = CustomData_get_layer(&me->pdata, CD_FACEMAP);
  if (facemap_data) {
    GPU_blend(GPU_BLEND_ALPHA);

    const MVert *verts = BKE_mesh_verts(me);
    const MPoly *polys = BKE_mesh_polys(me);
    const MLoop *loops = BKE_mesh_loops(me);

    int mpoly_len = me->totpoly;
    int mloop_len = me->totloop;

    facemap_data = CustomData_get_layer(&me->pdata, CD_FACEMAP);

    /* Make a batch and free it each time for now. */
    const int looptris_len = poly_to_tri_count(mpoly_len, mloop_len);
    const int vbo_len_capacity = looptris_len * 3;
    int vbo_len_used = 0;

    GPUVertFormat format_pos = {0};
    const uint pos_id = GPU_vertformat_attr_add(
        &format_pos, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);

    GPUVertBuf *vbo_pos = GPU_vertbuf_create_with_format(&format_pos);
    GPU_vertbuf_data_alloc(vbo_pos, vbo_len_capacity);

    GPUVertBufRaw pos_step;
    GPU_vertbuf_attr_get_raw_data(vbo_pos, pos_id, &pos_step);

    const MPoly *mp;
    int i;
    if (BKE_mesh_runtime_looptri_ensure(me)) {
      const MLoopTri *mlt = BKE_mesh_runtime_looptri_ensure(me);
      for (mp = polys, i = 0; i < mpoly_len; i++, mp++) {
        if (facemap_data[i] == facemap) {
          for (int j = 2; j < mp->totloop; j++) {
            copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[loops[mlt->tri[0]].v].co);
            copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[loops[mlt->tri[1]].v].co);
            copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[loops[mlt->tri[2]].v].co);
            vbo_len_used += 3;
            mlt++;
          }
        }
        else {
          mlt += mp->totloop - 2;
        }
      }
    }
    else {
      /* No tessellation data, fan-fill. */
      for (mp = polys, i = 0; i < mpoly_len; i++, mp++) {
        if (facemap_data[i] == facemap) {
          const MLoop *ml_start = &loops[mp->loopstart];
          const MLoop *ml_a = ml_start + 1;
          const MLoop *ml_b = ml_start + 2;
          for (int j = 2; j < mp->totloop; j++) {
            copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[ml_start->v].co);
            copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[ml_a->v].co);
            copy_v3_v3(GPU_vertbuf_raw_step(&pos_step), verts[ml_b->v].co);
            vbo_len_used += 3;

            ml_a++;
            ml_b++;
          }
        }
      }
    }

    if (vbo_len_capacity != vbo_len_used) {
      GPU_vertbuf_data_resize(vbo_pos, vbo_len_used);
    }

    GPUBatch *draw_batch = GPU_batch_create(GPU_PRIM_TRIS, vbo_pos, NULL);
    GPU_batch_program_set_builtin(draw_batch, GPU_SHADER_3D_UNIFORM_COLOR);
    GPU_batch_uniform_4fv(draw_batch, "color", col);
    GPU_batch_draw(draw_batch);
    GPU_batch_discard(draw_batch);
    GPU_vertbuf_discard(vbo_pos);

    GPU_blend(GPU_BLEND_NONE);
  }
}
