from os import path
from sys import stdout
from typing import List

from p3dsdk import CurrentP3DSession, Database, P3DFile, Model
from p3dsdk import CameraBezierPathAnimation, Vector3, CameraBookmarkAnimation
from p3dsdk import Null, CameraBezierPathAnimationPositionType, CameraBezierPathAnimationTargetType
from p3dsdk import ChannelsSimpleAnimation, ChannelsCurveAnimation, Surface


def find_kinematic_object(db: Database, name: str) -> Null:
    for obj in db.list_models()[0].list_kinematic_objects():
        if obj.name == name:
            return obj
    raise Exception(f"No kinemtaics object named '{name}' found in the database")


def find_surface(db: Database, names: List[str]) -> Surface:
    geometry_layers = db.list_models()[0].list_geometry_layers()
    for layer in geometry_layers:
        for surface in layer.list_surfaces():
            for name in names:
                if surface.name.startswith(name):
                    return surface
    raise Exception(f"No surface starting by '{' or '.join(names)}' found in the database")
  

def clean_empty_timelines(db: Database):
    timelines_to_remove = []
    for timeline in db.list_timelines():
        all_is_empty = timeline.product_animation_track().empty
        all_is_empty &= timeline.camera_animation_track().empty
        all_is_empty &= timeline.configuration_animation_track().empty
        all_is_empty &= timeline.empty_channels_track().empty
        all_is_empty &= timeline.empty_texture_track().empty
        for channel_track in timeline.channels_animation_tracks():
            all_is_empty &= channel_track.empty
        for texture_track in timeline.texture_animation_tracks():
            all_is_empty &= texture_track.empty
        if all_is_empty:
            timelines_to_remove.append(timeline)
    for timeline_to_remove in timelines_to_remove:
        timeline_to_remove.remove()


def create_bezier_path_camera_anim(db: Database) -> CameraBezierPathAnimation :
    anim = db.create_camera_bezier_path_animation('rotate_camera')
    anim.duration = 3
    anim.position_type = CameraBezierPathAnimationPositionType.FOLLOW_BEZIER_PATH
    bezier_path = db.list_models()[0].create_bezier_path('camera_bezier_path', [], [])
    bezier_path.translation = Vector3(0, 0.8, 0)
    anim.bezier_path_used_for_position = bezier_path
    anim.position_bezier_path_inverted = True
    anim.target_type = CameraBezierPathAnimationTargetType.FOLLOW_NULL
    anim.target_null = db.list_models()[0].create_null('camera_target_null')
    return anim


def create_bookmark_camera_anim(db: Database) -> CameraBookmarkAnimation :
    anim = db.create_camera_bookmark_animation('zoom_camera')
    group: CameraGroup = db.create_camera_group('CameraGroup For Animation')
    camera_1 = db.create_camera(group)
    camera_1.name = 'bookmarkCamera1'
    camera_1.view_from = Vector3(1,0.8,0)
    camera_2 = db.create_camera(group)
    camera_2.name = 'bookmarkCamera2'
    camera_2.view_from = Vector3(0.5, 0.4, 0.0)
    camera_1 = db.create_camera(group)
    camera_1.name = 'bookmarkCamera3'
    camera_1.view_from = Vector3(1, 0.8, 0)
    anim.add_camera_from_camera_group_to_animation(group=group)
    for camera_bookmark in anim.list_camera_bookmarks():
       camera_bookmark.duration = 1
    anim.list_camera_bookmarks()[-1].duration = 0
    return anim


def create_simple_channel_animation(db: Database) -> ChannelsSimpleAnimation :
    anim = db.create_channels_simple_animation('rotate_box')
    axis = find_kinematic_object(db, 'animated_axis')
    anim.set_axis_channel_animation(object=axis, start=0, end=360)
    anim.duration = 2
    return anim


def create_curve_channel_animation(db: Database) -> ChannelsCurveAnimation :
    anim = db.create_channels_curve_animation('translate_box')
    anim.duration = 3

    null_obj = find_kinematic_object(db, 'animated_null')
    null_try_curve = anim.add_null_channel_animation(object=null_obj, channel='Translation/X')
    null_try_curve.insert_key_frame(1, 0.3)
    null_try_curve.insert_key_frame(2, 0.3)
    null_try_curve.insert_key_frame(3, 0.0)
    null_try_curve = anim.add_null_channel_animation(object=null_obj, channel='Translation/Z')
    null_try_curve.insert_key_frame(1, 0.3)
    null_try_curve.insert_key_frame(2, -0.3)
    null_try_curve.insert_key_frame(3, 0.0)

    return anim


def prepare_product(db: Database):
    product = db.list_models()[0].create_product('Product')
    aspect_layer = product.create_aspect_layer("materials")
    mat_group = db.create_material_group("gbuffer materials")

    geometry_layers = db.list_models()[0].list_geometry_layers()
    for layer in geometry_layers:
        for surface in layer.list_surfaces():
            color = surface.color
            material = mat_group.create_standard_material("mat-" + surface.name)
            material.diffuse_color = color;
            aspect_layer.assign_material(surface, material) 


def prepare_kinematics(db: Database):
    null_obj = db.list_models()[0].create_null('animated_null')
    axis = null_obj.create_axis('animated_axis')
    axis.origin_point = (0, -0.35, 0)
    axis.end_point = (0, 0.35, 0)
    box = find_surface(db, ["Box","Cube"])
    axis.add_surface(box)


def timeline_example(db: Database):
    prepare_product(db)
    prepare_kinematics(db)

    timeline = db.create_timeline('Timeline Example')
    timeline.range_end = 10

    start_time = 0
    # create a curve channel animation and insert as a clip to the timeline in the empty track
    advanced_channel_anim = create_curve_channel_animation(db)
    timeline.empty_channels_track().insert_animation(start_time, advanced_channel_anim)

    start_time += int(advanced_channel_anim.duration * 1000)
    # create a simple channel animation and insert as a clip to the timeline
    simple_channel_anim = create_simple_channel_animation(db)
    timeline.empty_channels_track().insert_animation(start_time, simple_channel_anim)

    start_time += int(simple_channel_anim.duration * 1000)
    # create BezierPath Camera Animation and insert as a clip to the timeline at 0ms
    bp_camera_anim = create_bezier_path_camera_anim(db)
    timeline.camera_animation_track().insert_animation(start_time, bp_camera_anim)

    start_time += int(bp_camera_anim.duration * 1000)
    # create Camera Bookmark Animation and insert as a clip to the timeline after the previous animation
    # N.B. the duration of the animation is in seconds and the insert clip method takes time in ms
    bookmark_camera_anim = create_bookmark_camera_anim(db)
    timeline.camera_animation_track().insert_animation(start_time, bookmark_camera_anim)

    # create a product and insert a product key animation to the timeline
    timeline.product_animation_track().insert_animation(0, db.list_product_key_animations()[0])

    clean_empty_timelines(db)


if __name__ == "__main__":
    use_current_session = True
    if use_current_session:
        with CurrentP3DSession(fallback_port=33900) as p3d:
            timeline_example(p3d.data)
    else:
        with P3DFile.create() as database:
            timeline_example(database)
            test_file_name = 'database_' + path.splitext(path.basename(__file__))[0] + '.p3d'
            database.save_as(path.join(path.dirname(path.abspath(__file__)), test_file_name), True)
