// Copyright (c) 2018 ISciences, LLC.
// All rights reserved.
//
// This software is licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License. You may
// obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "grid.h"

namespace exactextract {
    Box grid_cell(const Grid<bounded_extent> & grid, size_t row, size_t col) {
        // The ternary clauses below are used to make sure that the cells along
        // the right and bottom edges of our grid are slightly larger than m_dx,dy
        // if needed to make sure that we capture our whole extent. This is necessary
        // because xmin + nx*m_dx may be less than xmax because of floating point
        // errors.
        return {
                grid.xmin() + col * grid.dx(),
                row == (grid.rows() - 1) ? grid.ymin() : (grid.ymax() - (row + 1) * grid.dy()),
                col == (grid.cols() - 1) ? grid.xmax() : (grid.xmin() + (col + 1) * grid.dx()),
                grid.ymax() - row * grid.dy()

        };
    }

    Box grid_cell(const Grid<infinite_extent> & grid, size_t row, size_t col) {
        double xmin, xmax, ymin, ymax;

        if (col == 0) {
            xmin = std::numeric_limits<double>::lowest();
        } else if (col == grid.cols() - 1) {
            xmin = grid.xmax(); // because rightmost col of regular grid may have different width from others
        } else  {
            xmin = grid.xmin() + (col - 1) * grid.dx();
        }

        switch(grid.cols() - col) {
            case 1: xmax = std::numeric_limits<double>::max(); break;
            case 2: xmax = grid.xmax(); break;
            default: xmax = grid.xmin() + col*grid.dx();
        }

        if (row == 0) {
            ymax = std::numeric_limits<double>::max();
        } else if (row == grid.rows() - 1) {
            ymax = grid.ymin(); // because bottom row of regular grid may have different height from others
        } else {
            ymax = grid.ymax() - (row - 1) * grid.dy();
        }

        switch(grid.rows() - row) {
            case 1: ymin = std::numeric_limits<double>::lowest(); break;
            case 2: ymin = grid.ymin(); break;
            default: ymin = grid.ymax() - row*grid.dy();
        }

        return { xmin, ymin, xmax, ymax };
    }

    Grid<infinite_extent> make_infinite(const Grid<bounded_extent> & grid) {
        return { grid.extent(), grid.dx(), grid.dy() };
    }

    Grid<bounded_extent> make_finite(const Grid<infinite_extent> & grid) {
        return { grid.extent(), grid.dx(), grid.dy() };
    }

    std::vector<Grid<bounded_extent>> subdivide(const Grid<bounded_extent> & grid, size_t max_size) {
        if (grid.size() < max_size) {
            return { grid };
        }

        size_t cols_per_block = std::min(max_size, grid.cols());
        size_t rows_per_block = max_size / cols_per_block;

        size_t col_blocks = (grid.cols() - 1) / cols_per_block + 1;
        size_t row_blocks = (grid.rows() - 1) / rows_per_block + 1;

        std::vector<Grid<bounded_extent>> subgrids;
        for (size_t i = 0; i < row_blocks; i++) {
            for (size_t j = 0; j < col_blocks; j++) {
                double xmin = grid.xmin() + grid.dx()*cols_per_block*j;
                double xmax = j == (col_blocks - 1) ? grid.xmax() : (grid.xmin() + grid.dx()*cols_per_block*(j+1));
                double ymax = grid.ymax() - grid.dy()*rows_per_block*i;
                double ymin = i == (row_blocks - 1) ? grid.ymin() : (grid.ymax() - grid.dy()*rows_per_block*(i+1));

                Box reduced = {xmin, ymin, xmax, ymax};
                subgrids.emplace_back(reduced, grid.dx(), grid.dy());
            }
        }

        return subgrids;
    }
}
