"""Möbius pencil of circles example for use in a tutorial in the docs.

Portions of this file are included in the docs. Comments of the form "# [...]" Mark
sections that should become code blocks in the docs. Keep this in mind when editing this
file!

These markers are not necessary when including a whole function/class/method etc.
"""

# [imports]
import numpy as np

import ddg
from ddg.blender.collection import collection
from ddg.blender.material import material

# [imports]


#
# BLENDER COLLECTIONS
# -------------------


# [clear]
ddg.blender.scene.clear(remove_collections=True)
ddg.blender.material.clear()
# [clear]


# [collection setup]
# for objects that were created for didactical purposes
example_coll = collection("example stuff")

# for objects belonging to the 3D moebius picture
mob_geo_coll = collection(
    "Moebius geometry",
    children=[
        ["lines", "points on line", "points on polar line"],
        "spherical circles",
        "polar spherical circles",
    ],
)

# for objects belonging to the 2D euclidean picture
euc_geo_coll = collection(
    "Euclidean geometry", children=["Euclidean circles", "orthogonal Euclidean circles"]
)

# for objects belonging to the light projection picture
projection_coll = collection("projection with light")

# for the slider function
slider_circles_coll = collection("slider circles")
# [collection setup]

# ------------------------------------------------------------

#
# BLENDER MATERIALS
# -----------------


# [materials]

material(name="s2", color=(0.413, 0.828, 0.990), specular=0.4, roughness=0.6, alpha=0.5)

material(name="euc plane", color=(0, 0, 0), specular=0.5, roughness=1, alpha=1)

material(
    name="polar spherical circle",
    color=(1, 0.533, 0.044),
    specular=0.1,
    roughness=1,
    alpha=1,
)

material(
    name="spherical circle", color=(0.029, 0.139, 1), specular=0.1, roughness=1, alpha=1
)

material(
    name="polar planar circle",
    color=(1, 0.533, 0.044),
    specular=0.1,
    roughness=0.9,
    alpha=1,
)

material(
    name="planar circle", color=(0.029, 0.139, 1), specular=1, roughness=1, alpha=1
)

# [materials]

# ------------------------------------------------------------

#
# SPHERE S2
# ---------

# [create sphere as quadric]
s2 = ddg.geometry.Quadric(np.diag([1, 1, 1, -1]))
# [create sphere as quadric]

# [display sphere]
bobj_s2 = ddg.blender.convert(s2, "s2", material="s2", collection=mob_geo_coll)
ddg.blender.mesh.shade_smooth(bobj_s2)
# [display sphere]


# ------------------------------------------------------------


#
# LINE L THROUGH S2
# -----------------

# [create line as subspace]
# line through S2
p1, p2 = np.array([-4, 0, 0.5, 1]), np.array([4, 0, 0.5, 1])
#
# p1, p2 = np.array([-1, 1, 0, 1]), np.array([1, 1, 0, 1])
l = ddg.geometry.Subspace(p1, p2)
# [create line as subspace]

# [line to smooth net]
# orthonormalize and center l
l = l.orthonormalize_and_center((p1 + p2) / 2)

# create smooth net
l_net = ddg.to_smooth_net(l, domain=[[-4, 4]], affine=True, convex=True)
# [line to smooth net]

# [line to blender object]
# to blender
bobj_l = ddg.blender.convert(
    l,
    "line",
    collection=mob_geo_coll.children[0],
    material="line",
)
bobj_l.data.bevel_depth = 0.01
# [line to blender object]


# -------------------------------------------------------------


#
# SPHERICAL AND PLANAR CIRCLES
# ----------------------------


#
# Spherical Circles
#

# [get a point]
# get a point
pt = l_net(2)
# [get a point]

# [homogenize point]
# homogenize pt
pt_homogenized = ddg.math.projective.homogenize(pt)
# [homogenize point]

# [convert point to subspace]
# convert point to subspace
pt_projective = ddg.geometry.Subspace(pt_homogenized)
# [convert point to subspace]

# [display point]
# display point in blender
bobj_pt = ddg.blender.vertices(
    pt_projective,
    "point",
    radius=0.05,
    material="point",
    collection=example_coll,
)
# [display point]


# [calculate polar plane]
# calculate its polar plane
plane = s2.polarize(pt_projective)
# [calculate polar plane]

# [calculate intersection circle]
# calculate intersection circle
circ_on_sphere = ddg.geometry.meet(plane, s2)
# [calculate intersection circle]

# [check for empty intersection]
# check if intersection is not empty
if circ_on_sphere.dimension != -1:
    bobj_circ_on_sphere = ddg.blender.convert(
        circ_on_sphere,
        "spherical circle",
        collection=example_coll,
        material="spherical circle",
    )
    bobj_circ_on_sphere.data.bevel_depth = 0.005
# [check for empty intersection]


#
# Planar Circles
#

# [stereographic project and display circle]
# stereographic project
euc_plane = ddg.geometry.subspace_from_affine_points_and_directions(
    [[0, 0, 0]], [[1, 0, 0], [0, 1, 0]]
)
circ_on_plane = ddg.geometry.stereographic_project(circ_on_sphere, euc_plane)

# convert to blender
bobj_circ_on_plane = ddg.blender.convert(
    circ_on_plane,
    "planar circle",
    collection=example_coll,
    material="planar circle",
)
bobj_circ_on_plane.data.bevel_depth = 0.005

bobj_euc_plane = ddg.blender.convert(
    euc_plane,
    "euc_plane",
    material="euc plane",
    collection=euc_geo_coll,
)
# [stereographic project and display circle]


# [stereographic project with a light]
# stereographic project
light = ddg.blender.light.light(
    type_="POINT", location=[0, 0, 1], energy=600, collection=projection_coll
)
light.data.shadow_soft_size = 0

