/* SPDX-License-Identifier: GPL-2.0-or-later
 * Copyright 2005 Blender Foundation. All rights reserved. */

/** \file
 * \ingroup modifiers
 */

#include <stddef.h>

#include "MEM_guardedalloc.h"

#include "BLI_utildefines.h"

#include "BLT_translation.h"

#include "DNA_defaults.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"

#include "BKE_cdderivedmesh.h"
#include "BKE_context.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_paint.h"
#include "BKE_screen.h"
#include "BKE_subdiv.h"
#include "BKE_subdiv_ccg.h"
#include "BKE_subdiv_deform.h"
#include "BKE_subdiv_mesh.h"
#include "BKE_subsurf.h"

#include "UI_interface.h"
#include "UI_resources.h"

#include "RNA_access.h"
#include "RNA_prototypes.h"

#include "WM_types.h" /* For subdivide operator UI. */

#include "DEG_depsgraph_query.h"

#include "MOD_modifiertypes.h"
#include "MOD_ui_common.h"

typedef struct MultiresRuntimeData {
  /* Cached subdivision surface descriptor, with topology and settings. */
  struct Subdiv *subdiv;
} MultiresRuntimeData;

static void initData(ModifierData *md)
{
  MultiresModifierData *mmd = (MultiresModifierData *)md;

  BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(mmd, modifier));

  MEMCPY_STRUCT_AFTER(mmd, DNA_struct_default_get(MultiresModifierData), modifier);

  /* Open subdivision panels by default. */
  md->ui_expand_flag = UI_PANEL_DATA_EXPAND_ROOT | UI_SUBPANEL_DATA_EXPAND_1;
}

static void requiredDataMask(Object *UNUSED(ob),
                             ModifierData *md,
                             CustomData_MeshMasks *r_cddata_masks)
{
  MultiresModifierData *mmd = (MultiresModifierData *)md;
  if (mmd->flags & eMultiresModifierFlag_UseCustomNormals) {
    r_cddata_masks->lmask |= CD_MASK_NORMAL;
    r_cddata_masks->lmask |= CD_MASK_CUSTOMLOOPNORMAL;
  }
}

static bool dependsOnNormals(ModifierData *md)
{
  MultiresModifierData *mmd = (MultiresModifierData *)md;
  if (mmd->flags & eMultiresModifierFlag_UseCustomNormals) {
    return true;
  }
  return false;
}

static void copyData(const ModifierData *md_src, ModifierData *md_dst, const int flag)
{
  BKE_modifier_copydata_generic(md_src, md_dst, flag);
}

static void freeRuntimeData(void *runtime_data_v)
{
  if (runtime_data_v == NULL) {
    return;
  }
  MultiresRuntimeData *runtime_data = (MultiresRuntimeData *)runtime_data_v;
  if (runtime_data->subdiv != NULL) {
    BKE_subdiv_free(runtime_data->subdiv);
  }
  MEM_freeN(runtime_data);
}

static void freeData(ModifierData *md)
{
  MultiresModifierData *mmd = (MultiresModifierData *)md;
  freeRuntimeData(mmd->modifier.runtime);
}

static MultiresRuntimeData *multires_ensure_runtime(MultiresModifierData *mmd)
{
  MultiresRuntimeData *runtime_data = (MultiresRuntimeData *)mmd->modifier.runtime;
  if (runtime_data == NULL) {
    runtime_data = MEM_callocN(sizeof(*runtime_data), "subsurf runtime");
    mmd->modifier.runtime = runtime_data;
  }
  return runtime_data;
}

/* Main goal of this function is to give usable subdivision surface descriptor
 * which matches settings and topology. */
static Subdiv *subdiv_descriptor_ensure(MultiresModifierData *mmd,
                                        const SubdivSettings *subdiv_settings,
                                        const Mesh *mesh)
{
  MultiresRuntimeData *runtime_data = (MultiresRuntimeData *)mmd->modifier.runtime;
  Subdiv *subdiv = BKE_subdiv_update_from_mesh(runtime_data->subdiv, subdiv_settings, mesh);
  runtime_data->subdiv = subdiv;
  return subdiv;
}

