
#include "coloquinte/optimization_subproblems.hxx"

#include <stdexcept>

namespace coloquinte{

std::vector<capacity_t>  transport_1D(std::vector<t1D_elt> sources, std::vector<t1D_elt> sinks){
    /* Description of the algorithm:
     *
     *    For each cell, put it in its optimal region or the last region where a cell is if there is no space in it
     *    Push all changes in the derivative of the cost function to a priority queue; those changes occur
     *          when evicting the preceding cell from a region (most such changes are 0 and not considered, hence the complexity)
     *          when moving to a non-full region
     *    While the new cell overlaps with a new region, get the new slope (derivative) at this point
     *    and push all preceding cell until this region is freed or the slope becomes 0 (in which case the new region is now occupied)
     */

    struct bound{
        capacity_t pos;
        int_t slope_diff;
        bool operator<(bound const o) const{ return pos < o.pos; }
    };

    std::priority_queue<bound> bounds;
    std::vector<capacity_t> constraining_pos;
    std::vector<capacity_t> prev_cap(1, 0), prev_dem(1, 0);
    for(auto const s : sinks){
        prev_cap.push_back(s.second + prev_cap.back());
    }
    for(auto const s : sources){
        prev_dem.push_back(s.second + prev_dem.back());
    }
    // The sinks have enough capacity to hold the whole demand
    assert(prev_cap.back() >= prev_dem.back());

    const capacity_t min_abs_pos = 0, max_abs_pos = prev_cap.back() - prev_dem.back();
    assert(min_abs_pos <= max_abs_pos);

    auto push_bound = [&](capacity_t p, int_t s){
        assert(s >= 0);
        if(p > min_abs_pos){
            bound B;
            B.pos = p;
            B.slope_diff = s;
            bounds.push(B);
        }
    }; 

    // Distance to the right - distance to the left
    auto get_slope = [&](index_t src, index_t boundary){
        assert(boundary+1 < sinks.size());
        assert(src < sources.size());
        return std::abs((float)(sources[src].first - sinks[boundary+1].first)) - std::abs((float)(sources[src].first - sinks[boundary].first));
    };

    capacity_t cur_abs_pos = min_abs_pos;
    index_t opt_r=0, next_r=0, first_free_r=0;

    for(index_t i=0; i<sources.size(); ++i){
        // Update the optimal region
        while(opt_r+1 < sinks.size() and (sinks[opt_r].first + sinks[opt_r+1].first)/2 < sources[i].first){
            ++opt_r;
        }
        // Update the next region
        index_t prev_next_r = next_r;
        while(next_r < sinks.size() and sinks[next_r].first <= sources[i].first){
            ++next_r;
        }

        index_t dest_reg = std::max(first_free_r, opt_r);
        assert(dest_reg < sinks.size());

        if(i>0){
            // Push bounds due to changing the source crossing the boundary j/j+1
            // Linear amortized complexity accross all sources (next_r grows)
            // get_slope(i-1, j) - get_slope(i, j) == 0 if j >= next_r
            // get_slope(i-1, j) - get_slope(i, j) == 0 if j < prev_next_r-1

            for(index_t j=std::max(prev_next_r,1u)-1; j<std::min(first_free_r, next_r+1); ++j){
                assert(get_slope(i,j) <= get_slope(i-1,j));
                push_bound(prev_cap[j+1] - prev_dem[i], get_slope(i-1, j) - get_slope(i,j));
            }
        }
        // Add the bounds due to crossing the boundaries alone
        for(index_t j=first_free_r; j<opt_r; ++j){
            assert(get_slope(i,j) <= 0);
            push_bound(prev_cap[j+1] - prev_dem[i], -get_slope(i, j));
        }

        first_free_r = std::max(first_free_r, opt_r);
        capacity_t this_abs_pos = std::max(cur_abs_pos, prev_cap[first_free_r] - prev_dem[i]); // Just after the previous cell or at the beginning of the destination region

        while(first_free_r+1 < sinks.size() and this_abs_pos > std::max(prev_cap[first_free_r+1] - prev_dem[i+1], min_abs_pos)){ // Absolute position that wouldn't make the cell fit in the region, and we are not in the last region yet
            capacity_t end_pos = std::max(prev_cap[first_free_r+1] - prev_dem[i+1], min_abs_pos);

            int_t add_slope = get_slope(i, first_free_r);
            int_t slope = add_slope;

            while(not bounds.empty() and slope >= 0 and bounds.top().pos > end_pos){
                this_abs_pos = bounds.top().pos;
                slope -= bounds.top().slope_diff;
                bounds.pop();
            }
            if(slope >= 0){ // We still push: the cell completely escapes the region
                this_abs_pos = end_pos;
                push_bound(end_pos, add_slope-slope);
            }
            else{ // Ok, absorbed the whole slope: push what remains and we still occupy the next region
                push_bound(this_abs_pos, -slope);
                ++first_free_r;
            }
        }
        cur_abs_pos = this_abs_pos;
        constraining_pos.push_back(this_abs_pos);
    }

    assert(constraining_pos.size() == sources.size());
    if(not constraining_pos.empty()){
        // Calculate the final constraining_pos
        constraining_pos.back() = std::min(max_abs_pos, constraining_pos.back());
    }

    std::partial_sum(constraining_pos.rbegin(), constraining_pos.rend(), constraining_pos.rbegin(), [](capacity_t a, capacity_t b)->capacity_t{ return std::min(a, b); });

    for(index_t i=0; i<constraining_pos.size(); ++i){
        constraining_pos[i] += prev_dem[i];
    }

    return constraining_pos;
}

namespace{ // Anonymous namespace to hide the transportation structures

class current_allocation{
    static const index_t null_ind;

