/*
 * Copyright (c) 2025, Nico Weber <thakis@chromium.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibGfx/ImageFormats/JPEG2000ProgressionIterators.h>

namespace Gfx::JPEG2000 {

LayerResolutionLevelComponentPositionProgressionIterator::LayerResolutionLevelComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function<int(int resolution_level, int component)> precinct_count)
    : m_layer_count(layer_count)
    , m_max_number_of_decomposition_levels(max_number_of_decomposition_levels)
    , m_component_count(component_count)
    , m_precinct_count(move(precinct_count))
    , m_generator(generator())

{
}

bool LayerResolutionLevelComponentPositionProgressionIterator::has_next() const
{
    return m_generator.has_next();
}

ProgressionData LayerResolutionLevelComponentPositionProgressionIterator::next()
{
    return m_generator.next();
}

SyncGenerator<ProgressionData> LayerResolutionLevelComponentPositionProgressionIterator::generator()
{
    // B.12.1.1 Layer-resolution level-component-position progression
    // "for each l = 0,..., L – 1
    //      for each r = 0,..., Nmax
    //          for each i = 0,..., Csiz – 1
    //              for each k = 0,..., numprecincts – 1
    //                  packet for component i, resolution level r, layer l, and precinct k.
    //  Here, L is the number of layers and Nmax is the maximum number of decomposition levels, N_L, used in any component of the tile."
    // FIXME: This always iterates up to Nmax, instead of just N_l of each component. That means several of the iteration results will be invalid and skipped.
    // (This is a performance issue, not a correctness issue.)

    for (int l = 0; l < m_layer_count; ++l)
        for (int r = 0; r <= m_max_number_of_decomposition_levels; ++r)
            for (int i = 0; i < m_component_count; ++i)
                for (int k = 0; k < m_precinct_count(r, i); ++k)
                    co_yield ProgressionData { l, r, i, k };
}

ResolutionLevelLayerComponentPositionProgressionIterator::ResolutionLevelLayerComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function<int(int resolution_level, int component)> precinct_count)
    : m_layer_count(layer_count)
    , m_max_number_of_decomposition_levels(max_number_of_decomposition_levels)
    , m_component_count(component_count)
    , m_precinct_count(move(precinct_count))
    , m_generator(generator())
{
}

bool ResolutionLevelLayerComponentPositionProgressionIterator::has_next() const
{
    return m_generator.has_next();
}

ProgressionData ResolutionLevelLayerComponentPositionProgressionIterator::next()
{
    return m_generator.next();
}

SyncGenerator<ProgressionData> ResolutionLevelLayerComponentPositionProgressionIterator::generator()
{
    // B.12.1.2 Resolution level-layer-component-position progression
    // "for each r = 0,..., Nmax
    //      for each l = 0,..., L – 1
    //          for each i = 0,..., Csiz – 1
    //              for each k = 0,..., numprecincts – 1
    //                  packet for component i, resolution level r, layer l, and precinct k."
    // FIXME: This always iterates up to Nmax, instead of just N_l of each component. That means several of the iteration results will be invalid and skipped.
    // (This is a performance issue, not a correctness issue.)

    for (int r = 0; r <= m_max_number_of_decomposition_levels; ++r)
        for (int l = 0; l < m_layer_count; ++l)
            for (int i = 0; i < m_component_count; ++i)
                for (int k = 0; k < m_precinct_count(r, i); ++k)
                    co_yield ProgressionData { l, r, i, k };
}

ResolutionLevelPositionComponentLayerProgressionIterator::ResolutionLevelPositionComponentLayerProgressionIterator(
    int layer_count, int max_number_of_decomposition_levels, int component_count, Function<int(int resolution_level, int component)> precinct_count,
    Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
    Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
    Function<int(int component)> N_L,
    Function<int(int resolution_level, int component)> num_precincts_wide,
    Gfx::IntRect tile_rect,
    Function<IntRect(int resolution_level, int component)> ll_rect)
    : m_layer_count(layer_count)
    , m_max_number_of_decomposition_levels(max_number_of_decomposition_levels)
    , m_component_count(component_count)
    , m_precinct_count(move(precinct_count))
    , m_XRsiz(move(XRsiz))
    , m_YRsiz(move(YRsiz))
    , m_PPx(move(PPx))
    , m_PPy(move(PPy))
    , m_N_L(move(N_L))
    , m_num_precincts_wide(move(num_precincts_wide))
    , m_tile_rect(tile_rect)
    , m_ll_rect(move(ll_rect))
    , m_generator(generator())
{
}

bool ResolutionLevelPositionComponentLayerProgressionIterator::has_next() const
{
    return m_generator.has_next();
}

ProgressionData ResolutionLevelPositionComponentLayerProgressionIterator::next()
{
    return m_generator.next();
}

SyncGenerator<ProgressionData> ResolutionLevelPositionComponentLayerProgressionIterator::generator()
{
    auto compute_precinct = [&](int x, int y, int r, int i) {
        // (B-20)
        auto const trx0 = m_ll_rect(r, i).left();
        auto const try0 = m_ll_rect(r, i).top();
        auto const x_offset = floor_div(ceil_div(x, m_XRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPx(r, i)) - floor_div(trx0, 1 << m_PPx(r, i));
        auto const y_offset = floor_div(ceil_div(y, m_YRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPy(r, i)) - floor_div(try0, 1 << m_PPy(r, i));
        return x_offset + m_num_precincts_wide(r, i) * y_offset;
    };
    // B.12.1.3 Resolution level-position-component-layer progression
    // "for each r = 0,..., Nmax
    //      for each y = ty0,..., ty1 – 1,
    //          for each x = tx0,..., tx1 – 1,
    //              for each i = 0,..., Csiz – 1
    //                  if ((y divisible by YRsiz(i) * 2 ** (PPy(r, i) + N_L(i) - r) OR
    //                      ((y == ty0) AND (try0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPy(r, i) + N_L(i) - r))))
    //                  if ((x divisible by XRsiz(i) * 2 ** (PPx(r, i) + N_L(i) - r) OR
    //                      ((x == tx0) AND (trx0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPx(r, i) + N_L(i) - r))))
    //          for the next precinct, k, if one exists,
    //              for each l = 0,..., L – 1
    //                  packet for component i, resolution level r, layer l, and precinct k."
    // The motivation for this loop is to walk corresponding precincts in different components at the same time,
    // even if the components have different precinct counts.
    for (int r = 0; r <= m_max_number_of_decomposition_levels; ++r) {
        auto const tx0 = m_tile_rect.left();
        auto const ty0 = m_tile_rect.top();
        for (int y = ty0; y < m_tile_rect.bottom(); ++y) {
            for (int x = tx0; x < m_tile_rect.right(); ++x) {
                for (int i = 0; i < m_component_count; ++i) {
                    auto const trx0 = m_ll_rect(r, i).left();
                    auto const try0 = m_ll_rect(r, i).top();
                    if ((y % (m_YRsiz(i) * (1 << (m_PPy(r, i) + m_N_L(i) - r))) == 0)
                        || ((y == ty0) && (try0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPy(r, i) + m_N_L(i) - r)) != 0))) {
                        if ((x % (m_XRsiz(i) * (1 << (m_PPx(r, i) + m_N_L(i) - r))) == 0)
                            || ((x == tx0) && (trx0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPx(r, i) + m_N_L(i) - r)) != 0))) {
                            if (int k = compute_precinct(x, y, r, i); k < m_precinct_count(r, i)) {
                                for (int l = 0; l < m_layer_count; ++l) {
                                    co_yield ProgressionData { l, r, i, k };
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

PositionComponentResolutionLevelLayerProgressionIterator::PositionComponentResolutionLevelLayerProgressionIterator(
    int layer_count, int component_count, Function<int(int resolution_level, int component)> precinct_count,
    Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
    Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
    Function<int(int component)> N_L,
    Function<int(int resolution_level, int component)> num_precincts_wide,
    Gfx::IntRect tile_rect,
    Function<IntRect(int resolution_level, int component)> ll_rect)
    : m_layer_count(layer_count)
    , m_component_count(component_count)
    , m_precinct_count(move(precinct_count))
    , m_XRsiz(move(XRsiz))
    , m_YRsiz(move(YRsiz))
    , m_PPx(move(PPx))
    , m_PPy(move(PPy))
    , m_N_L(move(N_L))
    , m_num_precincts_wide(move(num_precincts_wide))
    , m_tile_rect(tile_rect)
    , m_ll_rect(move(ll_rect))
    , m_generator(generator())
{
}

bool PositionComponentResolutionLevelLayerProgressionIterator::has_next() const
{
    return m_generator.has_next();
}

ProgressionData PositionComponentResolutionLevelLayerProgressionIterator::next()
{
    return m_generator.next();
}

SyncGenerator<ProgressionData> PositionComponentResolutionLevelLayerProgressionIterator::generator()
{
    auto compute_precinct = [&](int x, int y, int r, int i) {
        // (B-20)
        auto const trx0 = m_ll_rect(r, i).left();
        auto const try0 = m_ll_rect(r, i).top();
        auto const x_offset = floor_div(ceil_div(x, m_XRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPx(r, i)) - floor_div(trx0, 1 << m_PPx(r, i));
        auto const y_offset = floor_div(ceil_div(y, m_YRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPy(r, i)) - floor_div(try0, 1 << m_PPy(r, i));
        return x_offset + m_num_precincts_wide(r, i) * y_offset;
    };
    // B.12.1.4 Position-component-resolution level-layer progression
    // "for each y = ty0,..., ty1 – 1,
    //      for each x = tx0,..., tx1 – 1,
    //          for each i = 0,..., Csiz – 1
    //              for each r = 0,..., NL where NL is the number of decomposition levels for component i,
    //                  if ((y divisible by YRsiz(i) * 2 ** (PPy(r, i) + N_L(i) - r) OR
    //                      ((y == ty0) AND (try0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPy(r, i) + N_L(i) - r))))
    //                      if ((x divisible by XRsiz(i) * 2 ** (PPx(r, i) + N_L(i) - r) OR
    //                          ((x == tx0) AND (trx0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPx(r, i) + N_L(i) - r))))
    //                              for the next precinct, k, if one exists, in the sequence shown in Figure B.8
    //                                  for each l = 0,..., L – 1
    //                                      packet for component i, resolution level r, layer l, and precinct k."
    // The motivation for this loop is to walk corresponding precincts in different components and resolution levels at the same time,
    // even if the components or resolution levels have different precinct counts.
    auto const tx0 = m_tile_rect.left();
    auto const ty0 = m_tile_rect.top();
    for (int y = ty0; y < m_tile_rect.bottom(); ++y) {
        for (int x = tx0; x < m_tile_rect.right(); ++x) {
            for (int i = 0; i < m_component_count; ++i) {
                for (int r = 0; r <= m_N_L(i); ++r) {
                    auto const trx0 = m_ll_rect(r, i).left();
                    auto const try0 = m_ll_rect(r, i).top();
                    if ((y % (m_YRsiz(i) * (1 << (m_PPy(r, i) + m_N_L(i) - r))) == 0)
                        || ((y == ty0) && (try0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPy(r, i) + m_N_L(i) - r)) != 0))) {
                        if ((x % (m_XRsiz(i) * (1 << (m_PPx(r, i) + m_N_L(i) - r))) == 0)
                            || ((x == tx0) && (trx0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPx(r, i) + m_N_L(i) - r)) != 0))) {
                            if (int k = compute_precinct(x, y, r, i); k < m_precinct_count(r, i)) {
                                for (int l = 0; l < m_layer_count; ++l) {
                                    co_yield ProgressionData { l, r, i, k };
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

ComponentPositionResolutionLevelLayerProgressionIterator::ComponentPositionResolutionLevelLayerProgressionIterator(
    int layer_count, int component_count, Function<int(int resolution_level, int component)> precinct_count,
    Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
    Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
    Function<int(int component)> N_L,
    Function<int(int resolution_level, int component)> num_precincts_wide,
    Gfx::IntRect tile_rect,
    Function<IntRect(int resolution_level, int component)> ll_rect)
    : m_layer_count(layer_count)
    , m_component_count(component_count)
    , m_precinct_count(move(precinct_count))
    , m_XRsiz(move(XRsiz))
    , m_YRsiz(move(YRsiz))
    , m_PPx(move(PPx))
    , m_PPy(move(PPy))
    , m_N_L(move(N_L))
    , m_num_precincts_wide(move(num_precincts_wide))
    , m_tile_rect(tile_rect)
    , m_ll_rect(move(ll_rect))
    , m_generator(generator())
{
}

bool ComponentPositionResolutionLevelLayerProgressionIterator::has_next() const
{
    return m_generator.has_next();
}

ProgressionData ComponentPositionResolutionLevelLayerProgressionIterator::next()
{
    return m_generator.next();
}

SyncGenerator<ProgressionData> ComponentPositionResolutionLevelLayerProgressionIterator::generator()
{
    auto compute_precinct = [&](int x, int y, int r, int i) {
        // (B-20)
        auto const trx0 = m_ll_rect(r, i).left();
        auto const try0 = m_ll_rect(r, i).top();
        auto const x_offset = floor_div(ceil_div(x, m_XRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPx(r, i)) - floor_div(trx0, 1 << m_PPx(r, i));
        auto const y_offset = floor_div(ceil_div(y, m_YRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPy(r, i)) - floor_div(try0, 1 << m_PPy(r, i));
        return x_offset + m_num_precincts_wide(r, i) * y_offset;
    };
    // B.12.1.5 Component-position-resolution level-layer progression
    // "for each i = 0,..., Csiz – 1
    //      for each y = ty0,..., ty1 – 1,
    //          for each x = tx0,..., tx1 – 1,
    //              for each r = 0,..., NL where NL is the number of decomposition levels for component i,
    //                  if ((y divisible by YRsiz(i) * 2 ** (PPy(r, i) + N_L(i) - r) OR
    //                      ((y == ty0) AND (try0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPy(r, i) + N_L(i) - r))))
    //                      if ((x divisible by XRsiz(i) * 2 ** (PPx(r, i) + N_L(i) - r) OR
    //                          ((x == tx0) AND (trx0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPx(r, i) + N_L(i) - r))))
    //                              for the next precinct, k, if one exists, in the sequence shown in Figure B.8
    //                                  for each l = 0,..., L – 1
    //                                      packet for component i, resolution level r, layer l, and precinct k."
    // The motivation for this loop is to walk corresponding precincts in different resolution levels at the same time,
    // even if the resolution levels have different precinct counts.
    auto const tx0 = m_tile_rect.left();
    auto const ty0 = m_tile_rect.top();
    for (int i = 0; i < m_component_count; ++i) {
        for (int y = ty0; y < m_tile_rect.bottom(); ++y) {
            for (int x = tx0; x < m_tile_rect.right(); ++x) {
                for (int r = 0; r <= m_N_L(i); ++r) {
                    auto const trx0 = m_ll_rect(r, i).left();
                    auto const try0 = m_ll_rect(r, i).top();
                    if ((y % (m_YRsiz(i) * (1 << (m_PPy(r, i) + m_N_L(i) - r))) == 0)
                        || ((y == ty0) && (try0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPy(r, i) + m_N_L(i) - r)) != 0))) {
                        if ((x % (m_XRsiz(i) * (1 << (m_PPx(r, i) + m_N_L(i) - r))) == 0)
                            || ((x == tx0) && (trx0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPx(r, i) + m_N_L(i) - r)) != 0))) {
                            if (int k = compute_precinct(x, y, r, i); k < m_precinct_count(r, i)) {
                                for (int l = 0; l < m_layer_count; ++l) {
                                    co_yield ProgressionData { l, r, i, k };
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

}
