package io.visus.orbis.feature.orb.util

import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.sqrt

/**
 * Generates procedural sky cubemaps for environment mapping and reflections.
 *
 * This generator creates a 6-face cubemap texture representing a simple sky gradient
 * that transitions from a blue sky at the top, through a bright horizon, down to a
 * dark ground color. The cubemap can be used for:
 * - Environment reflections on glossy surfaces
 * - Skybox rendering
 * - Image-based lighting approximations
 *
 * The generated cubemap uses RGBA8 format with faces ordered as: +X, -X, +Y, -Y, +Z, -Z
 * following the standard cubemap convention.
 *
 * @see generate
 */
object CubemapGenerator {

    /** Resolution of each cubemap face in pixels (width and height). */
    private const val FACE_SIZE = 64

    /** Bytes per pixel in RGBA8 format. */
    private const val BYTES_PER_PIXEL = 4

    // Sky color constants (stored individually to avoid array access overhead)
    /** Red component of the sky color at zenith (top of sky dome). */
    private const val SKY_TOP_R = 0.4f
    /** Green component of the sky color at zenith. */
    private const val SKY_TOP_G = 0.6f
    /** Blue component of the sky color at zenith. */
    private const val SKY_TOP_B = 0.9f

    /** Red component of the horizon color. */
    private const val SKY_HORIZON_R = 0.8f
    /** Green component of the horizon color. */
    private const val SKY_HORIZON_G = 0.85f
    /** Blue component of the horizon color. */
    private const val SKY_HORIZON_B = 0.95f

    /** Red component of the ground color (below horizon). */
    private const val GROUND_R = 0.15f
    /** Green component of the ground color. */
    private const val GROUND_G = 0.12f
    /** Blue component of the ground color. */
    private const val GROUND_B = 0.1f

    /**
     * Generates a procedural sky cubemap with 6 faces.
     *
     * The returned buffer contains pixel data for all 6 faces laid out sequentially
     * in the standard cubemap order: +X, -X, +Y, -Y, +Z, -Z. Each face is [FACE_SIZE]
     * pixels square, with 4 bytes per pixel (RGBA8 format).
     *
     * The buffer is allocated as a direct ByteBuffer with native byte order for
     * efficient upload to GPU textures.
     *
     * @return A [ByteBuffer] positioned at the start, containing the complete cubemap
     *         data. Total size is `FACE_SIZE * FACE_SIZE * 4 * 6` bytes.
     */
    fun generate(): ByteBuffer {
        val totalSize = FACE_SIZE * FACE_SIZE * BYTES_PER_PIXEL * 6
        val buffer = ByteBuffer.allocateDirect(totalSize).order(ByteOrder.nativeOrder())

        // Pre-allocate reusable arrays to avoid per-pixel allocations
        val direction = FloatArray(3)
        val color = FloatArray(4)

        for (face in 0 until 6) {
            generateFace(buffer, face, direction, color)
        }

        buffer.rewind()
        return buffer
    }

    /**
     * Generates pixel data for a single cubemap face.
     *
     * Iterates over all pixels in the face, converts each pixel coordinate to a 3D
     * direction vector, samples the sky color for that direction, and writes the
     * resulting RGBA values to the buffer.
     *
     * @param buffer The destination buffer to write pixel data to.
     * @param face The face index (0-5) corresponding to +X, -X, +Y, -Y, +Z, -Z.
     * @param direction Reusable array for storing the computed direction vector.
     * @param color Reusable array for storing the sampled RGBA color.
     */
    private fun generateFace(buffer: ByteBuffer, face: Int, direction: FloatArray, color: FloatArray) {
        for (y in 0 until FACE_SIZE) {
            for (x in 0 until FACE_SIZE) {
                // Convert pixel coordinates to direction vector
                val u = (x + 0.5f) / FACE_SIZE * 2.0f - 1.0f
                val v = (y + 0.5f) / FACE_SIZE * 2.0f - 1.0f

                faceToDirection(face, u, v, direction)
                sampleSky(direction, color)

                // Write RGBA bytes (no coerceIn needed - colors are already in valid 0-1 range)
                buffer.put((color[0] * 255f).toInt().toByte())
                buffer.put((color[1] * 255f).toInt().toByte())
                buffer.put((color[2] * 255f).toInt().toByte())
                buffer.put((color[3] * 255f).toInt().toByte())
            }
        }
    }

