/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup overlay
 */

#pragma once

#include "BKE_paint.hh"

#include "draw_sculpt.hh"

#include "overlay_base.hh"

namespace blender::draw::overlay {

/**
 * Draw a specific color for front and back-faces on surfaces.
 * Can be toggle in (Viewport Overlays > Geometry > Face Orientation)
 */
class Facing : Overlay {

 private:
  PassMain ps_ = {"Facing"};

 public:
  void begin_sync(Resources &res, const State &state) final
  {
    enabled_ = state.v3d && state.show_face_orientation() && !state.xray_enabled &&
               !res.is_selection() && !state.is_depth_only_drawing;
    if (!enabled_) {
      /* Not used. But release the data. */
      ps_.init();
      return;
    }

    const View3DShading &shading = state.v3d->shading;
    const bool is_solid_viewport = shading.type == OB_SOLID;
    bool use_cull = (is_solid_viewport && (shading.flag & V3D_SHADING_BACKFACE_CULLING));
    DRWState backface_cull_state = use_cull ? DRW_STATE_CULL_BACK : DRWState(0);

    /* Use the Depth Equal test in solid mode to ensure transparent textures display correctly.
     * (See #128113). And the Depth-Less test in other modes (E.g. EEVEE) to ensure the overlay
     * displays correctly (See # 114000). */
    DRWState depth_compare_state = is_solid_viewport ? DRW_STATE_DEPTH_EQUAL :
                                                       DRW_STATE_DEPTH_LESS_EQUAL;

    ps_.init();
    ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | depth_compare_state |
                      backface_cull_state,
                  state.clipping_plane_count);
    ps_.shader_set(res.shaders->facing.get());
    ps_.bind_ubo(OVERLAY_GLOBALS_SLOT, &res.globals_buf);
    ps_.bind_ubo(DRW_CLIPPING_UBO_SLOT, &res.clip_planes_buf);
  }

  void object_sync(Manager &manager,
                   const ObjectRef &ob_ref,
                   Resources & /*res*/,
                   const State &state) final
  {
    if (!enabled_) {
      return;
    }
    const bool renderable = DRW_object_is_renderable(ob_ref.object);
    const bool draw_surface = (ob_ref.object->dt >= OB_WIRE) &&
                              (renderable || (ob_ref.object->dt == OB_WIRE));
    const bool draw_facing = draw_surface && (ob_ref.object->dt >= OB_SOLID);
    if (!draw_facing) {
      return;
    }
    const bool use_sculpt_pbvh = BKE_sculptsession_use_pbvh_draw(ob_ref.object, state.rv3d) &&
                                 !state.is_image_render;

    if (use_sculpt_pbvh) {
      ResourceHandle handle = manager.resource_handle_for_sculpt(ob_ref);

      for (SculptBatch &batch : sculpt_batches_get(ob_ref.object, SCULPT_BATCH_DEFAULT)) {
        ps_.draw(batch.batch, handle);
      }
    }
    else {
      blender::gpu::Batch *geom = DRW_cache_object_surface_get(ob_ref.object);
      if (geom) {
        ps_.draw(geom, manager.unique_handle(ob_ref));
      }
    }
  }

  void pre_draw(Manager &manager, View &view) final
  {
    if (!enabled_) {
      return;
    }

    manager.generate_commands(ps_, view);
  }

  void draw(Framebuffer &framebuffer, Manager &manager, View &view) final
  {
    if (!enabled_) {
      return;
    }

    GPU_framebuffer_bind(framebuffer);
    manager.submit_only(ps_, view);
  }
};

}  // namespace blender::draw::overlay
