# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 3
#  of the License, or (at your option) any later version.
#
#  This program 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####


from mathutils import Vector
from mathutils.geometry import intersect_point_line


def depth_get(co, ray_start, ray_dir):
    dvec = co - ray_start
    return dvec.dot(ray_dir)


def region_2d_to_orig_and_view_vector(region, rv3d, coord):
    viewinv = rv3d.view_matrix.inverted_safe()
    persinv = rv3d.perspective_matrix.inverted_safe()

    dx = (2.0 * coord[0] / region.width) - 1.0
    dy = (2.0 * coord[1] / region.height) - 1.0

    if rv3d.is_perspective:
        origin_start = viewinv.translation.copy()

        out = Vector((dx, dy, -0.5))

        w = out.dot(persinv[3].xyz) + persinv[3][3]

        view_vector = ((persinv @ out) / w) - origin_start
    else:
        view_vector = -viewinv.col[2].xyz

        origin_start = ((persinv.col[0].xyz * dx) +
                        (persinv.col[1].xyz * dy) +
                        viewinv.translation)

    view_vector.normalize()
    return view_vector, origin_start


def project_co_v3(sctx, co):
    proj_co = sctx.proj_mat @ co.to_4d()
    try:
        proj_co.xy /= proj_co.w
    except Exception as e:
        print(e)

    win_half = sctx.winsize * 0.5
    proj_co[0] = (proj_co[0] + 1.0) * win_half[0]
    proj_co[1] = (proj_co[1] + 1.0) * win_half[1]

    return proj_co.xy



def intersect_boundbox_threshold(sctx, MVP, ray_origin_local, ray_direction_local, bbmin, bbmax):
    local_bvmin = Vector()
    local_bvmax = Vector()
    tmin = Vector()
    tmax = Vector()

    if (ray_direction_local[0] < 0.0):
        local_bvmin[0] = bbmax[0]
        local_bvmax[0] = bbmin[0]
    else:
        local_bvmin[0] = bbmin[0]
        local_bvmax[0] = bbmax[0]

    if (ray_direction_local[1] < 0.0):
        local_bvmin[1] = bbmax[1]
        local_bvmax[1] = bbmin[1]
    else:
        local_bvmin[1] = bbmin[1]
        local_bvmax[1] = bbmax[1]

    if (ray_direction_local[2] < 0.0):
        local_bvmin[2] = bbmax[2]
        local_bvmax[2] = bbmin[2]
    else:
        local_bvmin[2] = bbmin[2]
        local_bvmax[2] = bbmax[2]

    if (ray_direction_local[0]):
        tmin[0] = (local_bvmin[0] - ray_origin_local[0]) / ray_direction_local[0]
        tmax[0] = (local_bvmax[0] - ray_origin_local[0]) / ray_direction_local[0]
    else:
        tmin[0] = sctx.depth_range[0]
        tmax[0] = sctx.depth_range[1]

    if (ray_direction_local[1]):
        tmin[1] = (local_bvmin[1] - ray_origin_local[1]) / ray_direction_local[1]
        tmax[1] = (local_bvmax[1] - ray_origin_local[1]) / ray_direction_local[1]
    else:
        tmin[1] = sctx.depth_range[0]
        tmax[1] = sctx.depth_range[1]

    if (ray_direction_local[2]):
        tmin[2] = (local_bvmin[2] - ray_origin_local[2]) / ray_direction_local[2]
        tmax[2] = (local_bvmax[2] - ray_origin_local[2]) / ray_direction_local[2]
    else:
        tmin[2] = sctx.depth_range[0]
        tmax[2] = sctx.depth_range[1]

    # `va` and `vb` are the coordinates of the AABB edge closest to the ray #
    va = Vector()
    vb = Vector()
    # `rtmin` and `rtmax` are the minimum and maximum distances of the ray hits on the AABB #

    if ((tmax[0] <= tmax[1]) and (tmax[0] <= tmax[2])):
        rtmax = tmax[0]
        va[0] = vb[0] = local_bvmax[0]
        main_axis = 3
    elif ((tmax[1] <= tmax[0]) and (tmax[1] <= tmax[2])):
        rtmax = tmax[1]
        va[1] = vb[1] = local_bvmax[1]
        main_axis = 2
    else:
        rtmax = tmax[2]
        va[2] = vb[2] = local_bvmax[2]
        main_axis = 1

    if ((tmin[0] >= tmin[1]) and (tmin[0] >= tmin[2])):
        rtmin = tmin[0]
        va[0] = vb[0] = local_bvmin[0]
        main_axis -= 3
    elif ((tmin[1] >= tmin[0]) and (tmin[1] >= tmin[2])):
        rtmin = tmin[1]
        va[1] = vb[1] = local_bvmin[1]
        main_axis -= 1
    else:
        rtmin = tmin[2]
        va[2] = vb[2] = local_bvmin[2]
        main_axis -= 2

    if (main_axis < 0):
        main_axis += 3

#ifdef IGNORE_BEHIND_RAY
    depth_max = depth_get(local_bvmax, ray_origin_local, ray_direction_local)
    if (depth_max < sctx.depth_range[0]):
        return False
#endif

    if (rtmin <= rtmax):
        # if rtmin < rtmax, ray intersect `AABB` #
        return True

    if (ray_direction_local[main_axis] < 0.0):
        va[main_axis] = local_bvmax[main_axis]
        vb[main_axis] = local_bvmin[main_axis]

    else:
        va[main_axis] = local_bvmin[main_axis]
        vb[main_axis] = local_bvmax[main_axis]

    win_half = sctx.winsize * 0.5

    scale = abs(local_bvmax[main_axis] - local_bvmin[main_axis])

    va2d = Vector((
        (MVP[0].xyz.dot(va) + MVP[0][3]),
        (MVP[1].xyz.dot(va) + MVP[1][3]),
    ))

    vb2d = Vector((
        (va2d[0] + MVP[0][main_axis] * scale),
        (va2d[1] + MVP[1][main_axis] * scale),
    ))

    depth_a = MVP[3].xyz.dot(va) + MVP[3][3]
    depth_b = depth_a + MVP[3][main_axis] * scale

    va2d /= depth_a
    vb2d /= depth_b

    va2d[0] = (va2d[0] + 1.0) * win_half[0]
    va2d[1] = (va2d[1] + 1.0) * win_half[1]
    vb2d[0] = (vb2d[0] + 1.0) * win_half[0]
    vb2d[1] = (vb2d[1] + 1.0) * win_half[1]

    p, fac = intersect_point_line(sctx.mval, va2d, vb2d)
    if fac < 0.0:
        return (sctx.mval - va2d).length_squared < sctx._dist_px_sq
    elif fac > 1.0:
        return (sctx.mval - vb2d).length_squared < sctx._dist_px_sq
    else:
        return (sctx.mval - p).length_squared < sctx._dist_px_sq


def intersect_ray_segment_fac(v0, v1, ray_direction, ray_origin):
    a = v1 - v0
    t = v0 - ray_origin
    n = a.cross(ray_direction)
    nlen = n.length_squared

    # if (nlen == 0.0f) the lines are parallel, has no nearest point, only distance squared.*/
    if nlen == 0.0:
        # Calculate the distance to the nearest point to origin then #
        return a.dot(ray_direction) < 0
    else:
        c = n - t
        cray = c.cross(ray_direction)
        return cray.dot(n) / nlen