    /**
     * Converts 2D face coordinates to a 3D direction vector.
     *
     * Maps normalized UV coordinates on a cubemap face to the corresponding
     * world-space direction vector pointing outward from the cube center.
     *
     * @param face The face index (0-5): 0=+X, 1=-X, 2=+Y, 3=-Y, 4=+Z, 5=-Z.
     * @param u Horizontal coordinate in range [-1, 1], left to right.
     * @param v Vertical coordinate in range [-1, 1], bottom to top.
     * @param out Output array to store the normalized direction vector [x, y, z].
     */
    private fun faceToDirection(face: Int, u: Float, v: Float, out: FloatArray) {
        // Cubemap face ordering: +X, -X, +Y, -Y, +Z, -Z
        when (face) {
            0 -> { out[0] = 1.0f; out[1] = -v; out[2] = -u }   // +X
            1 -> { out[0] = -1.0f; out[1] = -v; out[2] = u }   // -X
            2 -> { out[0] = u; out[1] = 1.0f; out[2] = v }     // +Y (top)
            3 -> { out[0] = u; out[1] = -1.0f; out[2] = -v }   // -Y (bottom)
            4 -> { out[0] = u; out[1] = -v; out[2] = 1.0f }    // +Z
            5 -> { out[0] = -u; out[1] = -v; out[2] = -1.0f }  // -Z
        }
        normalize(out)
    }

    /**
     * Normalizes a 3D vector in-place to unit length.
     *
     * @param v The vector to normalize, modified in-place. Must have at least 3 elements.
     */
    private fun normalize(v: FloatArray) {
        val lenSq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]
        if (lenSq > 0f) {
            val invLen = (1.0 / sqrt(lenSq.toDouble())).toFloat()
            v[0] *= invLen
            v[1] *= invLen
            v[2] *= invLen
        }
    }

    /**
     * Samples the sky color for a given direction vector.
     *
     * Uses the Y component of the direction to determine vertical position:
     * - Positive Y (above horizon): Interpolates from horizon color to sky top color
     * - Negative Y (below horizon): Interpolates from horizon color to ground color
     *
     * This creates a simple gradient sky with a bright horizon that fades to
     * a deeper blue at the zenith and a dark ground below.
     *
     * @param direction The normalized direction vector to sample.
     * @param out Output array to store the RGBA color values in range [0, 1].
     */
    private fun sampleSky(direction: FloatArray, out: FloatArray) {
        val y = direction[1]

        if (y > 0f) {
            // Above horizon: interpolate from horizon to sky top
            val t = if (y > 1f) 1f else y
            out[0] = SKY_HORIZON_R + (SKY_TOP_R - SKY_HORIZON_R) * t
            out[1] = SKY_HORIZON_G + (SKY_TOP_G - SKY_HORIZON_G) * t
            out[2] = SKY_HORIZON_B + (SKY_TOP_B - SKY_HORIZON_B) * t
            out[3] = 1f
        } else {
            // Below horizon: interpolate from horizon to ground
            val t = if (y < -1f) 1f else -y
            out[0] = SKY_HORIZON_R + (GROUND_R - SKY_HORIZON_R) * t
            out[1] = SKY_HORIZON_G + (GROUND_G - SKY_HORIZON_G) * t
            out[2] = SKY_HORIZON_B + (GROUND_B - SKY_HORIZON_B) * t
            out[3] = 1f
        }
    }

    /**
     * Returns the resolution (width and height) of each cubemap face in pixels.
     *
     * @return The size of each square cubemap face.
     */
    fun getFaceSize(): Int = FACE_SIZE
}
