// 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 <cmath>
#include <stdexcept>

#include "box.h"

namespace exactextract {

    static inline double clamp(double x, double low, double high) {
        return std::min(std::max(x, low), high);
    }

    Side Box::side(const Coordinate &c) const {
        if (c.x == xmin) {
            return Side::LEFT;
        } else if (c.x == xmax) {
            return Side::RIGHT;
        } else if (c.y == ymin) {
            return Side::BOTTOM;
        } else if (c.y == ymax) {
            return Side::TOP;
        }

        return Side::NONE;
    }

    Crossing Box::crossing(const Coordinate &c1, const Coordinate &c2) const {
        // vertical line
        if (c1.x == c2.x) {
            if (c2.y >= ymax) {
                return Crossing{Side::TOP, c1.x, ymax};
            } else if (c2.y <= ymin) {
                return Crossing{Side::BOTTOM, c1.x, ymin};
            } else {
                throw std::runtime_error("Never get here.");
            }
        }

        // horizontal line
        if (c1.y == c2.y) {
            if (c2.x >= xmax) {
                return Crossing{Side::RIGHT, xmax, c1.y};
            } else if (c2.x <= xmin) {
                return Crossing{Side::LEFT, xmin, c1.y};
            } else {
                throw std::runtime_error("Never get here");
            }
        }

        double m = std::abs((c2.y - c1.y) / (c2.x - c1.x));

        bool up = c2.y > c1.y;
        bool right = c2.x > c1.x;

        if (up) {
            if (right) {
                // 1st quadrant
                double y2 = c1.y + m * (xmax - c1.x);

                if (y2 < ymax) {
                    return Crossing{Side::RIGHT, xmax, clamp(y2, ymin, ymax)};
                } else {
                    double x2 = c1.x + (ymax - c1.y) / m;
                    return Crossing{Side::TOP, clamp(x2, xmin, xmax), ymax};
                }
            } else {
                // 2nd quadrant
                double y2 = c1.y + m * (c1.x - xmin);

                if (y2 < ymax) {
                    return Crossing{Side::LEFT, xmin, clamp(y2, ymin, ymax)};
                } else {
                    double x2 = c1.x - (ymax - c1.y) / m;
                    return Crossing{Side::TOP, clamp(x2, xmin, xmax), ymax};
                }
            }
        } else {
            if (right) {
                // 4th quadrant
                double y2 = c1.y - m * (xmax - c1.x);

                if (y2 > ymin) {
                    return Crossing{Side::RIGHT, xmax, clamp(y2, ymin, ymax)};
                } else {
                    double x2 = c1.x + (c1.y - ymin) / m;
                    return Crossing{Side::BOTTOM, clamp(x2, xmin, xmax), ymin};
                }
            } else {
                // 3rd quadrant
                double y2 = c1.y - m * (c1.x - xmin);

                if (y2 > ymin) {
                    return Crossing{Side::LEFT, xmin, clamp(y2, ymin, ymax)};
                } else {
                    double x2 = c1.x - (c1.y - ymin) / m;
                    return Crossing{Side::BOTTOM, clamp(x2, xmin, xmax), ymin};
                }
            }
        }

    }

    bool Box::contains(const Box& b) const {
        return b.xmin >= xmin && b.xmax <= xmax && b.ymin >= ymin && b.ymax <= ymax;
    }

    bool Box::contains(const Coordinate &c) const {
        return c.x >= xmin && c.x <= xmax && c.y >= ymin && c.y <= ymax;
    }

    bool Box::strictly_contains(const Coordinate &c) const {
        return c.x > xmin && c.x < xmax && c.y > ymin && c.y < ymax;
    }

    std::ostream &operator<<(std::ostream &os, const Box &b) {
        os << "POLYGON ((";
        os << b.xmin << " " << b.ymin << ", ";
        os << b.xmax << " " << b.ymin << ", ";
        os << b.xmax << " " << b.ymax << ", ";
        os << b.xmin << " " << b.ymax << ", ";
        os << b.xmin << " " << b.ymin << "))";
        return os;
    }

}