/* Subdivide into fully qualified mesh. */

static Mesh *multires_as_mesh(MultiresModifierData *mmd,
                              const ModifierEvalContext *ctx,
                              Mesh *mesh,
                              Subdiv *subdiv)
{
  Mesh *result = mesh;
  const bool use_render_params = (ctx->flag & MOD_APPLY_RENDER);
  const bool ignore_simplify = (ctx->flag & MOD_APPLY_IGNORE_SIMPLIFY);
  const bool ignore_control_edges = (ctx->flag & MOD_APPLY_TO_BASE_MESH);
  const Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
  Object *object = ctx->object;
  SubdivToMeshSettings mesh_settings;
  BKE_multires_subdiv_mesh_settings_init(&mesh_settings,
                                         scene,
                                         object,
                                         mmd,
                                         use_render_params,
                                         ignore_simplify,
                                         ignore_control_edges);
  if (mesh_settings.resolution < 3) {
    return result;
  }
  BKE_subdiv_displacement_attach_from_multires(subdiv, mesh, mmd);
  result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh);
  return result;
}

/* Subdivide into CCG. */

static void multires_ccg_settings_init(SubdivToCCGSettings *settings,
                                       const MultiresModifierData *mmd,
                                       const ModifierEvalContext *ctx,
                                       Mesh *mesh)
{
  const bool has_mask = CustomData_has_layer(&mesh->ldata, CD_GRID_PAINT_MASK);
  const bool use_render_params = (ctx->flag & MOD_APPLY_RENDER);
  const bool ignore_simplify = (ctx->flag & MOD_APPLY_IGNORE_SIMPLIFY);
  const Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
  Object *object = ctx->object;
  const int level = multires_get_level(scene, object, mmd, use_render_params, ignore_simplify);
  settings->resolution = (1 << level) + 1;
  settings->need_normal = true;
  settings->need_mask = has_mask;
}

static Mesh *multires_as_ccg(MultiresModifierData *mmd,
                             const ModifierEvalContext *ctx,
                             Mesh *mesh,
                             Subdiv *subdiv)
{
  Mesh *result = mesh;
  SubdivToCCGSettings ccg_settings;
  multires_ccg_settings_init(&ccg_settings, mmd, ctx, mesh);
  if (ccg_settings.resolution < 3) {
    return result;
  }
  BKE_subdiv_displacement_attach_from_multires(subdiv, mesh, mmd);
  result = BKE_subdiv_to_ccg_mesh(subdiv, &ccg_settings, mesh);

  /* NOTE: CCG becomes an owner of Subdiv descriptor, so can not share
   * this pointer. Not sure if it's needed, but might have a second look
   * on the ownership model here. */
  MultiresRuntimeData *runtime_data = mmd->modifier.runtime;
  runtime_data->subdiv = NULL;

  return result;
}