    // Internal data structures

    // Priority queue element to determine the source to be used between regions
    struct movable_source{
        index_t source;
        float_t cost;
        bool operator<(movable_source const o) const{
               return cost > o.cost // Sorted by cost
            || (cost == o.cost && source < o.source); // And by index to limit the number of fractional elements between two regions
        }
        movable_source(index_t s, float_t c) : source(s), cost(c) {}
    };

    // Member data

    // The current state
    std::vector<std::vector<capacity_t>  > sr_allocations; // For each region, for each source, the capacity allocated by the region
    std::vector<std::vector<float_t> >     sr_costs;       // The costs from a region to a source
    std::vector<capacity_t>                s_demands;      // The demands of the sources
    std::vector<capacity_t>                r_capacities;   // The remaining capacities of the regions

    // Shortest path data
    std::vector<float_t>                   r_costs;        // The costs of allocating to a region
    std::vector<index_t>                   r_parents;      // The parents of the regions i.e. the regions where we push sources first (or null_ind)
    std::vector<index_t>                   r_sources;      // The source involved in these edges
    std::vector<capacity_t>                arc_capacities; // The capacities of the edges to the parents, or of the region if no parent

    // Best edges data
    std::vector<std::vector<std::priority_queue<movable_source> > > best_interregions_costs; // What is the best source to move to go from region k1 to region k2?
    index_t dijkstra_cnt;


    // Helper functions

    // Number of regions
    index_t region_cnt() const{
        assert(sr_costs.size() == sr_allocations.size());
        return sr_costs.size();
    }

    // Update the edge between two regions
    void update_edge(index_t r1, index_t r2);
    // Add a source to all heaps of a region; returns if we need to update a path
    bool add_source_to_heaps(index_t r, index_t source);
    // Initialize the heaps of a region
    void create_heaps(index_t reg);

    // Run the shortest path algorithm to update the cost of each region
    void dijkstra_update();

    // Update the edge and returns if we need to rerun Dijkstra
    bool push_edge(index_t reg, capacity_t flow);
    // Updates a full path when pushing an element; returns if we need to rerun Dijkstra
    bool push_path(index_t pushed_reg, capacity_t demanded, capacity_t & flow);

    public:
    // Add a new source to the transportation problem; should be done in decreasing order of demand to keep low complexity
    void add_source(index_t elt_ind);

