/**
 * @file resize_transform.hpp
 * @brief Resize transform with SIMD-accelerated interpolation
 *
 * Supports:
 * - Bilinear interpolation (SIMD-accelerated)
 * - Nearest neighbor interpolation
 * - Bicubic interpolation
 * - Arbitrary target dimensions
 */

#pragma once

#include "transform_base.hpp"
#include "simd_utils.hpp"
#include "../core/buffer_pool.hpp"
#include <cmath>

namespace turboloader {
namespace transforms {

/**
 * @brief Lanczos kernel lookup table for fast weight computation
 *
 * Precomputes Lanczos kernel weights to avoid expensive sin() calls
 * in the inner interpolation loop. ~25% faster Lanczos downsampling.
 */
namespace {
    constexpr int LANCZOS_LUT_SIZE = 512;  // High resolution for accuracy
    constexpr int LANCZOS_A = 3;           // Window size
    constexpr float LANCZOS_PI = 3.14159265358979323846f;

    /**
     * @brief Lookup table for Lanczos kernel weights
     *
     * Maps x values in [0, LANCZOS_A) to kernel weights.
     * Uses linear interpolation for sub-sample accuracy.
     */
    class LanczosLUT {
    public:
        float weights_[LANCZOS_LUT_SIZE];

        LanczosLUT() {
            // Precompute Lanczos weights at construction
            for (int i = 0; i < LANCZOS_LUT_SIZE; ++i) {
                float x = static_cast<float>(i) / (LANCZOS_LUT_SIZE - 1) * LANCZOS_A;
                weights_[i] = compute_lanczos(x);
            }
        }

        /**
         * @brief Fast lookup with linear interpolation
         */
        float lookup(float x) const {
            float abs_x = std::abs(x);
            if (abs_x >= LANCZOS_A) return 0.0f;

            // Map to LUT index with linear interpolation
            float fidx = abs_x / LANCZOS_A * (LANCZOS_LUT_SIZE - 1);
            int idx0 = static_cast<int>(fidx);
            int idx1 = idx0 + 1;

            if (idx1 >= LANCZOS_LUT_SIZE) return weights_[idx0];

            float frac = fidx - idx0;
            return weights_[idx0] * (1.0f - frac) + weights_[idx1] * frac;
        }

    private:
        static float compute_lanczos(float x) {
            if (x == 0.0f) return 1.0f;
            if (x >= LANCZOS_A) return 0.0f;

            float px = LANCZOS_PI * x;
            return (LANCZOS_A * std::sin(px) * std::sin(px / LANCZOS_A)) / (px * px);
        }
    };

    // Global LUT instance (initialized once at program start)
    static const LanczosLUT lanczos_lut;
}

/**
 * @brief Interpolation mode for resizing
 */
enum class InterpolationMode {
    NEAREST,
    BILINEAR,
    BICUBIC,
    LANCZOS  // High-quality downsampling (windowed sinc, a=3)
};

/**
 * @brief Resize transform with optional buffer pooling
 *
 * Buffer pooling reduces allocation overhead by reusing buffers.
 * Enable with use_buffer_pool=true for 5-15% throughput improvement.
 */
class ResizeTransform : public Transform {
public:
    /**
     * @brief Construct resize transform
     * @param target_width Target image width
     * @param target_height Target image height
     * @param mode Interpolation mode (default: BILINEAR)
     * @param use_buffer_pool Use global buffer pool for output allocation
     */
    ResizeTransform(int target_width, int target_height,
                   InterpolationMode mode = InterpolationMode::BILINEAR,
                   bool use_buffer_pool = false)
        : target_width_(target_width),
          target_height_(target_height),
          mode_(mode),
          use_buffer_pool_(use_buffer_pool) {}

    /**
     * @brief Enable or disable buffer pooling
     */
    void set_buffer_pool(bool enable) { use_buffer_pool_ = enable; }
    bool uses_buffer_pool() const { return use_buffer_pool_; }

