"""Example: Conics from Circles Animation

Let C be a circle in the Euclidean plane with center F2 and let F1 be a point
inside C. Then the locus of the centers of all circles that go through F1 and
touch C is an ellipse with foci F1 and F2.
"""

#############################################
# Setup
#############################################

# [setup-1]
# Import necessary libraries and modules
import bpy
import numpy as np

import ddg
from ddg.geometry import euclidean_models
from ddg.geometry.subspaces import subspace_from_affine_points as sfap
from ddg.visualization import blender

# [setup-1]

# [setup-2]
# Clear existing objects and materials in Blender scene
blender.object.clear()
blender.material.clear()

EUC2D = euclidean_models.ProjectiveModel(2)
# [setup-2]


#############################################
# Calculations
#############################################

# [calculations-1]
# Function to calculate a smaller circle centered on the ellipse
def small_circle(theta, ellipse, F1):
    """Returns smaller circle with center on the ellipse and tangent to big circle.

    Parameters
    ----------
    theta : float
        Plugged into a parametrization of the ellipse to find center of small circle.
        2 pi - periodic.
    ellipse : Quadric
        The ellipse from the construction
    F1 : Point

    Returns
    -------
    ddg.geometry.spheres.SphereLike
        Euclidean sphere
    """
    sn = ddg.to_smooth_net(ellipse, affine=True)
    c = sfap(sn(theta))
    r = np.linalg.norm(c.affine_point - F1.affine_point)
    return EUC2D.sphere(c, r)


# [calculations-1]

# [calculations-2]
# Define the properties of the big circle and the foci F1 and F2
big_circle = EUC2D.sphere(sfap([1, 1]), 2)
F2 = big_circle.center
big_r = big_circle.radius
F1 = sfap([2, 1.5])
# The fact that a=big_r/2 can be seen by considering a single circle with
# center on the line connecting F1 and F2.
ellipse = EUC2D.ellipse_from_foci(F1, F2, big_r / 2)
# [calculations-2]


#############################################
# Visualization
#############################################

# [visualization-1]
# Define visualization settings
SAMPLING = [0.1, 100, "c"]
SCENE = bpy.context.scene
WORLD = bpy.data.worlds["World"]

# Create and configure camera and rendering settings
camera = blender.camera.camera(
    type_="ORTHO",
    location=(F2.embed().affine_point + np.array([0.0, 0.0, 2.0])),
)
blender.camera.look_at_point(camera, F2.embed().affine_point)
# Change this if the radius of the big circle changes
camera.data.ortho_scale = 5.0
SCENE.camera = camera

SCENE.render.resolution_x = SCENE.render.resolution_y = 1024
# Make world background transparent
SCENE.render.film_transparent = True
# Set render engine to Eevee because it's orders of magnitude faster and
# suffices for this application
SCENE.render.engine = "BLENDER_EEVEE"
SCENE.view_settings.view_transform = "Raw"
# Set world color to pure white and (emission?) strength to 0.5
WORLD.node_tree.nodes["Background"].inputs[0].default_value = (1, 1, 1, 1)
WORLD.node_tree.nodes["Background"].inputs[1].default_value = 1
# [visualization-1]

# [visualization-2]
# Create materials and define colors
COL_BIG_CIRCLE = np.array([168, 32, 26]) / 255
COL_SMALL_CIRCLE = np.array([236, 154, 41]) / 255
COL_ELLIPSE = np.array([15, 136, 141]) / 255
mat_small_circle = blender.material.material(
    color=COL_SMALL_CIRCLE, name="Small circle"
)
mat_big_circle = blender.material.material(
    color=COL_BIG_CIRCLE, name="Big circle", alpha=0.5
)
mat_ellipse = blender.material.material(color=COL_ELLIPSE, name="Ellipse")
# [visualization-2]

# [visualization-3]
# Render regular objects
ddg.to_blender_object_helper(
    EUC2D.sphere_to_quadric(big_circle).embed(),
    sampling=SAMPLING,
    material=mat_big_circle,
)
ddg.to_blender_object_helper(F2.embed(), sphere_radius=0.04, material=mat_big_circle)
ddg.to_blender_object_helper(F1.embed(), sphere_radius=0.04, material=mat_ellipse)
ddg.to_blender_object_helper(ellipse.embed(), sampling=SAMPLING, material=mat_ellipse)
# [visualization-3]


#############################################
# Animation
#############################################

# [animation-1]
# Function to generate small circles during animation
def small_circle_callback(theta):
    sc = small_circle(theta, ellipse, F1)
    return [
        ddg.to_blender_object_helper(
            EUC2D.sphere_to_quadric(sc).embed(),
            sampling=SAMPLING,
            link=False,
            material=mat_small_circle,
            curve_properties={"bevel_depth": 0.0125},
        ),
        ddg.to_blender_object_helper(
            sc.center.embed(), sphere_radius=0.04, material=mat_small_circle
        ),
    ]


# [animation-1]

# [animation-2]
# Add animation properties and keyframes
blender.props.add_props_with_callback(
    blender.props.clear_callback("small circle", small_circle_callback), ["theta"], 0.0
)

FPS = 50
blender.animation.clear_animation_data(SCENE)
blender.animation.set_keyframe(SCENE, 0 * FPS, "theta", 0.0)
blender.animation.set_keyframe(SCENE, 3 * FPS, "theta", 2 * np.pi)
blender.animation.set_end_frame(3 * FPS)
# [animation-2]


from testing.tests.examples.blender.snapshot import opt_in  # noqa: E402

opt_in()