bobj_euc_plane.is_shadow_catcher = True
# [stereographic project with a light]

# -------------------------------------------------------------

#
# Möbius Pencil of Circles
# ------------------------


#
# Spherical and Planar Circles Function
#


def circles_from_point(
    pt,
    pt_name,
    circ_name,
    planar_name,
    pt_coll=None,
    spherical_coll=None,
    planar_coll=None,
    material_spherical=None,
    material_planar=None,
):
    """
    Create the point, the corresp. spherical circle,
    and the corresp. planar circle.

    Parameters
    ----------
    pt : array of length 3

    pt_coll,
    spherical_coll,
    planar_ coll : Blender Collections (default=None)

    material_spherical: string or bpy.tupes.Material
    material_planar: string or bpy.types.Material

    Returns
    -------
    None or list
    """

    # display point in blender
    bobj_pt = ddg.blender.vertices(
        ddg.nets.PointNet(pt),
        pt_name,
        radius=0.05,
        collection=pt_coll,
        material="point",
    )

    # homogenize pt
    pt_homogenized = ddg.math.projective.homogenize(pt)

    # convert point to subspace
    pt_homogenized = ddg.geometry.Subspace(pt_homogenized)

    # calculate its polar plane
    plane = s2.polarize(pt_homogenized)

    # calculate intersection circle
    circ_on_sphere = ddg.geometry.meet(plane, s2)

    # check if intersection is not empty
    if circ_on_sphere.dimension != -1:

        # convert spherical circle to blender
        bobj_circ_on_sphere = ddg.blender.convert(
            circ_on_sphere,
            circ_name,
            collection=spherical_coll,
            material=material_spherical,
        )
        bobj_circ_on_sphere.data.bevel_depth = 0.006

        # stereographic project
        circ_on_plane = ddg.geometry.stereographic_project(circ_on_sphere, euc_plane)

        # convert planar circle to blender
        bobj_circ_on_plane = ddg.blender.convert(
            circ_on_plane,
            planar_name,
            collection=planar_coll,
            material=material_planar,
        )
        bobj_circ_on_plane.data.bevel_depth = 0.006

        return [bobj_pt, bobj_circ_on_sphere, bobj_circ_on_plane]

    else:
        return [bobj_pt]


#
# Wrapper Function for Points on a Line
#


# [get circles for a point]
circles_from_point(
    l_net(1.3),
    "p1.3",
    "circ1.3",
    "plan1.3",
    pt_coll=mob_geo_coll.children[0].children[0],
    spherical_coll=mob_geo_coll.children[1],
    planar_coll=euc_geo_coll.children[0],
    material_spherical="spherical circle",
    material_planar="planar circle",
)
# [get circles for a point]


# Adding Slider


# [add slider]


callback_collection = ddg.blender.collection.collection("callback")


def callback(t):
    ddg.blender.collection.clear([callback_collection], deep=True)
    circles_from_point(
        l_net(t),
        "p_slider",
        "circ_slider",
        "plan_slider",
        pt_coll=callback_collection,
        spherical_coll=callback_collection,
        planar_coll=callback_collection,
    )


ddg.blender.props.add_props_with_callback(callback, ("t",), 1.0)
# [add slider]

#
# Visualize some Circles in Pencil
#


# [sample domain of line]
# LINEAR SAMPLING OF DOMAIN
# this function takes arg3 samples equidistantly of the intervall [arg1,ag2]

sample = np.linspace(-3, 3, 30)
# [sample domain of line]


# [visualize pencil]
# visualize pencil
for i, t in enumerate(sample):
    circles_from_point(
        l_net(t),
        f"p_{i}",
        f"circ_{i}",
        f"plane_{i}",
        pt_coll=mob_geo_coll.children[0].children[0],
        spherical_coll=mob_geo_coll.children[1],
        planar_coll=euc_geo_coll.children[0],
        material_spherical="spherical circle",
        material_planar="planar circle",
    )
# [visualize pencil]


#
# POLAR LINE TO L
# ----------------

# [orthogonal pencil]
# get polar subspace of l
polar_l = s2.polarize(l)

# orthonormalize
polar_l = polar_l.orthonormalize()

# convert to net
polar_l_net = ddg.to_smooth_net(polar_l, domain=[[-4, 4]], affine=True, convex=True)

# convert to blender
bobj_polar = ddg.blender.convert(
    polar_l,
    "polar line",
    collection=mob_geo_coll.children[0],
)
bobj_polar.data.bevel_depth = 0.01

# visualize orthogonal pencil
for i, t in enumerate(sample):
    circles_from_point(
        polar_l_net(t),
        f"polar_p_{i}",
        f"polar_c_{i}",
        f"polar_plane_{i}",
        pt_coll=mob_geo_coll.children[0].children[1],
        spherical_coll=mob_geo_coll.children[2],
        planar_coll=euc_geo_coll.children[1],
        material_spherical="polar spherical circle",
        material_planar="polar planar circle",
    )
# [orthogonal pencil]

# -------------------------------------------------------------

#
# CAMERA AND LIGHT
#

# [camera and light]

# add camera and light
cam = ddg.blender.camera.camera(location=(0, 0, 19))
light = ddg.blender.light.light(location=(0, 0, 50))

light.data.use_shadow = False

# [camera and light]

# -------------------------------------------------------------

#
# HIDE COLLECTIONS
#

# [hide collections]

example_coll.hide_render = True
example_coll.hide_viewport = True
projection_coll.hide_render = True
projection_coll.hide_viewport = True
slider_circles_coll.hide_render = True
slider_circles_coll.hide_viewport = True

# [hide collections]