static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
{
  Mesh *result = mesh;
#if !defined(WITH_OPENSUBDIV)
  BKE_modifier_set_error(ctx->object, md, "Disabled, built without OpenSubdiv");
  return result;
#endif
  MultiresModifierData *mmd = (MultiresModifierData *)md;
  SubdivSettings subdiv_settings;
  BKE_multires_subdiv_settings_init(&subdiv_settings, mmd);
  if (subdiv_settings.level == 0) {
    return result;
  }
  MultiresRuntimeData *runtime_data = multires_ensure_runtime(mmd);
  Subdiv *subdiv = subdiv_descriptor_ensure(mmd, &subdiv_settings, mesh);
  if (subdiv == NULL) {
    /* Happens on bad topology, ut also on empty input mesh. */
    return result;
  }
  const bool use_clnors = mmd->flags & eMultiresModifierFlag_UseCustomNormals &&
                          mesh->flag & ME_AUTOSMOOTH &&
                          CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL);
  /* NOTE: Orco needs final coordinates on CPU side, which are expected to be
   * accessible via MVert. For this reason we do not evaluate multires to
   * grids when orco is requested. */
  const bool for_orco = (ctx->flag & MOD_APPLY_ORCO) != 0;
  /* Needed when rendering or baking will in sculpt mode. */
  const bool for_render = (ctx->flag & MOD_APPLY_RENDER) != 0;

  const bool sculpt_base_mesh = mmd->flags & eMultiresModifierFlag_UseSculptBaseMesh;

  if ((ctx->object->mode & OB_MODE_SCULPT) && !for_orco && !for_render && !sculpt_base_mesh) {
    /* NOTE: CCG takes ownership over Subdiv. */
    result = multires_as_ccg(mmd, ctx, mesh, subdiv);
    result->runtime.subdiv_ccg_tot_level = mmd->totlvl;
    /* TODO(sergey): Usually it is sculpt stroke's update variants which
     * takes care of this, but is possible that we need this before the
     * stroke: i.e. when exiting blender right after stroke is done.
     * Annoying and not so much black-boxed as far as sculpting goes, and
     * surely there is a better way of solving this. */
    if (ctx->object->sculpt != NULL) {
      SculptSession *sculpt_session = ctx->object->sculpt;
      sculpt_session->subdiv_ccg = result->runtime.subdiv_ccg;
      sculpt_session->multires.active = true;
      sculpt_session->multires.modifier = mmd;
      sculpt_session->multires.level = mmd->sculptlvl;
      sculpt_session->totvert = mesh->totvert;
      sculpt_session->totpoly = mesh->totpoly;
      sculpt_session->mvert = NULL;
      sculpt_session->mpoly = NULL;
      sculpt_session->mloop = NULL;
    }
    // BKE_subdiv_stats_print(&subdiv->stats);
  }
  else {
    if (use_clnors) {
      /* If custom normals are present and the option is turned on calculate the split
       * normals and clear flag so the normals get interpolated to the result mesh. */
      BKE_mesh_calc_normals_split(mesh);
      CustomData_clear_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY);
    }

    result = multires_as_mesh(mmd, ctx, mesh, subdiv);

    if (use_clnors) {
      float(*lnors)[3] = CustomData_get_layer(&result->ldata, CD_NORMAL);
      BLI_assert(lnors != NULL);
      BKE_mesh_set_custom_normals(result, lnors);
      CustomData_set_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY);
      CustomData_set_layer_flag(&result->ldata, CD_NORMAL, CD_FLAG_TEMPORARY);
    }
    // BKE_subdiv_stats_print(&subdiv->stats);
    if (subdiv != runtime_data->subdiv) {
      BKE_subdiv_free(subdiv);
    }
  }
  return result;
}

static void deformMatrices(ModifierData *md,
                           const ModifierEvalContext *ctx,
                           Mesh *mesh,
                           float (*vertex_cos)[3],
                           float (*deform_matrices)[3][3],
                           int verts_num)

{
#if !defined(WITH_OPENSUBDIV)
  BKE_modifier_set_error(ctx->object, md, "Disabled, built without OpenSubdiv");
  return;
#endif

  /* Subsurf does not require extra space mapping, keep matrices as is. */
  (void)deform_matrices;

  MultiresModifierData *mmd = (MultiresModifierData *)md;

  SubdivSettings subdiv_settings;
  BKE_multires_subdiv_settings_init(&subdiv_settings, mmd);
  if (subdiv_settings.level == 0) {
    return;
  }

  SubdivToCCGSettings ccg_settings;
  multires_ccg_settings_init(&ccg_settings, mmd, ctx, mesh);
  if (ccg_settings.resolution < 3) {
    return;
  }

  MultiresRuntimeData *runtime_data = multires_ensure_runtime(mmd);
  Subdiv *subdiv = subdiv_descriptor_ensure(mmd, &subdiv_settings, mesh);
  if (subdiv == NULL) {
    /* Happens on bad topology, ut also on empty input mesh. */
    return;
  }
  BKE_subdiv_displacement_attach_from_multires(subdiv, mesh, mmd);
  BKE_subdiv_deform_coarse_vertices(subdiv, mesh, vertex_cos, verts_num);
  if (subdiv != runtime_data->subdiv) {
    BKE_subdiv_free(subdiv);
  }
}

