#include "contour_path.h"

std::pair<double, double>
contour::Tangent4Hull(bool left, const Edge &e1, const Edge &e2) noexcept {
    double tmp[2];
    (void)tmp;
    _ASSERT(e1.InflectionPoints(tmp)==0);
    _ASSERT(e2.InflectionPoints(tmp)==0);
    if (e1.IsStraight() && e2.IsStraight()) {
        //fast track if they connect
        if (e1.GetEnd()==e2.GetStart()) {
            if (max_clockwise(left, e1.GetStart(), e1.GetEnd(), e2.GetEnd())) return {1, 0}; //the two lines are already convex
            return {0, 1}; //replace both edges with a single line
        }
        //These are true, if e2.start is better (counterclockwise for left==true) than e2.end 
        const bool s1to2 = max_clockwise(left, e1.GetStart(), e2.GetStart(), e2.GetEnd()); //e2 seen from e1 start
        const bool e1to2 = max_clockwise(left, e1.GetEnd(), e2.GetStart(), e2.GetEnd());   //e2 seen from e1 end
        //Below we should apply reverse clockwisedness. 
        //But instead we replace start and end, so that on equality we get 'false' and hence prefer start
        const bool s2to1 = !max_clockwise(!left, e2.GetStart(), e1.GetEnd(), e1.GetStart());//e1 seen from e2 start (reverse clockwiseness)
        const bool e2to1 = !max_clockwise(!left, e2.GetEnd(), e1.GetEnd(), e1.GetStart());  //e1 seen from e2 end (reverse clockwiseness)
        if (s1to2==e1to2 && s2to1==e2to1)
            return {s2to1 ? 0.0 : 1.0, s1to2 ? 0.0 : 1.0};
        if (s1to2==e1to2)
            return {(s1to2 ? s2to1 : e2to1) ? 0.0 : 1.0,
                    s1to2 ? 0.0 : 1.0};
        if (s2to1==e2to1)
            return {s2to1 ? 0.0 : 1.0,
                    (s2to1 ? s1to2 : e1to2) ? 0.0 : 1.0};
        _ASSERT(0); //they probably cross, both segments see the two endpoints other in 
                    //different directions from its own endpoints
        return {0, 1}; //no better idea --- from the e1.start to e2.end
    }
    if (e1.IsStraight()) {
        //if e1 is straight, but e2 is not
        auto st = e2.TangentFromNoInflection<false>(e1.GetStart()); //take backward tangent
        auto en = e2.TangentFromNoInflection<false>(e1.GetEnd());

        //check if the end of e2 is better than any of the tangents 
        if (st.first) {
            if (max_clockwise(!left, e1.GetStart(), e2.Pos2Point(st.second), e2.GetStart()))
                st.second = 0;
            if (max_clockwise(!left, e1.GetStart(), e2.Pos2Point(st.second), e2.GetEnd()))
                st.second = 1;
        }
        if (en.first) {
            if (max_clockwise(!left, e1.GetEnd(), e2.Pos2Point(en.second), e2.GetStart()))
                en.second = 0;
            if (max_clockwise(!left, e1.GetEnd(), e2.Pos2Point(en.second), e2.GetEnd()))
                en.second = 1;
        }
        if (e1.GetEnd()==e2.GetStart()) {
            if (st.second==0) return {1.0, en.second};
            if (en.second==0) return {0.0, st.second};
        }
        const bool from_st = max_clockwise(!left, e2.Pos2Point(st.second), e1.GetStart(), e1.GetEnd()); //e1 seen from st touchpoint
        const bool from_en = max_clockwise(!left, e2.Pos2Point(en.second), e1.GetStart(), e1.GetEnd()); //e1 seen from en touchpoint
        _ASSERT(from_st == from_en);
        if (from_st != from_en)
            return {0, 1}; //no better idea
        return {from_st ? 0.0 : 1.0, from_st ? st.second : en.second};
    }
    if (e2.IsStraight()) {
        //if e2 is straight, but e1 is not
        //st and en are positions on 'e1': tangents from e2.start and s2.end
        auto st = e1.TangentFromNoInflection<true>(e2.GetStart()); //take forward tangent
        auto en = e1.TangentFromNoInflection<true>(e2.GetEnd());

        //check if the end of e2 is better than any of the tangents 
        if (st.first) {
            if (max_clockwise(left, e2.GetStart(), e1.Pos2Point(st.second), e1.GetStart()))
                st.second = 0;
            if (max_clockwise(left, e2.GetStart(), e1.Pos2Point(st.second), e1.GetEnd()))
                st.second = 1;
        }
        if (en.first) {
            if (max_clockwise(left, e2.GetEnd(), e1.Pos2Point(en.second), e1.GetStart()))
                en.second = 0;
            if (max_clockwise(left, e2.GetEnd(), e1.Pos2Point(en.second), e1.GetEnd()))
                en.second = 1;
        }
        //Special case: two edges are joined
        if (e1.GetEnd()==e2.GetStart()) {
            if (st.second==1) return {en.second, 1.0};
            if (en.second==1) return {st.second, 0.0};
        }
        const bool from_st = max_clockwise(left, e1.Pos2Point(st.second), e2.GetStart(), e2.GetEnd()); //e1 seen from st touchpoint
        const bool from_en = max_clockwise(left, e1.Pos2Point(en.second), e2.GetStart(), e2.GetEnd()); //e1 seen from en touchpoint
        _ASSERT(from_st == from_en || e1.GetLength()<1 || e2.GetLength()<1); 
        if (from_st != from_en)
            return {0, 1}; //no better idea. This may happen if one of the edges is very short & the other is long
        return {from_st ? st.second : en.second, from_st ? 0.0 : 1.0};
    }

    //these tell if the edges turn clockwise or not
    //They are true if clockwise (invert for left == false)
    struct data {
        const Edge &edge;
        const bool cc;
        double pos;
        data(const Edge &e, bool c) : edge(e), cc(c), pos(0.5) {}
    };
    std::array<data, 2> d = {
        data{e1, !e1.IsStraight() && max_clockwise(left, e1.GetStart(), e1.GetC2(), e1.GetEnd())},
        data{e2, !e2.IsStraight() && max_clockwise(left, e2.GetStart(), e2.GetC2(), e2.GetEnd())}
    };
    double delta;
    do {
        delta = 0;
        double pos = d[1].edge.template TangentFromNoInflection<false>(d[0].edge.Pos2Point(d[0].pos)).second;
        if (max_clockwise(!left, d[0].edge.Pos2Point(d[0].pos), d[1].edge.Pos2Point(pos), d[1].edge.GetStart()))
            pos = 0;
        if (max_clockwise(!left, d[0].edge.Pos2Point(d[0].pos), d[1].edge.Pos2Point(pos), d[1].edge.GetEnd()))
            pos = 1;
        delta += fabs(d[1].pos-pos);
        d[1].pos = pos;
        pos = d[0].edge.template TangentFromNoInflection<true>(d[1].edge.Pos2Point(d[1].pos)).second;
        if (max_clockwise(left, d[1].edge.Pos2Point(d[1].pos), d[0].edge.Pos2Point(pos), d[0].edge.GetStart()))
            pos = 0;
        if (max_clockwise(left, d[1].edge.Pos2Point(d[1].pos), d[0].edge.Pos2Point(pos), d[0].edge.GetEnd()))
            pos = 1;
        delta += fabs(d[0].pos-pos);
        d[0].pos = pos;
    } while (delta>0.00001);
    return {d[0].pos, d[1].pos};
}
