namespace SideScrollerGame.Sim.Math;

public static class FixPointUtil
{
    public static long DistancePointToSegmentSquared(FixPointVector2 point, FixPointVector2 segmentStart, FixPointVector2 segmentEnd)
    {
        var segment = segmentEnd - segmentStart;
        var toStart = point - segmentStart;

        var segmentLengthSquared = FixPointVector2.DotLong(segment, segment);
        if (segmentLengthSquared == 0L)
            return FixPointVector2.DotLong(toStart, toStart);

        var projection = FixPointVector2.DotLong(toStart, segment);
        if (projection <= 0)
            return FixPointVector2.DotLong(toStart, toStart);

        FixPointVector2 toPointOnSegment;
        if (projection >= segmentLengthSquared)
            toPointOnSegment = point - segmentEnd;
        else
            toPointOnSegment = toStart - segment * FixPoint16.FromRational(projection, segmentLengthSquared);

        return FixPointVector2.DotLong(toPointOnSegment, toPointOnSegment);
    }

    public static FixPoint16 DistancePointToSegment(FixPointVector2 point, FixPointVector2 segmentStart, FixPointVector2 segmentEnd)
    {
        return FixPoint16.SqrtLong(DistancePointToSegmentSquared(point, segmentStart, segmentEnd));
    }

    public static bool LineLineIntersection(FixPointVector2 line1Start, FixPointVector2 line1End, FixPointVector2 line2Start, FixPointVector2 line2End, out FixPointVector2 intersectionPoint)
    {
        intersectionPoint = default;

        var s1 = line1End - line1Start;
        var s2 = line2End - line2Start;

        var det = (FixPoint16Long)(-s2.m_X) * s1.m_Y + (FixPoint16Long)(s1.m_X) * s2.m_Y;
        if (FixPoint16Long.Abs(det) < s_IntersectionEpsilon)
            return false;

        var t = ((FixPoint16Long)s2.m_X * (line1Start.m_Y - line2Start.m_Y) - (FixPoint16Long)s2.m_Y * (line1Start .m_X - line2Start.m_X)) / det;
        var px = line1Start.m_X + (t * s1.m_X);
        var py = line1Start.m_Y + (t * s1.m_Y);

        if (px.m_Value > s_LineIntersectionMax || px.m_Value < s_LineIntersectionMin || py.m_Value > s_LineIntersectionMax || py.m_Value < s_LineIntersectionMin)
            return false;

        intersectionPoint = new FixPointVector2(FixPoint16.FromValue(px.m_Value), FixPoint16.FromValue(py.m_Value));
        return true;
    }

    public static FixPoint16 MultiplyClamped(FixPoint16 a, FixPoint16 b)
    {
        var iResult = (((long)a.m_Value * b.m_Value) + FixPoint16.c_Half) >> FixPoint16.c_Shift;

        if (iResult < FixPoint16.c_LongMin)
            iResult = FixPoint16.c_LongMin;
        else if (iResult > FixPoint16.c_LongMax)
            iResult = FixPoint16.c_LongMax;

        return new() { m_Value = (int)iResult };
    }

    private static readonly FixPoint16 s_IntersectionEpsilon = FixPoint16.Epsilon * 30;
    private static readonly long s_LineIntersectionMax = FixPoint16.c_LongMax / 4;
    private static readonly long s_LineIntersectionMin = FixPoint16.c_LongMin / 4;
}