static void panel_draw(const bContext *C, Panel *panel)
{
  uiLayout *col;
  uiLayout *layout = panel->layout;

  PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL);

  uiLayoutSetPropSep(layout, true);

  col = uiLayoutColumn(layout, true);
  uiItemR(col, ptr, "levels", 0, IFACE_("Level Viewport"), ICON_NONE);
  uiItemR(col, ptr, "sculpt_levels", 0, IFACE_("Sculpt"), ICON_NONE);
  uiItemR(col, ptr, "render_levels", 0, IFACE_("Render"), ICON_NONE);

  const bool is_sculpt_mode = CTX_data_active_object(C)->mode & OB_MODE_SCULPT;
  uiBlock *block = uiLayoutGetBlock(panel->layout);
  UI_block_lock_set(block, !is_sculpt_mode, IFACE_("Sculpt Base Mesh"));
  uiItemR(col, ptr, "use_sculpt_base_mesh", 0, IFACE_("Sculpt Base Mesh"), ICON_NONE);
  UI_block_lock_clear(block);

  uiItemR(layout, ptr, "show_only_control_edges", 0, NULL, ICON_NONE);

  modifier_panel_end(layout, ptr);
}

static void subdivisions_panel_draw(const bContext *UNUSED(C), Panel *panel)
{
  uiLayout *row;
  uiLayout *layout = panel->layout;

  PointerRNA ob_ptr;
  PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);

  uiLayoutSetEnabled(layout, RNA_enum_get(&ob_ptr, "mode") != OB_MODE_EDIT);

  MultiresModifierData *mmd = (MultiresModifierData *)ptr->data;

  /**
   * Changing some of the properties can not be done once there is an
   * actual displacement stored for this multi-resolution modifier.
   * This check will disallow changes for those properties.
   * This check is a bit stupid but it should be sufficient for the usual
   * multi-resolution usage. It might become less strict and only disallow
   * modifications if there is CD_MDISPS layer, or if there is actual
   * non-zero displacement, but such checks will be too slow to be done
   * on every redraw.
   */

  PointerRNA op_ptr;
  uiItemFullO(layout,
              "OBJECT_OT_multires_subdivide",
              IFACE_("Subdivide"),
              ICON_NONE,
              NULL,
              WM_OP_EXEC_DEFAULT,
              0,
              &op_ptr);
  RNA_enum_set(&op_ptr, "mode", MULTIRES_SUBDIVIDE_CATMULL_CLARK);
  RNA_string_set(&op_ptr, "modifier", ((ModifierData *)mmd)->name);

  row = uiLayoutRow(layout, false);
  uiItemFullO(row,
              "OBJECT_OT_multires_subdivide",
              IFACE_("Simple"),
              ICON_NONE,
              NULL,
              WM_OP_EXEC_DEFAULT,
              0,
              &op_ptr);
  RNA_enum_set(&op_ptr, "mode", MULTIRES_SUBDIVIDE_SIMPLE);
  RNA_string_set(&op_ptr, "modifier", ((ModifierData *)mmd)->name);
  uiItemFullO(row,
              "OBJECT_OT_multires_subdivide",
              IFACE_("Linear"),
              ICON_NONE,
              NULL,
              WM_OP_EXEC_DEFAULT,
              0,
              &op_ptr);
  RNA_enum_set(&op_ptr, "mode", MULTIRES_SUBDIVIDE_LINEAR);
  RNA_string_set(&op_ptr, "modifier", ((ModifierData *)mmd)->name);

  uiItemS(layout);

  uiItemO(layout, IFACE_("Unsubdivide"), ICON_NONE, "OBJECT_OT_multires_unsubdivide");
  uiItemO(layout, IFACE_("Delete Higher"), ICON_NONE, "OBJECT_OT_multires_higher_levels_delete");
}

static void shape_panel_draw(const bContext *UNUSED(C), Panel *panel)
{
  uiLayout *row;
  uiLayout *layout = panel->layout;

  PointerRNA ob_ptr;
  modifier_panel_get_property_pointers(panel, &ob_ptr);

  uiLayoutSetEnabled(layout, RNA_enum_get(&ob_ptr, "mode") != OB_MODE_EDIT);

  row = uiLayoutRow(layout, false);
  uiItemO(row, IFACE_("Reshape"), ICON_NONE, "OBJECT_OT_multires_reshape");
  uiItemO(row, IFACE_("Apply Base"), ICON_NONE, "OBJECT_OT_multires_base_apply");
}