    std::unique_ptr<ImageData> apply(const ImageData& input) override {
        if (input.width == target_width_ && input.height == target_height_) {
            // No resize needed, return copy
            size_t copy_size = input.size_bytes();
            uint8_t* buffer = allocate_buffer(copy_size);
            auto output = std::make_unique<ImageData>(
                buffer,
                input.width, input.height, input.channels, input.stride, true
            );
            std::memcpy(output->data, input.data, copy_size);
            return output;
        }

        size_t output_size = target_width_ * target_height_ * input.channels;
        uint8_t* buffer = allocate_buffer(output_size);
        auto output = std::make_unique<ImageData>(
            buffer,
            target_width_, target_height_, input.channels,
            target_width_ * input.channels, true
        );

        switch (mode_) {
            case InterpolationMode::NEAREST:
                resize_nearest(input, *output);
                break;
            case InterpolationMode::BILINEAR:
                resize_bilinear(input, *output);
                break;
            case InterpolationMode::BICUBIC:
                resize_bicubic(input, *output);
                break;
            case InterpolationMode::LANCZOS:
                resize_lanczos(input, *output);
                break;
        }

        return output;
    }

    const char* name() const override { return "Resize"; }

private:
    /**
     * @brief Nearest neighbor interpolation
     */
    void resize_nearest(const ImageData& input, ImageData& output) {
        float x_ratio = static_cast<float>(input.width) / target_width_;
        float y_ratio = static_cast<float>(input.height) / target_height_;

        for (int y = 0; y < target_height_; ++y) {
            int src_y = static_cast<int>(y * y_ratio);
            src_y = std::min(src_y, input.height - 1);

            for (int x = 0; x < target_width_; ++x) {
                int src_x = static_cast<int>(x * x_ratio);
                src_x = std::min(src_x, input.width - 1);

                size_t src_idx = (src_y * input.width + src_x) * input.channels;
                size_t dst_idx = (y * target_width_ + x) * output.channels;

                for (int c = 0; c < input.channels; ++c) {
                    output.data[dst_idx + c] = input.data[src_idx + c];
                }
            }
        }
    }

    /**
     * @brief Bilinear interpolation (SIMD-accelerated)
     *
     * Uses SIMD-optimized resize for RGB images (processes 4-8 pixels at a time).
     * Provides 2-3x speedup over scalar implementation on ARM NEON and x86 AVX2.
     */
    void resize_bilinear(const ImageData& input, ImageData& output) {
        // Use SIMD-accelerated resize (Phase 3.2 v2.13.0)
        simd::resize_bilinear_simd(
            input.data, output.data,
            input.width, input.height,
            target_width_, target_height_,
            input.channels
        );
    }

    /**
     * @brief Bicubic interpolation weight (Catmull-Rom)
     */
    static float cubic_weight(float x) {
        x = std::abs(x);
        if (x <= 1.0f) {
            return 1.5f * x * x * x - 2.5f * x * x + 1.0f;
        } else if (x < 2.0f) {
            return -0.5f * x * x * x + 2.5f * x * x - 4.0f * x + 2.0f;
        }
        return 0.0f;
    }

    /**
     * @brief Bicubic interpolation
     */
    void resize_bicubic(const ImageData& input, ImageData& output) {
        float x_ratio = static_cast<float>(input.width) / target_width_;
        float y_ratio = static_cast<float>(input.height) / target_height_;

        for (int y = 0; y < target_height_; ++y) {
            float src_y = (y + 0.5f) * y_ratio - 0.5f;
            int y0 = static_cast<int>(std::floor(src_y));

            for (int x = 0; x < target_width_; ++x) {
                float src_x = (x + 0.5f) * x_ratio - 0.5f;
                int x0 = static_cast<int>(std::floor(src_x));

                float dx = src_x - x0;
                float dy = src_y - y0;

                size_t dst_idx = (y * target_width_ + x) * output.channels;

                for (int c = 0; c < input.channels; ++c) {
                    // Initialize sum to neutral gray (128) to avoid uninitialized
                    // pixels at corners where weight_sum may be zero
                    float sum = 128.0f;
                    float weight_sum = 0.0f;

                    // 4x4 kernel
                    for (int ky = -1; ky <= 2; ++ky) {
                        int py = y0 + ky;
                        if (py < 0 || py >= input.height) continue;

                        float wy = cubic_weight(ky - dy);

                        for (int kx = -1; kx <= 2; ++kx) {
                            int px = x0 + kx;
                            if (px < 0 || px >= input.width) continue;

                            float wx = cubic_weight(kx - dx);
                            float w = wx * wy;

                            size_t src_idx = (py * input.width + px) * input.channels + c;
                            // Reset sum on first valid weight to accumulate properly
                            if (weight_sum == 0.0f) {
                                sum = 0.0f;
                            }
                            sum += input.data[src_idx] * w;
                            weight_sum += w;
                        }
                    }

                    if (weight_sum > 0.0f) {
                        sum /= weight_sum;
                    }
                    // If weight_sum is still 0, sum remains 128 (neutral gray)

                    output.data[dst_idx + c] = static_cast<uint8_t>(
                        simd::clamp(sum, 0.0f, 255.0f)
                    );
                }
            }
        }
    }

