#include <render/spiral.h>

Spiral::Spiral(Vector2i size, Vector2i offset, size_t block_size)
    : m_block_size(block_size),
      m_size(size), m_offset(offset)
{

    m_blocks = (m_size.cast<Float>() / m_block_size).array().ceil().matrix().cast<int>();
    m_block_count = m_blocks.prod();

    reset();
}

void Spiral::reset()
{
    m_block_counter = 0;
    m_current_direction = Direction::Right;
    m_position = m_blocks / 2;
    m_steps_left = 1;
    m_steps = 1;
}

std::tuple<Vector2i, Vector2i, size_t> Spiral::next_block()
{
    // Reimplementation of the spiraling block generator by Adam Arbree.
    const std::lock_guard<std::mutex> lock(m_mutex);
    // Calculate a unique identifer per block
    size_t block_id = m_block_counter;

    Vector2i offset(m_position * (int)m_block_size);
    Vector2i size = (m_size - offset).cwiseMin(m_block_size);

    offset += m_offset;

    assert((size.array() > 0).all());

    ++m_block_counter;

    if (m_block_counter != m_block_count)
    {
        // Prepare the next block's position along the spiral.
        do
        {
            switch (m_current_direction)
            {
            case Direction::Right:
                ++m_position.x();
                break;
            case Direction::Down:
                ++m_position.y();
                break;
            case Direction::Left:
                --m_position.x();
                break;
            case Direction::Up:
                --m_position.y();
                break;
            }

            if (--m_steps_left == 0)
            {
                m_current_direction = Direction(((int)m_current_direction + 1) % 4);
                if (m_current_direction == Direction::Left ||
                    m_current_direction == Direction::Right)
                    ++m_steps;
                m_steps_left = m_steps;
            }
        } while ((m_position.array() < 0 || m_position.array() >= m_blocks.array()).any());
    }

    return {offset, size, block_id};
}