/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file contour_distance.cpp Various auxiliary functions
 * non-inline functions from contour_basics.h.
 * @ingroup contour_files
 */

#include <cassert>
#include <vector>
#include <algorithm>
#include "contour_edge.h"

namespace contour {

/** Calculates how the line `A`->`B` crosses the rectangle.
*
* If no crossing, an invalid range is returned.
* If there is a crossing, "pos" values are returned, as follows.
* `0` corresponds to `A`, `1` corresponds to `B`, values in between
* correspond to the section `A`->`B` and outside likewise.
*/
Range Block::Cut(const XY &A, const XY &B) const
{
    Range ret;
    bool have_one = false, have_two = false;
    if (IsInvalid() || A.test_equal(B)) goto invalid;
    if (test_equal(A.x, B.x)) {
        if (!x.IsWithinBool(A.x)) goto invalid;
        ret.from = (y.from-A.y)/(B.y-A.y);
        ret.till = (y.till-A.y)/(B.y-A.y);
        goto valid;
    }
    if (test_equal(A.y, B.y)) {
        if (!y.IsWithinBool(A.y)) goto invalid;
        ret.from = (x.from-A.x)/(B.x-A.x);
        ret.till = (x.till-A.x)/(B.x-A.x);
        goto valid;
    }
    if (y.IsWithinBool((B.y-A.y)/(B.x-A.x)*(x.from-A.x)+A.y)) {
        ret.from = (x.from-A.x)/(B.x-A.x);
        have_one = true;
    }
    if (y.IsWithinBool((B.y-A.y)/(B.x-A.x)*(x.till-A.x)+A.y)) {
        (have_one ? ret.till : ret.from) = (x.till-A.x)/(B.x-A.x);
        if (have_one) goto valid;
        have_one = true;
    }
    //It may be that the cut crosses a vertex. In this case we need
    //that x match, which is more different
    if (x.IsWithinBool((B.x-A.x)/(B.y-A.y)*(y.from-A.y)+A.x)) {
        (have_one ? ret.till : ret.from) = (y.from-A.y)/(B.y-A.y);
        if (have_one) have_two = true;
        have_one = true;
    }
    if (x.IsWithinBool((B.x-A.x)/(B.y-A.y)*(y.till-A.y)+A.x)) {
        const double p = (y.till-A.y)/(B.y-A.y);
        if (have_two) {
            //if p is further away from the first cp than
            //the second cp, we make p the second cp
            if (fabs(ret.till-ret.from)<fabs(p-ret.from))
                ret.till = p;
            goto valid;
        }
        (have_one ? ret.till : ret.from) = p;
        if (have_one) goto valid;
        _ASSERT(0);
    }
    if (have_two) goto valid;
    if (have_one) {
        //at the vertex
        ret.till = ret.from;
        goto valid;
    }
invalid:
    ret.MakeInvalid();
    return ret;
valid:
    if (ret.from > ret.till)
        std::swap(ret.from, ret.till);
    return ret;
}

/** Calculates how the line `A`->`B` crosses the rectangle.
*
* If no crossing, nothing is returned.
* If there is a crossing, 2 coordinates are returned in the order
* so that [0]->[1] is the same direction as A->B. If the line just
* touches a vertex [0]==[1] is retruned.
*/
std::optional<std::array<XY, 2>> Block::CutEx(const XY &A, const XY &B) const
{
    std::optional<std::array<XY, 2>> ret = std::array<XY, 2>();
    bool have_one = false, have_two = false;
    if (IsInvalid() || A.test_equal(B)) goto invalid;
    if (test_equal(A.x, B.x)) {
        if (!x.IsWithinBool(A.x)) goto invalid;
        ret.value()[0] = { A.x, y.from };
        ret.value()[1] = { A.x, y.till };
        goto valid;
    }
    if (test_equal(A.y, B.y)) {
        if (!y.IsWithinBool(A.y)) goto invalid;
        ret.value()[0] = { x.from, A.y };
        ret.value()[1] = { x.till, A.y };
        goto valid;
    }
    if (y.IsWithinBool((B.y-A.y)/(B.x-A.x)*(x.from-A.x)+A.y)) {
        ret.value()[0] = { x.from, (B.y-A.y)/(B.x-A.x)*(x.from-A.x)+A.y };
        have_one = true;
    }
    if (y.IsWithinBool((B.y-A.y)/(B.x-A.x)*(x.till-A.x)+A.y)) {
        ret.value()[have_one] = { x.till, (B.y-A.y)/(B.x-A.x)*(x.till-A.x)+A.y };
        if (have_one) goto valid;
        have_one = true;
    }
    //It may be that the cut crosses a vertex. In this case we need
    //that x match, which is more different
    if (x.IsWithinBool((B.x-A.x)/(B.y-A.y)*(y.from-A.y)+A.x)) {
        ret.value()[have_one] = { (B.x-A.x)/(B.y-A.y)*(y.from-A.y)+A.x, y.from };
        if (have_one) have_two = true;
        have_one = true;
    }
    if (x.IsWithinBool((B.x-A.x)/(B.y-A.y)*(y.till-A.y)+A.x)) {
        const XY p = { (B.x-A.x)/(B.y-A.y)*(y.till-A.y)+A.x , y.till };
        if (have_two) {
            //if p is further away from the first cp than
            //the second cp, we make p the second cp
            if (ret.value()[0].DistanceSqr(ret.value()[1]) <
                ret.value()[0].DistanceSqr(p))
                ret.value()[1] = p;
            goto valid;
        }
        ret.value()[have_one] = p;
        if (have_one) goto valid;
        _ASSERT(0);
    }
    if (have_two) goto valid;
    if (have_one) {
        //at the vertex
        ret.value()[1] = ret.value()[0];
        goto valid;
    }
invalid:
    ret.reset();
    return ret;
valid:
    //Make so that [0]->[1] points the same dir as A->B
    if ((fabs(A.x-B.x)<=fabs(A.y-B.y) && ((A.y<B.y) != (ret.value()[0].y<ret.value()[1].y))) ||
        (fabs(A.x-B.x)>=fabs(A.y-B.y) && ((A.x<B.x) != (ret.value()[0].x<ret.value()[1].x))))
            std::swap(ret.value()[0], ret.value()[1]);
    return ret;
}

/** Returns the relation of the three points `a`, `b`, `c`.
* @ingroup contour_internal
*/
ETriangleDirType triangle_dir(XY a, XY b, XY c) noexcept
{
    if (a == b) return b==c ? ALL_EQUAL : A_EQUAL_B;
    if (a == c) return A_EQUAL_C;
    if (b == c) return B_EQUAL_C;
    //Decide if we divide by x or y coordinates
    if (fabs(a.x - b.x) < fabs(a.y - b.y)) {
        const double m = (a.x - b.x)/(a.y - b.y);
        double cx = m*(c.y-a.y) + a.x; //(cx, c.y) is a point on the a-b line
        if (test_equal(cx, c.x)) return IN_LINE;
        return ((c.x < cx) ^ (a.y < b.y)) ? COUNTERCLOCKWISE : CLOCKWISE;
    } else {
        const double m = (a.y - b.y)/(a.x - b.x);
        double cy = m*(c.x-a.x) + a.y; //(c.x, cy) is a point on the a-b line
        if (test_equal(cy, c.y)) return IN_LINE;
        return ((c.y < cy) ^ (a.x < b.x)) ? CLOCKWISE : COUNTERCLOCKWISE;
    }
}


/** Returns the *fake angle* of the `base`->`A` and the `base`->`B` segments.
* @ingroup contour_internal
*
* In order to save computation we do not use the angles in radian
* merely its sine, since we only do comparison with these values, no summation
* or other arithmetics. We call this *fake angle*.
* Values between [0..1] thus correspond to radians [0..PI/2], values
* between [1..2] to radians [PI/2..PI], etc.
*
* We return an angle that follows clockwise and can be larger than 180 degrees.
* So if `B` is just a bit counterclockwise from `A`, we get a value close to 360
* degree (that is the fake value 4).
* @param base The base of the angle.
* @param A one end
* @param B the other end.
* @returns The fake angle from 'A' to 'B'. -1 on error (degenerate cases)
*/
double angle(const XY &base, const XY &A, const XY &B) noexcept
{
    bool clockwise;
    switch (triangle_dir(base, A, B)) {
    case IN_LINE:
    case CLOCKWISE:
        clockwise = true;
        break;
    case COUNTERCLOCKWISE:
        clockwise = false;
        break;
    case B_EQUAL_C:
        return 0;  //zero degrees
    default:
        return -1; //error one of them equals to another
    };
    double cos = (A-base).DotProduct(B-base) / base.Distance(A) / base.Distance(B);
    cos = std::min(cos, 1.);
    cos = std::max(cos, -1.);
    if (clockwise)
        return 1-cos; //gives [0..2]
    else
        return cos+3; //gives (2..4)
}

/** Returns the (clockwise) *fake angle* of the `base`->`A` compared to a horizontal line.
* @ingroup contour_internal
*
* In order to save computation we do not use the angles in radian
* merely its sine, since we only do comparison with these values, no summation
* or other arithmetics. We call this *fake angle*.
* Values between [0..1] thus correspond to radians [0..PI/2], values
* between [1..2] to radians [PI/2..PI], etc.
*
* We return an angle that follows clockwise and can be larger than 180 degrees.
* @param base The base of the angle.
* @param A The point of which we calculate the angle for.
* @returns The fake angle of 'base'->'A' from a horizontal line going through 'base'.
*          -1 on error ('base'=='A')
*/
double angle_to_horizontal(const XY &base, const XY &A) noexcept
{
    if (base.test_equal(A))
        return -1;
    double cos = XY(100,0).DotProduct(A-base) / 100 / base.Distance(A);
    //normalize to avoid errors from imprecision above
    cos = std::min(+1., cos);
    cos = std::max(-1., cos);
    double ret = (A.y>=base.y) 
        ? 1-cos  //gives [0..2]
        : cos+3; //gives [2..4)
    if (ret>=4) return 0;
    return ret;
}

/** Calculates `pos` on a straight segment.
*
* @param [in] M Start of the segment.
* @param [in] N End of the segment.
* @param [in] p Point to calculate pos for. Assumed to be on the line of `M`->`N`.
* @returns pos value. [0..1] if p is inside MN, other value if not.
*/
double point2pos_straight(const XY &M, const XY&N, const XY &p) noexcept
{
    if (M.test_equal(N)) {
        _ASSERT(M.test_equal(p));
        return 0;
    }
    if (fabs(M.x-N.x) > fabs(M.y-N.y)) {
        double t = (p.x-M.x)/(N.x-M.x);
        if (test_equal(p.y, M.y + (N.y-M.y)*t)) return t;
    } else {
        double t = (p.y-M.y)/(N.y-M.y);
        if (test_equal(p.x, M.x + (N.x-M.x)*t)) return t;
    }
    _ASSERT(0);
    return -1;
}

}  //namespace contour