    /**
     * @brief Lanczos kernel (windowed sinc filter, a=3)
     * @deprecated Use lanczos_lut.lookup() for better performance
     */
    static float lanczos_kernel(float x, int a = 3) {
        if (x == 0.0f) return 1.0f;
        if (std::abs(x) >= a) return 0.0f;

        constexpr float PI = 3.14159265358979323846f;
        float px = PI * x;
        return (a * std::sin(px) * std::sin(px / a)) / (px * px);
    }

    /**
     * @brief Lanczos interpolation (high-quality resampling)
     *
     * Uses precomputed LUT for ~25% faster performance.
     */
    void resize_lanczos(const ImageData& input, ImageData& output) {
        constexpr int a = 3;  // Lanczos window size
        float x_ratio = static_cast<float>(input.width) / target_width_;
        float y_ratio = static_cast<float>(input.height) / target_height_;

        for (int y = 0; y < target_height_; ++y) {
            float src_y = (y + 0.5f) * y_ratio - 0.5f;
            int y0 = static_cast<int>(std::floor(src_y));

            for (int x = 0; x < target_width_; ++x) {
                float src_x = (x + 0.5f) * x_ratio - 0.5f;
                int x0 = static_cast<int>(std::floor(src_x));

                float dx = src_x - x0;
                float dy = src_y - y0;

                size_t dst_idx = (y * target_width_ + x) * output.channels;

                for (int c = 0; c < input.channels; ++c) {
                    // Initialize sum to neutral gray (128) to avoid uninitialized
                    // pixels at corners where weight_sum may be zero
                    float sum = 128.0f;
                    float weight_sum = 0.0f;

                    // Lanczos kernel: 6x6 window (a=3)
                    // Using LUT for ~25% faster performance
                    for (int ky = -a + 1; ky <= a; ++ky) {
                        int py = y0 + ky;
                        if (py < 0 || py >= input.height) continue;

                        float wy = lanczos_lut.lookup(ky - dy);

                        for (int kx = -a + 1; kx <= a; ++kx) {
                            int px = x0 + kx;
                            if (px < 0 || px >= input.width) continue;

                            float wx = lanczos_lut.lookup(kx - dx);
                            float w = wx * wy;

                            size_t src_idx = (py * input.width + px) * input.channels + c;
                            // Reset sum on first valid weight to accumulate properly
                            if (weight_sum == 0.0f) {
                                sum = 0.0f;
                            }
                            sum += input.data[src_idx] * w;
                            weight_sum += w;
                        }
                    }

                    if (weight_sum > 0.0f) {
                        sum /= weight_sum;
                    }
                    // If weight_sum is still 0, sum remains 128 (neutral gray)

                    output.data[dst_idx + c] = static_cast<uint8_t>(
                        simd::clamp(sum, 0.0f, 255.0f)
                    );
                }
            }
        }
    }

    /**
     * @brief Allocate buffer using pool or direct allocation
     */
    uint8_t* allocate_buffer(size_t size) {
        if (use_buffer_pool_) {
            // Use global buffer pool for reduced allocation overhead
            auto buffer = get_resize_buffer_pool().acquire(size);
            return buffer.release();  // Transfer ownership to caller
        }
        return new uint8_t[size];
    }

    int target_width_;
    int target_height_;
    InterpolationMode mode_;
    bool use_buffer_pool_ = false;
};

} // namespace transforms
} // namespace turboloader