static void generate_panel_draw(const bContext *UNUSED(C), Panel *panel)
{
  uiLayout *col, *row;
  uiLayout *layout = panel->layout;

  PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL);
  MultiresModifierData *mmd = (MultiresModifierData *)ptr->data;

  bool is_external = RNA_boolean_get(ptr, "is_external");

  if (mmd->totlvl == 0) {
    uiItemO(
        layout, IFACE_("Rebuild Subdivisions"), ICON_NONE, "OBJECT_OT_multires_rebuild_subdiv");
  }

  col = uiLayoutColumn(layout, false);
  row = uiLayoutRow(col, false);
  if (is_external) {
    uiItemO(row, IFACE_("Pack External"), ICON_NONE, "OBJECT_OT_multires_external_pack");
    uiLayoutSetPropSep(col, true);
    row = uiLayoutRow(col, false);
    uiItemR(row, ptr, "filepath", 0, NULL, ICON_NONE);
  }
  else {
    uiItemO(col, IFACE_("Save External..."), ICON_NONE, "OBJECT_OT_multires_external_save");
  }
}

static void advanced_panel_draw(const bContext *UNUSED(C), Panel *panel)
{
  uiLayout *col;
  uiLayout *layout = panel->layout;

  PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL);

  bool has_displacement = RNA_int_get(ptr, "total_levels") != 0;

  uiLayoutSetPropSep(layout, true);

  uiLayoutSetActive(layout, !has_displacement);

  uiItemR(layout, ptr, "quality", 0, NULL, ICON_NONE);

  col = uiLayoutColumn(layout, false);
  uiLayoutSetActive(col, true);
  uiItemR(col, ptr, "uv_smooth", 0, NULL, ICON_NONE);
  uiItemR(col, ptr, "boundary_smooth", 0, NULL, ICON_NONE);

  uiItemR(layout, ptr, "use_creases", 0, NULL, ICON_NONE);
  uiItemR(layout, ptr, "use_custom_normals", 0, NULL, ICON_NONE);
}

static void panelRegister(ARegionType *region_type)
{
  PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Multires, panel_draw);
  modifier_subpanel_register(
      region_type, "subdivide", "Subdivision", NULL, subdivisions_panel_draw, panel_type);
  modifier_subpanel_register(region_type, "shape", "Shape", NULL, shape_panel_draw, panel_type);
  modifier_subpanel_register(
      region_type, "generate", "Generate", NULL, generate_panel_draw, panel_type);
  modifier_subpanel_register(
      region_type, "advanced", "Advanced", NULL, advanced_panel_draw, panel_type);
}

ModifierTypeInfo modifierType_Multires = {
    /* name */ N_("Multires"),
    /* structName */ "MultiresModifierData",
    /* structSize */ sizeof(MultiresModifierData),
    /* srna */ &RNA_MultiresModifier,
    /* type */ eModifierTypeType_Constructive,
    /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsMapping |
        eModifierTypeFlag_RequiresOriginalData,
    /* icon */ ICON_MOD_MULTIRES,

    /* copyData */ copyData,

    /* deformVerts */ NULL,
    /* deformMatrices */ deformMatrices,
    /* deformVertsEM */ NULL,
    /* deformMatricesEM */ NULL,
    /* modifyMesh */ modifyMesh,
    /* modifyGeometrySet */ NULL,

    /* initData */ initData,
    /* requiredDataMask */ requiredDataMask,
    /* freeData */ freeData,
    /* isDisabled */ NULL,
    /* updateDepsgraph */ NULL,
    /* dependsOnTime */ NULL,
    /* dependsOnNormals */ dependsOnNormals,
    /* foreachIDLink */ NULL,
    /* foreachTexLink */ NULL,
    /* freeRuntimeData */ freeRuntimeData,
    /* panelRegister */ panelRegister,
    /* blendWrite */ NULL,
    /* blendRead */ NULL,
};
