import math

import numpy as np


def distance_3d(point1, point2):
    """Calculates distance between 3D points point1 and point2"""
    x1, y1, z1 = point1
    x2, y2, z2 = point2

    return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2)


# function generated by Phind
def find_c_coordinates(point1, point2, dist):
    """"
    Finds coordinates of a point (located on the vector from point1 to point2) only by knowing its distance from point1
    """
    a = np.array([point1[0], point1[1], point1[2]])
    b = np.array([point2[0], point2[1], point2[2]])

    vec_a2b = b - a
    norm_a2b = vec_a2b / np.linalg.norm(vec_a2b)
    dist_a2c = norm_a2b * dist
    c = a + dist_a2c
    c = np.round(c).astype(int)

    return tuple(c)


def get_linear_gradient(colors, nb_colors, return_format="rgb"):
    """Returns a gradient based on colors given"""
    if return_format != "hex" and return_format != "rgb":
        raise Exception("Invalid format chosen")
    if nb_colors < 3 or len(colors) > nb_colors:
        raise Exception("Invalid number of colors wanted")
    if len(colors) < 2:
        raise Exception("Invalid colors (should contains at least two colors)")
    if len(colors) == nb_colors:
        return colors

    if any(isinstance(color, str) for color in colors):
        for i, color in enumerate(colors):
            if isinstance(color, str):
                color = color[1:]
                colors[i] = (int(color[:2], 16), int(color[2:4], 16), int(color[4:6], 16))

    gradient = []
    new_points = nb_colors - len(colors)

    distances = [distance_3d(colors[i], colors[i + 1]) for i in range(len(colors) - 1)]
    global_distance = sum(distances)
    part_size = global_distance / (new_points + 1)

    cum_distance = 0
    cum_part_size = part_size

    for i in range(len(colors) - 1):
        a = colors[i]
        b = colors[i + 1]

        gradient.append(a)

        cum_distance += distances[i]

        while cum_distance > cum_part_size:
            dist_from_cur_point = cum_part_size - (cum_distance - distances[i])

            gradient.append(find_c_coordinates(a, b, dist_from_cur_point))

            cum_part_size += part_size

    gradient.append(colors[-1])

    if return_format == "hex":
        gradient = [f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}" for color in gradient]

    return gradient
