
#include "coloquinte/rough_legalizers.hxx"
#include "coloquinte/optimization_subproblems.hxx"

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cassert>
#include <map>

namespace coloquinte{
namespace gp{

  using std::cerr;
  using std::endl;

namespace{
    const capacity_t default_density_mul = 256;
}

void region_distribution::just_uniquify(std::vector<cell_ref> & cell_references){
    if(cell_references.size() >= 1){
        index_t j=0;
        cell_ref prev_cell = cell_references[0];
        for(auto it = cell_references.begin()+1; it != cell_references.end(); ++it){
            if(it->index_in_list_ == prev_cell.index_in_list_){
                prev_cell.allocated_capacity_ += it->allocated_capacity_;
            }
            else{
                cell_references[j] = prev_cell;
                ++j;
                prev_cell = *it;
            }
        }
        cell_references[j]=prev_cell;
        cell_references.resize(j+1);
    }
}

void region_distribution::sort_uniquify(std::vector<cell_ref> & cell_references){
    std::sort(cell_references.begin(), cell_references.end(), [](cell_ref a, cell_ref b){ return a.index_in_list_ < b.index_in_list_; });
    just_uniquify(cell_references);
}

void region_distribution::region::uniquify_references(){
    sort_uniquify(cell_references_);
}

void region_distribution::fractions_minimization(){
    for(region & R : placement_regions_){
        R.uniquify_references();
    }

    // Find cycles of cut cells, then find a spanning tree to reallocate the cells
    // TODO
}

void region_distribution::region::selfcheck() const{
    capacity_t total_allocated = 0;
    for(cell_ref const c : cell_references_){
        total_allocated += c.allocated_capacity_;
        assert(c.allocated_capacity_ > 0);
    }
    assert(total_allocated <= capacity_);
}

void region_distribution::selfcheck() const{
    for(region const & R : placement_regions_){
        R.selfcheck();
    }
    std::vector<capacity_t> capacities(cell_list_.size(), 0);
    for(region const & R : placement_regions_){
        for(cell_ref const C : R.cell_references_){
            capacities[C.index_in_list_] += C.allocated_capacity_;
        }
    }
    for(index_t i=0; i < cell_list_.size(); ++i){
        assert(capacities[i] == cell_list_[i].demand_ * cell_density_mul);
    }
}

region_distribution::region::region(capacity_t cap, point<float_t> pos, std::vector<cell_ref> cells) : capacity_(cap), pos_(pos), cell_references_(cells){}

box<int_t> region_distribution::get_box(index_t x, index_t y, index_t x_cnt, index_t y_cnt) const{
    auto ret = box<int_t>(
        placement_area_.x_min + ( ((std::int64_t) (placement_area_.x_max - placement_area_.x_min)) * x )     / x_cnt,
        placement_area_.x_min + ( ((std::int64_t) (placement_area_.x_max - placement_area_.x_min)) * (x+1) ) / x_cnt,
        placement_area_.y_min + ( ((std::int64_t) (placement_area_.y_max - placement_area_.y_min)) * y )     / y_cnt,
        placement_area_.y_min + ( ((std::int64_t) (placement_area_.y_max - placement_area_.y_min)) * (y+1) ) / y_cnt
    );
  //assert(not ret.empty());
    return ret;
}

std::vector<region_distribution::region> region_distribution::prepare_regions(index_t x_cnt, index_t y_cnt) const{
    assert(placement_area_.x_max > placement_area_.x_min);
    assert(placement_area_.y_max > placement_area_.y_min);

    // Uses a sweepline algorithm to initialize all regions' capacities at a time, taking macros and density maps into account

    // The events in the priority queue: basically a density_limit object, but the y_min may be different from the original one
    struct event{
        box<int_t> box_;
        capacity_t multiplicator_;
        bool operator<(event const o) const{
            return box_.y_min > o.box_.y_min // Priority queue = highest first
            or (box_.y_min == o.box_.y_min and multiplicator_ > o.multiplicator_); // Smallest density first, just because
        }
        event(box<int_t> surface, capacity_t den) : box_(surface), multiplicator_(den) {}
        event(density_limit D, capacity_t density_mul) : box_(D.box_) {
            multiplicator_ = static_cast<capacity_t>(std::ceil(D.density_ * static_cast<float_t>(density_mul)));
        }
    };

    struct line_y{
        int_t y_min, y_max;
        capacity_t multiplicator_;
        line_y(int_t mn, int_t mx, capacity_t cap) : y_min(mn), y_max(mx), multiplicator_(cap) {}
    };

    // The regions' capacities
    std::vector<capacity_t> region_caps(x_cnt * y_cnt, 0);

    // Find the limits of the regions
    std::vector<int_t> x_reg_lims(x_cnt+1), y_reg_lims(y_cnt+1);
    for(index_t i=0; i<=x_cnt; ++i){
        x_reg_lims[i] = placement_area_.x_min + ( ((std::int64_t) (placement_area_.x_max - placement_area_.x_min)) * i ) / x_cnt;
    }
    for(index_t i=0; i<=y_cnt; ++i){
        y_reg_lims[i] = placement_area_.y_min + ( ((std::int64_t) (placement_area_.y_max - placement_area_.y_min)) * i ) / y_cnt;
    }

    //std::vector<box<int_t> > added;

    auto add_region = [&](box<int_t> bx, capacity_t d){
        /*
        // Failed attempt at calculating the coordinates directly
        point<int_t> dims = placement_area_.dimensions();
        auto mins = point<int_t>(placement_area_.x_min, placement_area_.y_min);

        index_t x_mn = (static_cast<std::int64_t>(bx.x_min - mins.x + 1) * x_cnt) / dims.x,
                x_mx = (static_cast<std::int64_t>(bx.x_max - mins.x - 1) * x_cnt) / dims.x + 1,
                y_mn = (static_cast<std::int64_t>(bx.y_min - mins.y + 1) * y_cnt) / dims.y,
                y_mx = (static_cast<std::int64_t>(bx.y_max - mins.y - 1) * y_cnt) / dims.y + 1;
        */

        /*
        // Additional debugging
        for(index_t k=0; k<added.size(); ++k){
            assert(not bx.intersects(added[k]));
        }
        added.push_back(bx);
        */
    
        assert(bx.x_min >= placement_area_.x_min);
        assert(bx.y_min >= placement_area_.y_min);
        assert(bx.x_max <= placement_area_.x_max);
        assert(bx.y_max <= placement_area_.y_max);

        index_t x_mn = std::upper_bound(x_reg_lims.begin(), x_reg_lims.end(), bx.x_min) - x_reg_lims.begin() -1,
                y_mn = std::upper_bound(y_reg_lims.begin(), y_reg_lims.end(), bx.y_min) - y_reg_lims.begin() -1,
                x_mx = std::lower_bound(x_reg_lims.begin(), x_reg_lims.end(), bx.x_max) - x_reg_lims.begin(),
                y_mx = std::lower_bound(y_reg_lims.begin(), y_reg_lims.end(), bx.y_max) - y_reg_lims.begin();

        for(index_t x=x_mn; x<x_mx; ++x){
            for(index_t y=y_mn; y<y_mx; ++y){
                box<int_t> cur_box = get_box(x, y, x_cnt, y_cnt);
                assert(bx.intersects(cur_box));
                box<int_t> inter = bx.intersection(cur_box);
                point<int_t> dims = inter.dimensions();
                region_caps[y*x_cnt + x] += d * static_cast<capacity_t>(dims.x) * static_cast<capacity_t>(dims.y);
            }
        }
    };

    // All rectangles and new rectangles are pushed there
    std::priority_queue<event> events;

    for(density_limit D : density_map_){
        // Keep only the useful parts of the rectangles to simplify the algorithm
        if(D.box_.intersects(placement_area_)){
            density_limit pushed;
            pushed.density_ = D.density_;
            pushed.box_ = D.box_.intersection(placement_area_);
            assert(not pushed.box_.empty()); // always true with this definition of intersects
            events.push(event(pushed, full_density_mul));
        }
    }

    // The initial sweepline, with begin and end of the line
    std::map<int_t, line_y> active_obstacles;

    line_y placement_begin (placement_area_.y_min, placement_area_.y_max, full_density_mul),
           placement_end   (placement_area_.y_min, placement_area_.y_max, 0);

    active_obstacles.insert(std::pair<int_t, line_y>(placement_area_.x_min, placement_begin)); // Full density placement area as initial object
    active_obstacles.insert(std::pair<int_t, line_y>(placement_area_.x_max, placement_end));


    // Main loop: sweep the line on y (the line is horizontal, and moves toward bigger y)
    while(not events.empty()){
        event D = events.top();
        int_t x_b=D.box_.x_min, x_e=D.box_.x_max;
        int_t y_b=D.box_.y_min, y_e=D.box_.y_max;
        events.pop();

        assert(x_b >= placement_area_.x_min);
        assert(y_b >= placement_area_.y_min);
        assert(x_e <= placement_area_.x_max);
        assert(y_e <= placement_area_.y_max);

        // For each delimitation between the bounds of the new rectangle
        //      If the new delimitation has higher density or this delimitation ends on y there
        //          Remove it, push a new event if it ends on y after this rectangle
        //      Else:
        //          Keep it, push a new event if it ends on y before this rectangle

        // Find covered or partially covered rectangles on the line (rectangles on the line don't overlap)
        line_y const new_elt(y_b, y_e, D.multiplicator_);
        std::vector<line_y> new_delimitations;

        // First element on the line whose x is after our x_min (i.e. may have an intersection), and while there is an intersection
        auto first_it = active_obstacles.upper_bound(x_b);
        assert(first_it != active_obstacles.begin());
        assert(std::prev(first_it)->first <= x_b);

        bool currently_in = false;
        for(auto it = std::prev(first_it); it != active_obstacles.end() and it->first < x_e;){
            auto next_it = std::next(it);
            assert(next_it != active_obstacles.end());
            assert(it->second.y_min <= y_b);
            assert(it->second.y_max >= y_b);
            assert(it->first < x_e);
            assert(next_it->first > x_b);
            assert(next_it-> first > it->first);

            int_t x_c_min = std::max(x_b, it->first),
                  x_c_max = std::min(x_e, next_it->first);
            assert(x_c_min < x_c_max);
            // Add the area from it->second.y_min to D.box_.y_min
            if(y_b > it->second.y_min)
                add_region(box<int_t>(it->first, next_it->first, it->second.y_min, y_b), it->second.multiplicator_);
            it->second.y_min = y_b; // Now the part before has been used

            auto part_b = *it, part_e = *next_it;

            if(part_b.second.multiplicator_ > D.multiplicator_ or part_b.second.y_max == y_b){ // The new event is visible now

                // In case parts of the line become visible again later
                if(part_b.second.y_max > y_e){ // Push back a new event to account for the comeback
                    events.push(event(box<int_t>(x_c_min, x_c_max, y_e, part_b.second.y_max), part_b.second.multiplicator_));
                }

                // Depending whether this part of the line is or is not fully covered
                if(part_b.first >= x_b){ // If covered at the beginning
                    active_obstacles.erase(it);
                }
                if(part_e.first > x_e){ // If not covered at the end
                    auto inserted = active_obstacles.insert(std::pair<int_t, line_y>(x_e, part_b.second)); 
                    assert(inserted.second);
                }

                // The events becomes visible on the line
                if(not currently_in){
                    auto inserted = active_obstacles.insert(std::pair<int_t, line_y>(x_c_min, new_elt));
                    assert(inserted.second);
                }
                currently_in = true;
            }
            else{ // The new event is not visible yet
                currently_in = false;
                if(part_b.second.y_max < y_e){ // Push back a new event
                    events.push(event(box<int_t>(x_c_min, x_c_max, part_b.second.y_max, y_e), D.multiplicator_));
                }
            }

            it = next_it;
        }
    }
    for(auto it=active_obstacles.begin(); std::next(it) != active_obstacles.end(); ++it){
        assert(it->second.y_max == placement_area_.y_max);
        add_region(box<int_t>(it->first, std::next(it)->first, it->second.y_min, it->second.y_max), it->second.multiplicator_); 
    }

    std::vector<region> ret(x_cnt*y_cnt);
    for(index_t y=0; y<y_cnt; ++y){
        for(index_t x=0; x<x_cnt; ++x){
            box<int_t> bx = get_box(x, y, x_cnt, y_cnt);
            ret[y*x_cnt + x] = region(
                region_caps[y*x_cnt + x],
                point<float_t>(0.5f * bx.x_min + 0.5f * bx.x_max, 0.5f * bx.y_min + 0.5f * bx.y_max),
                std::vector<cell_ref>()
            );
        }
    }
    return ret;
}

void region_distribution::x_bipartition(){
    std::vector<region> old_placement_regions = prepare_regions(2*x_regions_cnt(), y_regions_cnt());
    placement_regions_.swap(old_placement_regions);

    index_t old_x_regions_cnt = x_regions_cnt();
    index_t old_y_regions_cnt = y_regions_cnt();
    x_regions_cnt_ *= 2;

    for(index_t x=0; x < old_x_regions_cnt; ++x){
        for(index_t y=0; y < old_y_regions_cnt; ++y){
            index_t i = y * old_x_regions_cnt + x;
            old_placement_regions[i].distribute_cells(get_region(2*x, y), get_region(2*x+1, y));
        }
    }
}

void region_distribution::y_bipartition(){
    std::vector<region> old_placement_regions = prepare_regions(x_regions_cnt(), 2*y_regions_cnt());
    placement_regions_.swap(old_placement_regions);

    index_t old_x_regions_cnt = x_regions_cnt();
    index_t old_y_regions_cnt = y_regions_cnt();
    y_regions_cnt_ *= 2;

    for(index_t x=0; x < old_x_regions_cnt; ++x){
        for(index_t y=0; y < old_y_regions_cnt; ++y){
            index_t i = y * old_x_regions_cnt + x;
            old_placement_regions[i].distribute_cells(get_region(x, 2*y), get_region(x, 2*y+1));
        }
    }
}

// The big awful function that handles optimal cell distribution between two regions; not meant to be called externally
void region_distribution::region::distribute_new_cells(region & region_a, region & region_b, std::vector<cell_ref> basic_cells){
    struct cost_diff_cell : cell_ref{
        float_t marginal_cost_;

        bool operator<(cost_diff_cell const o) const{ return marginal_cost_ < o.marginal_cost_; }
        cost_diff_cell(cell_ref cell, float_t cost) : cell_ref(cell), marginal_cost_(cost) {}
    };

    std::vector<cost_diff_cell> cells;
    for(cell_ref const c : basic_cells){
        cells.push_back(cost_diff_cell(c, region_a.distance(c) - region_b.distance(c)));
    }

    // Cells trending toward a first
    std::sort(cells.begin(), cells.end());//, [](cell_ref const c1, cell_ref const c2) -> bool{ return c1.marginal_cost_ < c2.marginal_cost_; });

    index_t preference_limit=0,         // First cell that would rather go to b (or cells.size())
         a_capacity_limit=0,            // After the last cell that region_a can take entirely (or 0)
         b_capacity_limit=cells.size(); // Last cell (but first in the vector) that region_b can take entirely (or cells.size())

    capacity_t remaining_capacity_a = region_a.capacity_, remaining_capacity_b = region_b.capacity_;
    for(;preference_limit < cells.size() && cells[preference_limit].marginal_cost_ <= 0.0; ++preference_limit)
      ;

    { // Block
    capacity_t remaining_cap_a = region_a.capacity_;
    index_t i=0;
    while(i<cells.size()){
        remaining_cap_a -= cells[i].allocated_capacity_;
        if(remaining_cap_a >= 0){
            a_capacity_limit = i+1;
            remaining_capacity_a = remaining_cap_a;
        }
        ++i;
    }
    } // Block

    { // Block
    capacity_t remaining_cap_b = region_b.capacity_;
    index_t i=cells.size();
    while(i>0){
        --i;
        remaining_cap_b -= cells[i].allocated_capacity_;
        if(remaining_cap_b >= 0){
            b_capacity_limit = i;
            remaining_capacity_b = remaining_cap_b;
        }
    }
    } // Block

    std::vector<cell_ref> & cells_a_side = region_a.cell_references_;
    std::vector<cell_ref> & cells_b_side = region_b.cell_references_;
    cells_a_side.clear();
    cells_b_side.clear();
    if(preference_limit >= b_capacity_limit and preference_limit <= a_capacity_limit){
        cells_a_side.insert(cells_a_side.end(), cells.begin(), cells.begin() + preference_limit);
        cells_b_side.insert(cells_b_side.end(), cells.begin() + preference_limit, cells.end());
    }
    else{
        index_t cut_position;
        capacity_t allocated_to_a_part;

        // Where do we cut?
        if(preference_limit < b_capacity_limit){ // Pack on b
            cut_position = b_capacity_limit-1; // Exists since preference_limit >= 0
            allocated_to_a_part = cells[cut_position].allocated_capacity_ - remaining_capacity_b;
        }
        else{ // Pack on a
        // if(preference_limit > a_capacity_limit)
            cut_position = a_capacity_limit; // Necessarily a correct position since preference_limits <= cells.size()
            allocated_to_a_part = remaining_capacity_a;
        }

        cells_a_side.reserve( cut_position             +1);
        cells_b_side.reserve(cells.size()-cut_position +1);

        cells_a_side.insert(cells_a_side.end(), cells.begin(), cells.begin() + cut_position);

        cell_ref cell_cut_a = cells[cut_position], cell_cut_b = cells[cut_position];
        cell_cut_a.allocated_capacity_ = allocated_to_a_part;
        cell_cut_b.allocated_capacity_ -= allocated_to_a_part;
        if(cell_cut_a.allocated_capacity_ > 0){ cells_a_side.push_back(cell_cut_a); }
        if(cell_cut_b.allocated_capacity_ > 0){ cells_b_side.push_back(cell_cut_b); }

        cells_b_side.insert(cells_b_side.end(), cells.begin() + cut_position+1, cells.end());
    }
}

void region_distribution::region::distribute_cells(region & a, region & b) const{
    distribute_new_cells(a, b, cell_references_);
    assert(a.allocated_capacity() + b.allocated_capacity() == allocated_capacity());
    assert(a.capacity() + b.capacity() == capacity());
}

void region_distribution::region::redistribute_cells(region & Ra, region & Rb){
    if(Ra.capacity() > 0 and Rb.capacity() > 0){
        std::vector<cell_ref> cells;
        cells.reserve(Ra.cell_references_.size()+Rb.cell_references_.size());
        cells.insert(cells.end(), Ra.cell_references_.begin(), Ra.cell_references_.end());
        cells.insert(cells.end(), Rb.cell_references_.begin(), Rb.cell_references_.end());

        distribute_new_cells(Ra, Rb, cells);
    }
}

void region_distribution::region::distribute_new_cells(std::vector<std::reference_wrapper<region_distribution::region> > regions, std::vector<cell_ref> all_cells){
    std::vector<capacity_t> caps;
    for(region_distribution::region & R : regions){
        caps.push_back(R.capacity_);
        R.cell_references_.clear();
    }

    sort_uniquify(all_cells);
    std::sort(all_cells.begin(), all_cells.end(), [](cell_ref const a, cell_ref const b){ return a.allocated_capacity_ > b.allocated_capacity_ or (a.allocated_capacity_ == b.allocated_capacity_ and a.pos_.x+a.pos_.y < b.pos_.x+b.pos_.y); });

    std::vector<std::vector<float_t> > costs(regions.size());
    std::vector<capacity_t> demands;
    for(auto const C : all_cells){
        for(index_t r=0; r<regions.size(); ++r){
            costs[r].push_back(regions[r].get().distance(C));
        }
        demands.push_back(C.allocated_capacity_);
    }

    std::vector<std::vector<capacity_t> > res = transport_generic(caps, demands, costs);

    assert(res.size() == regions.size());
    for(index_t i=0; i<regions.size(); ++i){
        assert(res[i].size() == all_cells.size());
        for(index_t j=0; j<all_cells.size(); ++j){
            if(res[i][j] > 0){
                cell_ref C = all_cells[j];
                C.allocated_capacity_ = res[i][j];
                regions[i].get().cell_references_.push_back(C);
            }
        }
    }

}

void region_distribution::region::redistribute_cells(std::vector<std::reference_wrapper<region_distribution::region> > regions){
    if(regions.size() > 1){
        std::vector<cell_ref> all_cells;
        for(region_distribution::region & R : regions){
            all_cells.insert(all_cells.end(), R.cell_references_.begin(), R.cell_references_.end());
        }
        distribute_new_cells(regions, all_cells);
    }
}

void region_distribution::region::distribute_cells(std::vector<std::reference_wrapper<region_distribution::region> > regions) const{
    distribute_new_cells(regions, cell_references_);
}

void region_distribution::multipartition(index_t x_width, index_t y_width){
    assert(x_width > 0 and y_width > 0);

    std::vector<region> old_placement_regions = prepare_regions(x_width*x_regions_cnt(), y_width*y_regions_cnt());
    placement_regions_.swap(old_placement_regions);

    index_t old_x_regions_cnt = x_regions_cnt();
    index_t old_y_regions_cnt = y_regions_cnt();
    x_regions_cnt_ *= x_width;
    y_regions_cnt_ *= y_width;

    for(index_t x=0; x < old_x_regions_cnt; ++x){
        for(index_t y=0; y < old_y_regions_cnt; ++y){

            index_t i = y * old_x_regions_cnt + x;
            std::vector<std::reference_wrapper<region> > destination_regions;

            capacity_t tot_cap = 0;
            // Take the new regions
            for(index_t l_x=0; l_x<x_width; ++l_x){
                for(index_t l_y=0; l_y<y_width; ++l_y){
                    // Initialize it
                    region & cur_reg = get_region(x_width*x + l_x, y_width*y + l_y);
                    destination_regions.push_back(std::reference_wrapper<region>(cur_reg));
                    tot_cap += cur_reg.capacity();
                }
            }
            // Distribute the cells
            old_placement_regions[i].distribute_cells(destination_regions);
            assert(tot_cap == old_placement_regions[i].capacity());
        }
    }
}

void region_distribution::redo_bipartitions(){
    // This function performs optimization between neighbouring regions in various directions
    // The most important feature is diagonal optimization, since it is not done during partitioning
    redo_diagonal_bipartitions();
    redo_adjacent_bipartitions();
}

void region_distribution::redo_diagonal_bipartitions(){
    // Take four cells at a time and optimize them
    auto const optimize_quad_diag = [&](index_t x, index_t y){
        region::redistribute_cells(get_region(x, y), get_region(x+1, y+1));
        region::redistribute_cells(get_region(x+1, y), get_region(x, y+1));
    };

    // x is the fast index: the innermost loop operates on it
    auto const optimize_diag_on_y = [&](index_t y){
        for(index_t x=0; x+1 < x_regions_cnt(); x+=2){
            if(x+2 < x_regions_cnt()){
                // x odd
                optimize_quad_diag(x+1, y);
            }
            // x even
            optimize_quad_diag(x, y);
        }
    };

    // OpenMP doesn't allow y+1 < y_regions_cnt(), but anyway y_regions_cnt() >= 1
    #pragma omp parallel for
    for(index_t y=0; y < y_regions_cnt()-1; y+=2){
        // y even
        optimize_diag_on_y(y);
    }
    #pragma omp parallel for
    for(index_t y=1; y < y_regions_cnt()-1; y+=2){
        // y odd
        optimize_diag_on_y(y);
    }
}

void region_distribution::redo_adjacent_bipartitions(){
    auto const optimize_H = [&](index_t x, index_t y){
        region::redistribute_cells(get_region(x, y), get_region(x+1, y));
    };
    auto const optimize_V = [&](index_t x, index_t y){
        region::redistribute_cells(get_region(x, y), get_region(x, y+1));
    };

    // x bipartitions
    #pragma omp parallel for
    for(index_t y=0; y < y_regions_cnt(); ++y){
        for(index_t x=0; x+1 < x_regions_cnt(); x+=2){
            if(x+2 < x_regions_cnt()){
                // x odd
                optimize_H(x+1, y);
            }
            // x even
            optimize_H(x, y);
        }
    }
    // y bipartitions
    #pragma omp parallel for
    for(index_t x=0; x < x_regions_cnt(); ++x){
        for(index_t y=0; y+1 < y_regions_cnt(); y+=2){
            if(y+2 < y_regions_cnt()){
                // y odd
                optimize_V(x, y+1);
            }
            // y even
            optimize_V(x, y);
        }
    }
}

void region_distribution::redo_multipartitions(index_t x_width, index_t y_width){
    if(x_width < 2 and y_width < 2) throw std::runtime_error("Multipartitioning requires an optimization window of 2 or more\n");

    auto const reoptimize_group = [&](index_t x, index_t y){
        std::vector<std::reference_wrapper<region> > to_opt;
        for(index_t l_x=x; l_x < std::min(x+x_width, x_regions_cnt()); ++l_x){
            for(index_t l_y=y; l_y < std::min(y+y_width, y_regions_cnt()); ++l_y){
                to_opt.push_back(std::reference_wrapper<region>(get_region(l_x, l_y)));
            }
        }
        region::redistribute_cells(to_opt);
    };

    auto const optimize_on_y = [&](index_t y){
        for(index_t x=0; x < x_regions_cnt(); x+=x_width){
            if(x+x_width < x_regions_cnt()){
                reoptimize_group(x+x_width/2, y);
            }
            reoptimize_group(x, y);
        }
    };

    for(index_t y=0; y < y_regions_cnt(); y+=y_width){
        if(y+y_width < y_regions_cnt()){
            optimize_on_y(y+y_width/2);
        }
        optimize_on_y(y);
    }
}

inline void region_distribution::region::distribute_new_cells(std::vector<std::reference_wrapper<region> > regions, std::vector<cell_ref> cells, std::function<float_t (point<float_t>)> coord){
	// Gather all cells and the useful regions
	std::vector<std::reference_wrapper<region> > all_regions;

	for(region & reg_ref : regions){
		if(reg_ref.capacity() > 0){
			cells.insert(cells.end(), reg_ref.cell_references_.begin(), reg_ref.cell_references_.end());
			reg_ref.cell_references_.clear();
			all_regions.push_back(std::reference_wrapper<region>(reg_ref));
		}
		else{
			assert(reg_ref.cell_references_.empty());
		}
	}

	// Sort the regions by coordinate
	std::sort(all_regions.begin(), all_regions.end(), [&](std::reference_wrapper<region> const a, std::reference_wrapper<region> const b){ return coord(a.get().pos_) < coord(b.get().pos_); });
	// And the cells
	std::sort(cells.begin(), cells.end(), [&](cell_ref const a, cell_ref const b){ return coord(a.pos_) < coord(b.pos_); });
	just_uniquify(cells);

	std::vector<t1D_elt> sources, sinks;
	for(cell_ref const c : cells){
		sources.push_back(t1D_elt(static_cast<int_t>(coord(c.pos_)), c.allocated_capacity_));
	}
	for(region & reg_ref : all_regions){
		sinks.push_back(t1D_elt(static_cast<int_t>(coord(reg_ref.pos_)), reg_ref.capacity()));
	}

	std::vector<capacity_t> const positions = transport_1D(sources, sinks);

	std::vector<capacity_t> prev_cap(1, 0);
	for(t1D_elt e: sinks){
		assert(e.second > 0);
		prev_cap.push_back(prev_cap.back() + e.second);
	}

	for(index_t i=0; i<sources.size(); ++i){
		assert(positions[i] + sources[i].second <= prev_cap.back());
		assert(positions[i] >= 0);
		assert(sources[i].second > 0);
		if(i>0)
			assert(sources[i-1].second + positions[i-1] <= positions[i]);
	}

	for(index_t i=0, r=0; i<sources.size(); ++i){
		cell_ref const c = cells[i];

		capacity_t cur_pos = positions[i];
		capacity_t cur_cap = sources[i].second;
		while(cur_cap > 0){
			while(prev_cap[r+1] <= cur_pos){ // After the end of region r
				++r;
			}
			cell_ref new_c = c;
			capacity_t used_cap = std::min(prev_cap[r+1] - cur_pos, cur_cap);
			new_c.allocated_capacity_ = used_cap;
			assert(used_cap >= 0);
			if(used_cap > 0){
				all_regions[r].get().cell_references_.push_back(new_c);
			}
			cur_pos += used_cap;
			cur_cap -= used_cap;
		}
	}
}

inline void region_distribution::region::redistribute_cells(std::vector<std::reference_wrapper<region> > & regions, std::function<float_t (point<float_t>)> coord){
    std::vector<cell_ref> cells;
	for(region & reg_ref : regions){
		if(reg_ref.capacity() > 0){
			cells.insert(cells.end(), reg_ref.cell_references_.begin(), reg_ref.cell_references_.end());
			reg_ref.cell_references_.clear();
		}
		else{
			assert(reg_ref.cell_references_.empty());
		}
	}
    distribute_new_cells(regions, cells, coord);
}

void region_distribution::redo_line_partitions(){
    // Optimize a single line or column
    #pragma omp parallel for
    for(index_t y=0; y<y_regions_cnt(); ++y){
        std::vector<std::reference_wrapper<region> > regs;
        for(index_t x=0; x<x_regions_cnt(); ++x){
            regs.push_back(std::reference_wrapper<region>(get_region(x, y)));
        }
        region::redistribute_cells(regs, [](point<float_t> p){ return p.x; });
    }
    #pragma omp parallel for
    for(index_t x=0; x<x_regions_cnt(); ++x){
        std::vector<std::reference_wrapper<region> > regs;
        for(index_t y=0; y<y_regions_cnt(); ++y){
            regs.push_back(std::reference_wrapper<region>(get_region(x, y)));
        }
        region::redistribute_cells(regs, [](point<float_t> p){ return p.y; });
    }
}

void region_distribution::x_resize(index_t sz){
    assert(sz > 0);
    std::vector<region> old_placement_regions = prepare_regions(sz, y_regions_cnt());
    placement_regions_.swap(old_placement_regions);

    index_t old_x_regions_cnt = x_regions_cnt();

    x_regions_cnt_ = sz;

    #pragma omp parallel for
    for(index_t y=0; y<y_regions_cnt(); ++y){
        std::vector<cell_ref> cells;
        for(index_t x=0; x<old_x_regions_cnt; ++x){
            std::vector<cell_ref> const & cur = old_placement_regions[y*old_x_regions_cnt + x].cell_references_;
            cells.insert(cells.end(), cur.begin(), cur.end());
        }

        std::vector<std::reference_wrapper<region> > regs;
        for(index_t x=0; x<x_regions_cnt(); ++x){
            regs.push_back(std::reference_wrapper<region>(get_region(x, y)));
        }
        region::distribute_new_cells(regs, cells, [](point<float_t> p){ return p.x; });
    }
}

void region_distribution::y_resize(index_t sz){
    assert(sz > 0);
    std::vector<region> old_placement_regions = prepare_regions(x_regions_cnt(), sz);
    placement_regions_.swap(old_placement_regions);

    index_t old_y_regions_cnt = y_regions_cnt();

    y_regions_cnt_ = sz;

    #pragma omp parallel for
    for(index_t x=0; x<x_regions_cnt(); ++x){
        std::vector<cell_ref> cells;
        for(index_t y=0; y<old_y_regions_cnt; ++y){
            std::vector<cell_ref> const & cur = old_placement_regions[y*x_regions_cnt() + x].cell_references_;
            cells.insert(cells.end(), cur.begin(), cur.end());
        }

        std::vector<std::reference_wrapper<region> > regs;
        for(index_t y=0; y<y_regions_cnt(); ++y){
            regs.push_back(std::reference_wrapper<region>(get_region(x, y)));
        }
        region::distribute_new_cells(regs, cells, [](point<float_t> p){ return p.y; });
    }
}

void region_distribution::redo_diag_partitions(index_t len){
    if(x_regions_cnt() <= 1 or y_regions_cnt() <= 1) return;

    auto const reoptimize_rdiag = [&](index_t x_b, index_t y_b){
        index_t max_delta = std::min(x_regions_cnt()-x_b, y_regions_cnt()-y_b);
        for(index_t delta=0; delta+len/2<max_delta; delta += len/2){
            std::vector<std::reference_wrapper<region> > to_opt;
            for(index_t m=0; m < std::min(max_delta-delta, len); ++m){
                to_opt.push_back(std::reference_wrapper<region>(get_region(x_b+delta+m, y_b+delta+m)));
            }
            region::redistribute_cells(to_opt);
        }
    };
    auto const reoptimize_ldiag = [&](index_t x_b, index_t y_b){
        index_t max_delta = std::min(x_regions_cnt()-x_b, y_b+1);
        for(index_t delta=0; delta+len/2<max_delta; delta += len/2){
            std::vector<std::reference_wrapper<region> > to_opt;
            for(index_t m=0; m < std::min(max_delta-delta, len); ++m){
                to_opt.push_back(std::reference_wrapper<region>(get_region(x_b+delta+m, y_b-delta-m)));
            }
            region::redistribute_cells(to_opt);
        }
    };

    for(index_t y=y_regions_cnt()-2; y >= 1; --y)
        reoptimize_rdiag(0, y);
    for(index_t x=0; x+1<x_regions_cnt(); ++x)
        reoptimize_rdiag(x, 0);
    for(index_t y=1; y+1<y_regions_cnt(); ++y)
        reoptimize_ldiag(0, y);
    for(index_t x=0; x+1<x_regions_cnt(); ++x)
        reoptimize_ldiag(x, y_regions_cnt()-1);
}

region_distribution::region_distribution(
    box<int_t> placement_area,
    netlist const & circuit, placement_t const & pl,
    std::vector<density_limit> const & density_map, bool full_density
    ):
        x_regions_cnt_(1),
        y_regions_cnt_(1),
        placement_area_(placement_area),
        density_map_(density_map),
        full_density_mul(default_density_mul)
    {

    capacity_t tot_area = 0;
    for(index_t i=0; i<circuit.cell_cnt(); ++i){
        auto c = circuit.get_cell(i);
        if( (c.attributes & XMovable) != 0 and (c.attributes & YMovable) != 0){
            cell_list_.push_back(movable_cell(c.area, static_cast<point<float_t> >(pl.positions_[i]) + 0.5f * static_cast<point<float_t> >(c.size), i));
            tot_area += c.area;
        }
        else{ // Create an obstacle corresponding to the macro
            auto pos = pl.positions_[i];
            auto end = pos + c.size;
            density_limit macro;
            macro.box_ = box<int_t>(pos.x, end.x, pos.y, end.y);
            macro.density_ = 0.0f;
            density_map_.push_back(macro);
        }
    }

    placement_regions_ = prepare_regions(1, 1);

    if(full_density){
        cell_density_mul = default_density_mul;
    }
    else{
        cell_density_mul = placement_regions_[0].capacity() / tot_area;
        if(cell_density_mul < full_density_mul){
            throw std::runtime_error("The density required by the obstacles is above 1");
        }
    }

    for(index_t i=0; i<cell_list_.size(); ++i){
        movable_cell const & c = cell_list_[i];
        if(c.demand_ == 0){
            throw std::runtime_error("A cell has been found with demand 0");
        }
        placement_regions_[0].cell_references_.push_back( cell_ref(c.demand_ * cell_density_mul, c.pos_, i) );
    }
}

region_distribution region_distribution::full_density_distribution(box<int_t> placement_area, netlist const & circuit, placement_t const & pl, std::vector<density_limit> const & density_map){
    return region_distribution(placement_area, circuit, pl, density_map, true);
}
region_distribution region_distribution::uniform_density_distribution(box<int_t> placement_area, netlist const & circuit, placement_t const & pl, std::vector<density_limit> const & density_map){
    return region_distribution(placement_area, circuit, pl, density_map, false);
}

void region_distribution::update(netlist const & circuit, placement_t const & pl){
    for(movable_cell & c : cell_list_){
        index_t i = c.index_in_placement_;
        c.pos_ = static_cast<point<float_t> >(pl.positions_[i]) + 0.5f * static_cast<point<float_t> >(circuit.get_cell(i).size);
    }
    for(region & R : placement_regions_){
        for(cell_ref & c : R.cell_references_){
            c.pos_ = cell_list_[c.index_in_list_].pos_;
        }
    }
}

std::vector<region_distribution::movable_cell> region_distribution::export_positions() const{
    std::vector<point<float_t> > weighted_pos(cell_list_.size(), point<float_t>(0.0, 0.0));

    for(region const & R : placement_regions_){
        for(cell_ref C : R.cell_references_){
            weighted_pos[C.index_in_list_] = weighted_pos[C.index_in_list_] + static_cast<float_t>(C.allocated_capacity_) * R.pos_;
        }
    }

    std::vector<movable_cell> ret;
    for(index_t i=0; i<cell_list_.size(); ++i){
        movable_cell C = cell_list_[i];
        C.pos_ = ( static_cast<float_t>(1.0) / static_cast<float_t>(C.demand_ * cell_density_mul) ) * weighted_pos[i];
        ret.push_back(C);
    }
    return ret;
}

struct OSRP_task{
    float_t size;
    float_t goal_pos;
    float_t weight;
    index_t orig;
    OSRP_task(float_t p, float_t s, float_t w, index_t i) : size(s), goal_pos(p), weight(w), orig(i) {}
    OSRP_task(){}
};

std::vector<float_t> get_optimal_quadratic_pos(std::vector<OSRP_task> cells, float_t pos_begin, float_t pos_end){

    if(cells.empty()){ return std::vector<float_t>(); }

    struct OSRP_cluster{
        index_t begin, end;
        float_t pos, size;
        float_t weight;
        OSRP_cluster(index_t b, index_t e, float_t p, float_t s, float_t w) : begin(b), end(e), pos(p), size(s), weight(w) {}
        void merge(OSRP_cluster const o){
            begin = o.begin;
            pos = (weight * pos + o.weight * o.pos) / (weight + o.weight);
            size += o.size;
            weight += o.weight;
        }
    };

    std::sort(cells.begin(), cells.end(), [](OSRP_task a, OSRP_task b){ return a.goal_pos < b.goal_pos; });

    // Modify the goal pos to get absolute goal positions between pos_begin and pos_end - sum_sizes
    float_t sum_sizes = 0.0;
    for(auto & c : cells){
        c.goal_pos -= sum_sizes;
        sum_sizes += c.size;
    }
    float_t abs_begin = pos_begin + 0.5 * cells[0].size; // First cell must be far enough from the beginning
    float_t abs_end = pos_end - sum_sizes + 0.5 * cells[0].size; // Last cell must be far enough from the end
    for(auto & c : cells){
        c.goal_pos = std::max(c.goal_pos, abs_begin);
        c.goal_pos = std::min(c.goal_pos, abs_end);
    }

    std::vector<OSRP_cluster> clusters;
    for(index_t i=0; i<cells.size(); ++i){
        OSRP_cluster to_add(
            i, i,
            cells[i].goal_pos,
            cells[i].size, cells[i].weight
        );
        while(not clusters.empty() and (clusters.back().pos >= to_add.pos)){
            to_add.merge(clusters.back());
            clusters.pop_back();
        }

        clusters.push_back(to_add);
    }

    std::vector<float_t> ret(cells.size(), 0.0);
    // For every cell, recover its true position from the absolute position of a cluster
    float_t sum_prev_sizes = 0.0;
    for(OSRP_cluster cur : clusters){
        for(index_t i=cur.begin; i <= cur.end; ++i){
            ret[cells[i].orig] = cur.pos + sum_prev_sizes;
            sum_prev_sizes += cells[i].size;
        }
    }
    return ret;
}

std::vector<region_distribution::movable_cell> region_distribution::export_spread_positions_quadratic() const{
    std::vector<point<float_t> > weighted_pos(cell_list_.size(), point<float_t>(0.0, 0.0));

    for(index_t y=0; y<y_regions_cnt(); ++y){
        for(index_t x=0; x<x_regions_cnt(); ++x){
            region const & R = get_region(x, y);
            index_t n = R.cell_references_.size();
            float_t total_capacity = static_cast<float_t>(R.capacity());
            box<float_t> surface = static_cast<box<float_t> >(get_box(x, y, x_regions_cnt(), y_regions_cnt()));
    
            std::vector<OSRP_task> x_cells(n), y_cells(n);
            for(index_t i=0; i<n; ++i){
                point<float_t> pt = R.cell_references_[i].pos_;
                float_t cap = static_cast<float_t>(R.cell_references_[i].allocated_capacity_);
                x_cells[i] = OSRP_task(pt.x, cap/total_capacity * (surface.x_max - surface.x_min), 1.0, i);
                y_cells[i] = OSRP_task(pt.y, cap/total_capacity * (surface.y_max - surface.y_min), 1.0, i);
            }
            std::vector<float_t> x_ret = get_optimal_quadratic_pos(x_cells, surface.x_min, surface.x_max);
            std::vector<float_t> y_ret = get_optimal_quadratic_pos(y_cells, surface.y_min, surface.y_max);
    
            for(index_t i=0; i<n; ++i){
                weighted_pos[R.cell_references_[i].index_in_list_] +=
                      static_cast<float_t>(R.cell_references_[i].allocated_capacity_)
                    * point<float_t>(x_ret[i], y_ret[i]);
            }
        }
    }

    std::vector<movable_cell> ret;
    for(index_t i=0; i<cell_list_.size(); ++i){
        movable_cell C = cell_list_[i];
        assert(C.demand_ > 0);
        C.pos_ = ( static_cast<float_t>(1.0) / static_cast<float_t>(C.demand_) ) * weighted_pos[i];
        assert(std::isfinite(C.pos_.x) and std::isfinite(C.pos_.y));
        ret.push_back(C);
    }
    return ret;
}

std::vector<region_distribution::movable_cell> region_distribution::export_spread_positions_linear() const{
    std::vector<point<float_t> > weighted_pos(cell_list_.size(), point<float_t>(0.0, 0.0));

    for(index_t y=0; y<y_regions_cnt(); ++y){
        for(index_t x=0; x<x_regions_cnt(); ++x){
            region const & R = get_region(x, y);
            index_t n = R.cell_references_.size();
            float_t total_capacity = static_cast<float_t>(R.capacity());
            box<float_t> surface = static_cast<box<float_t> >(get_box(x, y, x_regions_cnt(), y_regions_cnt()));
          //assert(surface.x_max > surface.x_min and surface.y_max > surface.y_min);
    
            std::vector<legalizable_task<float_t> > x_cells, y_cells;
    
            for(auto const C : R.cell_references_){
                float_t cap = static_cast<float_t>(C.allocated_capacity_);
                float_t x_cap_prop = cap/total_capacity * (surface.x_max - surface.x_min),
                        y_cap_prop = cap/total_capacity * (surface.y_max - surface.y_min);
                x_cells.push_back(legalizable_task<float_t>(x_cap_prop, C.pos_.x, C.index_in_list_));
                y_cells.push_back(legalizable_task<float_t>(y_cap_prop, C.pos_.y, C.index_in_list_));
            }
    
            OSRP_leg<float_t> x_leg(surface.x_min, surface.x_max), y_leg(surface.y_min, surface.y_max);
    
            std::sort(x_cells.begin(), x_cells.end());
            for(legalizable_task<float_t> & C : x_cells)
                C.target_pos -= 0.5 * C.width;
            for(legalizable_task<float_t> & C : x_cells)
                x_leg.push(C);
            auto x_pl = x_leg.get_placement();
            for(index_t i=0; i<n; ++i){
                assert(std::isfinite(x_pl[i].second));
                weighted_pos[x_pl[i].first].x += (x_pl[i].second + 0.5f * x_cells[i].width) * static_cast<float_t>(x_cells[i].width * total_capacity / (surface.x_max - surface.x_min));
            }

            std::sort(y_cells.begin(), y_cells.end());
            for(legalizable_task<float_t> & C : y_cells)
                C.target_pos -= 0.5 * C.width;
            for(legalizable_task<float_t> & C : y_cells)
                y_leg.push(C);
            auto y_pl = y_leg.get_placement();
            for(index_t i=0; i<n; ++i){
                assert(std::isfinite(y_pl[i].second));
                weighted_pos[y_pl[i].first].y += (y_pl[i].second + 0.5f * y_cells[i].width) * static_cast<float_t>(y_cells[i].width * total_capacity / (surface.y_max - surface.y_min));
            }
        }
    }

    std::vector<movable_cell> ret;
    for(index_t i=0; i<cell_list_.size(); ++i){
        movable_cell C = cell_list_[i];
        assert(C.demand_ > 0);
        C.pos_ = ( 1.0f / static_cast<float_t>(C.demand_ * cell_density_mul) ) * weighted_pos[i];
        assert(std::isfinite(C.pos_.x) and std::isfinite(C.pos_.y));
        ret.push_back(C);
    }
    return ret;
}

float_t region_distribution::region::cost() const{
    float_t res = 0.0;
    for(cell_ref const C : cell_references_){
        res += (std::abs(C.pos_.x-pos_.x) + std::abs(C.pos_.y-pos_.y)) * static_cast<float_t>(C.allocated_capacity_);
    }
    return res;
}

float_t region_distribution::cost() const{
    float_t res = 0.0;
    capacity_t tot_cap = 0;
    for(region const & R : placement_regions_){
        res += R.cost();
        tot_cap += R.allocated_capacity();
    }
    // Average over the cells' areas
    return res / static_cast<float_t>(tot_cap);
}

} // Namespace gp
} // Namespace coloquinte

