#pragma once

/*
Amanatides–Woo grid traversal algorithm in C++.
Given a line defined by two points, this algorithm traverses the grid cells
intersecting the line and calls a visitor function for each cell.
*/

#include "fl/math/math.h"
#include "fl/math/geometry.h"
#include "fl/math/math.h"
#include "fl/stl/limits.h"
#include "fl/stl/int.h"

namespace fl {

/// @brief Traverse a grid segment by selecting the cells that are crossed. This
/// version will select the fastest integer implementation based on the length
/// of the segment. Most of the time it will call traverseGridSegment16() since
/// segment spans are typically < 256 pixels.
/// @tparam GridVisitor
/// @param start start point
/// @param end  end point
/// @param visitor called for each cell visited.
/// @details Fully tested.
template <typename GridVisitor>
void traverseGridSegment(const vec2f &start, const vec2f &end,
                         GridVisitor &visitor);

/// @brief Traverse a grid segment using fixed-point 8.8 arithmetic.
/// @tparam GridVisitor
/// @param start start point
/// @param end  end point
/// @param visitor called for each cell visited.
/// @details UNTESTED!!!!
template <typename GridVisitor>
void traverseGridSegment16(const vec2f &start, const vec2f &end,
                           GridVisitor &visitor);

// @brief Traverse a grid segment using fixed-point 24.8 arithmetic.
/// @tparam GridVisitor
/// @param start start point
/// @param end end point
/// @param visitor called for each cell visited.
/// @details UNTESTED!!!!
template <typename GridVisitor>
void traverseGridSegment32(const vec2f &start, const vec2f &end,
                           GridVisitor &visitor);

/// @brief Traverse a grid segment using floating point arithmetic. Useful for
/// testing.
/// @tparam GridVisitor
/// @param start start point
/// @param end  end point
/// @param visitor called for each cell visited.
/// @details Fully tested.
template <typename GridVisitor>
void traverseGridSegmentFloat(const vec2f &start, const vec2f &end,
                              GridVisitor &visitor);

////////////////////////// IMPLEMENTATION DETAILS //////////////////////////

/// @brief Traverse a grid segment using fixed-point 8.8 arithmetic.
/// @tparam GridVisitor
/// @param start start point
/// @param end  end point
/// @param visitor called for each cell visited.
/// @details Fully tested.
template <typename GridVisitor>
inline void traverseGridSegmentFloat(const vec2f &start, const vec2f &end,
                                     GridVisitor &visitor) {
    int x0 = static_cast<int>(fl::floor(start.x));
    int y0 = static_cast<int>(fl::floor(start.y));
    int x1 = static_cast<int>(fl::floor(end.x));
    int y1 = static_cast<int>(fl::floor(end.y));

    int stepX = (x1 > x0) ? 1 : (x1 < x0) ? -1 : 0;
    int stepY = (y1 > y0) ? 1 : (y1 < y0) ? -1 : 0;

    float dx = end.x - start.x;
    float dy = end.y - start.y;

    float tDeltaX = (dx != 0.0f) ? fl::abs(1.0f / dx) : FL_FLT_MAX;
    float tDeltaY = (dy != 0.0f) ? fl::abs(1.0f / dy) : FL_FLT_MAX;

    float nextX = (stepX > 0) ? (fl::floor(start.x) + 1) : fl::floor(start.x);
    float nextY = (stepY > 0) ? (fl::floor(start.y) + 1) : fl::floor(start.y);

    float tMaxX = (dx != 0.0f) ? fl::abs((nextX - start.x) / dx) : FL_FLT_MAX;
    float tMaxY = (dy != 0.0f) ? fl::abs((nextY - start.y) / dy) : FL_FLT_MAX;

    float maxT = 1.0f;

    int currentX = x0;
    int currentY = y0;

    while (true) {
        visitor.visit(currentX, currentY);
        float t = fl::min(tMaxX, tMaxY);
        if (t > maxT)
            break;

        if (tMaxX < tMaxY) {
            tMaxX += tDeltaX;
            currentX += stepX;
        } else {
            tMaxY += tDeltaY;
            currentY += stepY;
        }
    }

    // Ensure the end cell (x1, y1) is visited at least once
    if (currentX != x1 || currentY != y1) {
        visitor.visit(x1, y1);
    }
}

/// @brief Traverse a grid segment using fixed-point 8.8 arithmetic.
/// @tparam GridVisitor
/// @param start start point
/// @param end  end point
/// @param visitor called for each cell visited.
/// @details UNTESTED!!!!
template <typename GridVisitor>
inline void traverseGridSegment16(const vec2f &start, const vec2f &end,
                                  GridVisitor &visitor) {
    const i16 FP_SHIFT = 8;
    const i16 FP_ONE = 1 << FP_SHIFT;
    // const i16 FP_MASK = FP_ONE - 1;

    // Convert to fixed-point (Q8.8), signed
    i16 startX_fp = static_cast<i16>(start.x * FP_ONE);
    i16 startY_fp = static_cast<i16>(start.y * FP_ONE);
    i16 endX_fp = static_cast<i16>(end.x * FP_ONE);
    i16 endY_fp = static_cast<i16>(end.y * FP_ONE);

    i16 x0 = startX_fp >> FP_SHIFT;
    i16 y0 = startY_fp >> FP_SHIFT;
    i16 x1 = endX_fp >> FP_SHIFT;
    i16 y1 = endY_fp >> FP_SHIFT;

    i16 stepX = (x1 > x0) ? 1 : (x1 < x0) ? -1 : 0;
    i16 stepY = (y1 > y0) ? 1 : (y1 < y0) ? -1 : 0;

    i16 deltaX_fp = endX_fp - startX_fp;
    i16 deltaY_fp = endY_fp - startY_fp;

    // Use (max)() to prevent macro expansion by Arduino.h's max macro
    u16 absDeltaX_fp =
        (deltaX_fp != 0) ? static_cast<u16>(
                               fl::abs((i32(FP_ONE) << FP_SHIFT) / deltaX_fp))
                         : (fl::numeric_limits<u16>::max)();
    u16 absDeltaY_fp =
        (deltaY_fp != 0) ? static_cast<u16>(
                               fl::abs((i32(FP_ONE) << FP_SHIFT) / deltaY_fp))
                         : (fl::numeric_limits<u16>::max)();

    i16 nextX_fp = (stepX > 0) ? ((x0 + 1) << FP_SHIFT) : (x0 << FP_SHIFT);
    i16 nextY_fp = (stepY > 0) ? ((y0 + 1) << FP_SHIFT) : (y0 << FP_SHIFT);

    // Use (max)() to prevent macro expansion by Arduino.h's max macro
    u16 tMaxX_fp =
        (deltaX_fp != 0)
            ? static_cast<u16>(
                  fl::abs(i32(nextX_fp - startX_fp)) * absDeltaX_fp >> FP_SHIFT)
            : (fl::numeric_limits<u16>::max)();
    u16 tMaxY_fp =
        (deltaY_fp != 0)
            ? static_cast<u16>(
                  fl::abs(i32(nextY_fp - startY_fp)) * absDeltaY_fp >> FP_SHIFT)
            : (fl::numeric_limits<u16>::max)();

    const u16 maxT_fp = FP_ONE;

    i16 currentX = x0;
    i16 currentY = y0;

    while (true) {
        visitor.visit(currentX, currentY);

        u16 t_fp = (tMaxX_fp < tMaxY_fp) ? tMaxX_fp : tMaxY_fp;
        if (t_fp > maxT_fp)
            break;

        if (tMaxX_fp < tMaxY_fp) {
            tMaxX_fp += absDeltaX_fp;
            currentX += stepX;
        } else {
            tMaxY_fp += absDeltaY_fp;
            currentY += stepY;
        }
    }

    // Ensure the end cell (x1, y1) is visited at least once
    if (currentX != x1 || currentY != y1) {
        visitor.visit(x1, y1);
    }
}

// @brief Traverse a grid segment using fixed-point 24.8 arithmetic.
/// @tparam GridVisitor
/// @param start start point
/// @param end end point
/// @param visitor called for each cell visited.
/// @details UNTESTED!!!!
template <typename GridVisitor>
inline void traverseGridSegment32(const vec2f &start, const vec2f &end,
                                  GridVisitor &visitor) {
    const i32 FP_SHIFT = 8;
    const i32 FP_ONE = 1 << FP_SHIFT;
    // const i32 FP_MASK = FP_ONE - 1;

    // Convert to fixed-point (Q24.8) signed
    i32 startX_fp = static_cast<i32>(start.x * FP_ONE);
    i32 startY_fp = static_cast<i32>(start.y * FP_ONE);
    i32 endX_fp = static_cast<i32>(end.x * FP_ONE);
    i32 endY_fp = static_cast<i32>(end.y * FP_ONE);

    i32 x0 = startX_fp >> FP_SHIFT;
    i32 y0 = startY_fp >> FP_SHIFT;
    i32 x1 = endX_fp >> FP_SHIFT;
    i32 y1 = endY_fp >> FP_SHIFT;

    i32 stepX = (x1 > x0) ? 1 : (x1 < x0) ? -1 : 0;
    i32 stepY = (y1 > y0) ? 1 : (y1 < y0) ? -1 : 0;

    i32 deltaX_fp = endX_fp - startX_fp;
    i32 deltaY_fp = endY_fp - startY_fp;

    // Use (max)() to prevent macro expansion by Arduino.h's max macro
    u32 absDeltaX_fp =
        (deltaX_fp != 0) ? static_cast<u32>(
                               fl::abs((fl::i64(FP_ONE) << FP_SHIFT) / deltaX_fp))
                         : (fl::numeric_limits<u32>::max)();
    u32 absDeltaY_fp =
        (deltaY_fp != 0) ? static_cast<u32>(
                               fl::abs((fl::i64(FP_ONE) << FP_SHIFT) / deltaY_fp))
                         : (fl::numeric_limits<u32>::max)();

    i32 nextX_fp = (stepX > 0) ? ((x0 + 1) << FP_SHIFT) : (x0 << FP_SHIFT);
    i32 nextY_fp = (stepY > 0) ? ((y0 + 1) << FP_SHIFT) : (y0 << FP_SHIFT);

    // Use (max)() to prevent macro expansion by Arduino.h's max macro
    u32 tMaxX_fp =
        (deltaX_fp != 0)
            ? static_cast<u32>(
                  fl::abs(fl::i64(nextX_fp - startX_fp)) * absDeltaX_fp >> FP_SHIFT)
            : (fl::numeric_limits<u32>::max)();
    u32 tMaxY_fp =
        (deltaY_fp != 0)
            ? static_cast<u32>(
                  fl::abs(fl::i64(nextY_fp - startY_fp)) * absDeltaY_fp >> FP_SHIFT)
            : (fl::numeric_limits<u32>::max)();

    const u32 maxT_fp = FP_ONE;

    i32 currentX = x0;
    i32 currentY = y0;

    while (true) {
        visitor.visit(currentX, currentY);

        u32 t_fp = (tMaxX_fp < tMaxY_fp) ? tMaxX_fp : tMaxY_fp;
        if (t_fp > maxT_fp)
            break;

        if (tMaxX_fp < tMaxY_fp) {
            tMaxX_fp += absDeltaX_fp;
            currentX += stepX;
        } else {
            tMaxY_fp += absDeltaY_fp;
            currentY += stepY;
        }
    }

    if (currentX != x1 || currentY != y1) {
        visitor.visit(x1, y1);
    }
}

template <typename GridVisitor>
inline void traverseGridSegment(const vec2f &start, const vec2f &end,
                                GridVisitor &visitor) {
    float dx = fl::abs(end.x - start.x);
    float dy = fl::abs(end.y - start.y);
    float maxRange = fl::max(dx, dy);

    // if (maxRange < 256.0f) {
    //     // Use Q8.8 (16-bit signed) if within ±127
    //     traverseGridSegment16(start, end, visitor);
    // }
    // else if (maxRange < 16777216.0f) {
    //     // Use Q24.8 (32-bit signed) if within ±8 million
    //     traverseGridSegment32(start, end, visitor);
    // }
    // else {
    //     // Fall back to floating-point
    //     traverseGridSegment(start, end, visitor);
    // }

    if (maxRange < 256.0f) {
        // Use Q8.8 (16-bit signed) if within ±127
        traverseGridSegment16(start, end, visitor);
    } else {
        // Use Q24.8 (32-bit signed) if within ±8 million
        traverseGridSegment32(start, end, visitor);
    }
}

} // namespace fl