    current_allocation(std::vector<capacity_t> caps, std::vector<capacity_t> demands, std::vector<std::vector<float_t> > costs)
        :
        sr_allocations(caps.size()),
        sr_costs(costs),
        s_demands(demands),
        r_capacities(caps),
        r_costs(caps.size(), 0.0),
        r_parents(caps.size(), null_ind),
        r_sources(caps.size(), null_ind),
        arc_capacities(caps),
        best_interregions_costs(caps.size(), std::vector<std::priority_queue<movable_source> >(caps.size())),
        dijkstra_cnt(0)
        {
            assert(caps.size() > 0);
            assert(costs.size() == caps.size());
            dijkstra_update();
        }

    std::vector<std::vector<capacity_t> > get_allocations() const{ return sr_allocations; }
    index_t get_iterations_cnt() const { return dijkstra_cnt; }
};

const index_t current_allocation::null_ind = std::numeric_limits<index_t>::max();

void current_allocation::update_edge(index_t r1, index_t r2){
    while(not best_interregions_costs[r1][r2].empty() and sr_allocations[r1][best_interregions_costs[r1][r2].top().source] == 0){
        best_interregions_costs[r1][r2].pop();
    }

    if(not best_interregions_costs[r1][r2].empty()){
        // There is an edge
        movable_source cur = best_interregions_costs[r1][r2].top();
        float_t new_cost = r_costs[r2] + cur.cost;
        if(new_cost < r_costs[r1]){
            r_costs[r1] = cur.cost;
            r_sources[r1] = cur.source;
            r_parents[r1] = r2;
            arc_capacities[r1] = sr_allocations[r1][cur.source];
        }
    }
}

bool current_allocation::add_source_to_heaps(index_t r, index_t source){
    bool need_rerun = false;
    for(index_t i=0; i<region_cnt(); ++i){
        if(i == r) continue;
        best_interregions_costs[r][i].push(
            movable_source(source,
                sr_costs[i][source] - sr_costs[r][source]
            )
        );
        while(sr_allocations[r][best_interregions_costs[r][i].top().source] == 0){
            best_interregions_costs[r][i].pop();
        }
        need_rerun = (best_interregions_costs[r][i].top().source == source) or need_rerun;
    }
    return need_rerun;
}

void current_allocation::create_heaps(index_t reg){
    // Get all relevant elements
    std::vector<std::vector<movable_source> > interregion_costs(region_cnt());
    for(index_t i=0; i<sr_allocations[reg].size(); ++i){
        if(sr_allocations[reg][i] > 0){
            for(index_t oreg=0; oreg<region_cnt(); ++oreg){
                if(oreg == reg) continue;
                interregion_costs[oreg].push_back(
                    movable_source(
                        i,
                        sr_costs[oreg][i] - sr_costs[reg][i]
                    )
                );
            }
        }
    }
    // Create the heaps
    for(index_t oreg=0; oreg<region_cnt(); ++oreg){
        best_interregions_costs[reg][oreg] = std::priority_queue<movable_source>(interregion_costs[oreg].begin(), interregion_costs[oreg].end());
    }
}

// Returns if the path has been modified so that we would need to rerun Dijkstra
bool current_allocation::push_edge(index_t reg, capacity_t flow){
    index_t cur_source = r_sources[reg];

    // Does this edge allocates a new source in the destination region? If yes, update the corresponding heaps
    bool already_present = sr_allocations[r_parents[reg]][cur_source] > 0;

    // Deallocating from the first region is handled by the get_edge function: just substract the flow
    sr_allocations[          reg ][cur_source] -= flow;
    sr_allocations[r_parents[reg]][cur_source] += flow;

    assert(sr_allocations[reg][cur_source] >= 0); // The source to be pushed was indeed present in the region
    assert(r_capacities[reg] == 0); // The region is full, which explains why we need to push
    assert(flow <= arc_capacities[reg]); // The flow is not bigger than what can be sent

    arc_capacities[reg] = sr_allocations[reg][cur_source]; // Just update the capacity if it turns out that we don't need to run Dijkstra
    
    if(arc_capacities[reg] == 0){
        // The source may have been deleted from a region: rerun Dijkstra at the end
        return true;
    }
    else if(not already_present and r_capacities[r_parents[reg]] == 0){
        // A new source is allocated to a full region: rerun Dijkstra at the end if it changed the heap's top
        return add_source_to_heaps(r_parents[reg], cur_source);
    }
    else{
        // The edge is still present with the same cost and non-zero updated capacity
        // The path still exists: no need to rerun Dijkstra yet
        return false;
    }
}

void current_allocation::dijkstra_update(){
    // Simple case of the regions with remaining capacity
    std::vector<int> visited(region_cnt(), 0);
    index_t visited_cnt = 0;
    for(index_t i=0; i<region_cnt(); ++i){
        r_sources[i] = null_ind;
        r_parents[i] = null_ind;
        if(r_capacities[i] > 0){
            r_costs[i] = 0.0;
            arc_capacities[i] = r_capacities[i];

            visited[i] = 1;
            ++visited_cnt;
        }
        else{
            r_costs[i] = std::numeric_limits<float_t>::infinity();
            arc_capacities[i] = 0;
        }
    }
    // if(visited_cnt <= 0) throw std::runtime_error("Capacity problem: no region has been marked as reachable\n");
    if(visited_cnt == region_cnt()){ return; }
    // Get the costs for every non-visited region
    for(index_t i=0; i<region_cnt(); ++i) if(visited[i] == 0){ // For every region that is not visited yet
        for(index_t j=0; j<region_cnt(); ++j) if(visited[j] == 1){ // For every already visited region
            // Get the best interregion cost
            update_edge(i,j);
        }
    }
    while(visited_cnt < region_cnt()){
        // Find the region with the lowest cost to visit; mark it visited
        index_t best_reg = null_ind;
        float_t best_cost = std::numeric_limits<float_t>::infinity();
        for(index_t i=0; i<region_cnt(); ++i) if(visited[i] == 0){ // For every region that is not visited yet
            if(r_costs[i] < best_cost){
                best_cost = r_costs[i];
                best_reg  = i;
            }
        }
        if(best_reg == null_ind) break; // Some regions are unreachable, typically because they have zero capacity at the beginning
        visited[best_reg] = 1;
        ++visited_cnt;
        // Update the cost for every unvisited region
        for(index_t i=0; i<region_cnt(); ++i) if(visited[i] == 0){ // For every region that is not visited yet
            update_edge(i, best_reg);
        }
    }
}

bool current_allocation::push_path(index_t pushed_reg, capacity_t demanded, capacity_t & flow){
    // Get the final flow sent, which is smaller than the capacities on the path
    flow = demanded;
    for(index_t reg = pushed_reg; reg != null_ind; reg = r_parents[reg]){
        flow = std::min(flow, arc_capacities[reg]);
    }

    bool rerun_dijkstra = false;
    // Update the path between the regions
    index_t reg = pushed_reg;
    for(; r_parents[reg] != null_ind; reg = r_parents[reg]){
        assert(r_capacities[reg] == 0);
        rerun_dijkstra = push_edge(reg, flow) or rerun_dijkstra;
    }

    assert(r_capacities[reg] > 0);
    assert(arc_capacities[reg] == r_capacities[reg]);
    assert(r_capacities[reg] >= flow);

    // Update the capacities at the end
    r_capacities[reg] -= flow;
    arc_capacities[reg] -= flow;

    // The last region on the path is the one that satisfies the demand
    if(r_capacities[reg] == 0){ // If we just consumed the available capacity, it becomes useful to move sources off this region: build the heap
        create_heaps(reg);
        rerun_dijkstra = true;
    }

    assert(flow > 0);

    // If an edge changes cost or a region is full,
    // we need to update the costs, parents, sources and arc_capacities using a Dijkstra
    // but later
    return rerun_dijkstra;
}

void current_allocation::add_source(index_t elt_ind){ //capacity_t demand, std::vector<float_t> const & costs){
    for(index_t i=0; i<region_cnt(); ++i){
        sr_allocations[i].push_back(0);
    }

    bool need_rerun = false;
    capacity_t demand = s_demands[elt_ind];

    while(demand > 0){
        // In case we modified the structures earlier
        if(need_rerun){
            dijkstra_update();
            need_rerun = false;
        }

        ++ dijkstra_cnt;
        index_t best_reg = null_ind;
        float_t best_cost = std::numeric_limits<float_t>::infinity();
        for(index_t reg=0; reg<region_cnt(); ++reg){
            // Find the region which gets the source
            if(r_costs[reg] + sr_costs[reg][elt_ind] < best_cost){
                best_reg = reg;
                best_cost = r_costs[reg] + sr_costs[reg][elt_ind];
            }
        }
        if(best_reg == null_ind){ throw std::runtime_error("No reachable region found\n"); }

        capacity_t flow = 0;
        // Tells us whether we need to update the data structures
        need_rerun = push_path(best_reg, demand, flow);
        demand -= flow;

        // Lazily store the change
        sr_allocations[best_reg][elt_ind] += flow;
    }

    // Set the source's demand
    for(index_t i=0; i<region_cnt(); ++i){
        if(r_capacities[i] == 0 and sr_allocations[i][elt_ind] > 0){
            need_rerun = add_source_to_heaps(i, elt_ind) or need_rerun;
        }
    }
    // We leave a clean set with correct paths for the next iteration
    if(need_rerun)
        dijkstra_update();
}

} // End anonymous namespace

std::vector<std::vector<capacity_t> > transport_generic(std::vector<capacity_t> const & capacities, std::vector<capacity_t> const & demands, std::vector<std::vector<float_t> > const & costs){
    current_allocation transporter(capacities, demands, costs);

    for(index_t i=0; i<demands.size(); ++i){
        transporter.add_source(i);
    }

    return transporter.get_allocations();
}

bool place_convex_single_row(std::vector<int_t> const & widths, std::vector<std::pair<int_t, int_t> > const & ranges, std::vector<cell_bound> bounds, std::vector<int_t> const & const_slopes, std::vector<int_t> & positions){
    std::sort(bounds.begin(), bounds.end());

    struct bound{
        int_t abs_pos;
        int_t slope_diff;

        bool operator<(bound const o) const{ return abs_pos < o.abs_pos; }
        bound(int_t p, int_t s) : abs_pos(p), slope_diff(s) {}
    };
    std::priority_queue<bound> prio_queue;

    std::vector<int_t> prev_widths(widths.size()+1, 0);
    std::partial_sum(widths.begin(), widths.end(), std::next(prev_widths.begin()));

    std::vector<int_t> constraining_pos(widths.size());

    int_t lower_lim = std::numeric_limits<int_t>::min();
    for(index_t i=0, j=0; i<widths.size(); ++i){
        int_t old_width = prev_widths[i];
        int_t new_width = prev_widths[i+1];

        lower_lim = std::max(ranges[i].first - old_width, lower_lim);
        int_t upper_lim = ranges[i].second - new_width;

        for(; j<bounds.size() and bounds[j].c == i; ++j){
            prio_queue.push(bound(bounds[j].pos - old_width, bounds[j].slope));
        }

        if(upper_lim < lower_lim){ // Infeasible
            return false;
        }
        int_t cur_slope = const_slopes[i];
        int_t cur_pos = upper_lim;

        while(not prio_queue.empty() and (cur_slope > 0 or prio_queue.top().abs_pos > upper_lim)){
            cur_slope -= prio_queue.top().slope_diff;
            cur_pos = prio_queue.top().abs_pos;
            prio_queue.pop();
        }
        int_t final_abs_pos = std::max(std::min(cur_pos, upper_lim), lower_lim);
        constraining_pos[i] = final_abs_pos;
        if(cur_slope < 0){
            prio_queue.push(bound(final_abs_pos, -cur_slope));
        }
    }

    positions.resize(constraining_pos.size());
    std::partial_sum(constraining_pos.rbegin(), constraining_pos.rend(), positions.rbegin(), [](int_t a, int_t b)->int_t{ return std::min(a,b); });
    for(index_t i=0; i<positions.size(); ++i){
        positions[i] += prev_widths[i];
    }
    return true;
}

bool place_noncvx_single_row(std::vector<int_t> const & widths, std::vector<std::pair<int_t, int_t> > const & ranges, std::vector<int> const & flippables, std::vector<cell_bound> bounds, std::vector<int_t> const & const_slopes, std::vector<int_t> & positions, std::vector<int> & flippings){
    flippings = std::vector<int>(positions.size(), 0);
    return place_convex_single_row(widths, ranges, bounds, const_slopes, positions);
}

} // Namespace coloquinte

