#pragma once

#include <mapbox/geometry/line_string.hpp>
#include <mapbox/geometry/point.hpp>
#include <mapbox/geometry/polygon.hpp>

#include <mapbox/geometry/wagyu/config.hpp>
#include <mapbox/geometry/wagyu/edge.hpp>
#include <mapbox/geometry/wagyu/local_minimum.hpp>
#include <mapbox/geometry/wagyu/util.hpp>

namespace mapbox {
namespace geometry {
namespace wagyu {

template <typename T>
active_bound_list_itr<T> process_horizontal_left_to_right(T scanline_y,
                                                          active_bound_list_itr<T>& horz_bound,
                                                          active_bound_list<T>& active_bounds,
                                                          ring_manager<T>& rings,
                                                          scanbeam_list<T>& scanbeam,
                                                          clip_type cliptype,
                                                          fill_type subject_fill_type,
                                                          fill_type clip_fill_type) {
    auto horizontal_itr_behind = horz_bound;
    bool shifted = false;
    bool is_maxima_edge = is_maxima(horz_bound, scanline_y);
    auto bound_max_pair = active_bounds.end();
    if (is_maxima_edge) {
        bound_max_pair = get_maxima_pair<T>(horz_bound, active_bounds);
    }

    auto hp_itr = rings.current_hp_itr;
    while (hp_itr != rings.hot_pixels.end() &&
           (hp_itr->y > scanline_y || (hp_itr->y == scanline_y && hp_itr->x < (*horz_bound)->current_edge->bot.x))) {
        ++hp_itr;
    }

    auto bnd = std::next(horz_bound);

    while (bnd != active_bounds.end()) {
        if (*bnd == nullptr) {
            ++bnd;
            continue;
        }
        // this code block inserts extra coords into horizontal edges (in output
        // polygons) wherever hot pixels touch these horizontal edges. This helps
        //'simplifying' polygons (ie if the Simplify property is set).
        while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y &&
               hp_itr->x < wround<T>((*bnd)->current_x) && hp_itr->x < (*horz_bound)->current_edge->top.x) {
            if ((*horz_bound)->ring) {
                add_point_to_ring(*(*horz_bound), *hp_itr, rings);
            }
            ++hp_itr;
        }

        if (greater_than((*bnd)->current_x, static_cast<double>((*horz_bound)->current_edge->top.x))) {
            break;
        }

        // Also break if we've got to the end of an intermediate horizontal edge ...
        // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
        if (wround<T>((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
            (*horz_bound)->next_edge != (*horz_bound)->edges.end() &&
            (*horz_bound)->current_edge->dx < (*horz_bound)->next_edge->dx) {
            break;
        }

        // note: may be done multiple times
        if ((*horz_bound)->ring) {
            add_point_to_ring(*(*horz_bound), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
                              rings);
        }

        // OK, so far we're still in range of the horizontal Edge  but make sure
        // we're at the last of consec. horizontals when matching with eMaxPair
        if (is_maxima_edge && bnd == bound_max_pair) {
            if ((*horz_bound)->ring && (*bound_max_pair)->ring) {
                add_local_maximum_point(*(*horz_bound), *(*bound_max_pair), (*horz_bound)->current_edge->top, rings,
                                        active_bounds);
            }
            *bound_max_pair = nullptr;
            *horz_bound = nullptr;
            if (!shifted) {
                ++horizontal_itr_behind;
            }
            return horizontal_itr_behind;
        }

        intersect_bounds(*(*horz_bound), *(*bnd), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
                         cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
        std::iter_swap(horz_bound, bnd);
        horz_bound = bnd;
        ++bnd;
        shifted = true;
    } // end while (bnd != active_bounds.end())

    if ((*horz_bound)->ring) {
        while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y &&
               hp_itr->x < (*horz_bound)->current_edge->top.x) {
            add_point_to_ring(*(*horz_bound), *hp_itr, rings);
            ++hp_itr;
        }
    }

    if ((*horz_bound)->ring) {
        add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
    }

    if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
        next_edge_in_bound(*(*horz_bound), scanbeam);
    } else {
        *horz_bound = nullptr;
    }
    if (!shifted) {
        ++horizontal_itr_behind;
    }
    return horizontal_itr_behind;
}

template <typename T>
active_bound_list_itr<T> process_horizontal_right_to_left(T scanline_y,
                                                          active_bound_list_itr<T>& horz_bound_fwd,
                                                          active_bound_list<T>& active_bounds,
                                                          ring_manager<T>& rings,
                                                          scanbeam_list<T>& scanbeam,
                                                          clip_type cliptype,
                                                          fill_type subject_fill_type,
                                                          fill_type clip_fill_type) {
    auto next_bnd_itr = std::next(horz_bound_fwd);
    bool is_maxima_edge = is_maxima(horz_bound_fwd, scanline_y);
    auto bound_max_pair = active_bounds.rend();
    if (is_maxima_edge) {
        bound_max_pair = active_bound_list_rev_itr<T>(get_maxima_pair<T>(horz_bound_fwd, active_bounds));
        --bound_max_pair;
    }
    auto hp_itr_fwd = rings.current_hp_itr;
    while (hp_itr_fwd != rings.hot_pixels.end() &&
           (hp_itr_fwd->y < scanline_y ||
            (hp_itr_fwd->y == scanline_y && hp_itr_fwd->x < (*horz_bound_fwd)->current_edge->top.x))) {
        ++hp_itr_fwd;
    }
    auto hp_itr = hot_pixel_rev_itr<T>(hp_itr_fwd);

    auto bnd = active_bound_list_rev_itr<T>(horz_bound_fwd);
    auto horz_bound = std::prev(bnd);
    while (bnd != active_bounds.rend()) {
        if (*bnd == nullptr) {
            ++bnd;
            continue;
        }
        // this code block inserts extra coords into horizontal edges (in output
        // polygons) wherever hot pixels touch these horizontal edges.
        while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y &&
               hp_itr->x > wround<T>((*bnd)->current_x) && hp_itr->x > (*horz_bound)->current_edge->top.x) {
            if ((*horz_bound)->ring) {
                add_point_to_ring(*(*horz_bound), *hp_itr, rings);
            }
            ++hp_itr;
        }

        if (less_than((*bnd)->current_x, static_cast<double>((*horz_bound)->current_edge->top.x))) {
            break;
        }

        // Also break if we've got to the end of an intermediate horizontal edge ...
        // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
        if (wround<T>((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
            (*horz_bound)->next_edge != (*horz_bound)->edges.end() &&
            (*horz_bound)->current_edge->dx < (*horz_bound)->next_edge->dx) {
            break;
        }

        // note: may be done multiple times
        if ((*horz_bound)->ring) {
            add_point_to_ring(*(*horz_bound), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
                              rings);
        }

        // OK, so far we're still in range of the horizontal Edge  but make sure
        // we're at the last of consec. horizontals when matching with eMaxPair
        if (is_maxima_edge && bnd == bound_max_pair) {
            if ((*horz_bound)->ring && (*bound_max_pair)->ring) {
                add_local_maximum_point(*(*horz_bound), *(*bound_max_pair), (*horz_bound)->current_edge->top, rings,
                                        active_bounds);
            }
            *bound_max_pair = nullptr;
            *horz_bound = nullptr;
            return next_bnd_itr;
        }

        intersect_bounds(*(*bnd), *(*horz_bound), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
                         cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
        std::iter_swap(horz_bound, bnd);
        horz_bound = bnd;
        ++bnd;
    } // end while (bnd != active_bounds.rend())

    if ((*horz_bound)->ring) {
        while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y &&
               hp_itr->x > (*horz_bound)->current_edge->top.x) {
            add_point_to_ring(*(*horz_bound), *hp_itr, rings);
            ++hp_itr;
        }
    }
    if ((*horz_bound)->ring) {
        add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
    }

    if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
        next_edge_in_bound(*(*horz_bound), scanbeam);
    } else {
        *horz_bound = nullptr;
    }
    return next_bnd_itr;
}

template <typename T>
active_bound_list_itr<T> process_horizontal(T scanline_y,
                                            active_bound_list_itr<T>& horz_bound,
                                            active_bound_list<T>& active_bounds,
                                            ring_manager<T>& rings,
                                            scanbeam_list<T>& scanbeam,
                                            clip_type cliptype,
                                            fill_type subject_fill_type,
                                            fill_type clip_fill_type) {
    if ((*horz_bound)->current_edge->bot.x < (*horz_bound)->current_edge->top.x) {
        return process_horizontal_left_to_right(scanline_y, horz_bound, active_bounds, rings, scanbeam, cliptype,
                                                subject_fill_type, clip_fill_type);
    } else {
        return process_horizontal_right_to_left(scanline_y, horz_bound, active_bounds, rings, scanbeam, cliptype,
                                                subject_fill_type, clip_fill_type);
    }
}

template <typename T>
void process_horizontals(T scanline_y,
                         active_bound_list<T>& active_bounds,
                         ring_manager<T>& rings,
                         scanbeam_list<T>& scanbeam,
                         clip_type cliptype,
                         fill_type subject_fill_type,
                         fill_type clip_fill_type) {
    for (auto bnd_itr = active_bounds.begin(); bnd_itr != active_bounds.end();) {
        if (*bnd_itr != nullptr && current_edge_is_horizontal<T>(bnd_itr)) {
            bnd_itr = process_horizontal(scanline_y, bnd_itr, active_bounds, rings, scanbeam, cliptype,
                                         subject_fill_type, clip_fill_type);
        } else {
            ++bnd_itr;
        }
    }
    active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr), active_bounds.end());
}
} // namespace wagyu
} // namespace geometry
} // namespace mapbox
