# -*- coding: utf-8 -*-
# Copyright (c) 2012-2020, Anima Istanbul
#
# This module is part of anima and is released under the MIT
# License: http://www.opensource.org/licenses/MIT

import pymel.core as pm


def camera_film_offset_tool():
    """Adds a locator to the selected camera with which you can adjust the 2d
    pan and zoom.

    Usage :
    -------
    - To add it, select the camera and use camera_film_offset_tool
    - To remove it, set the transformations to 0 0 1 and then simply delete
    the curve
    - You can also use the ``enable`` option to temporarily disable the effect
    """
    camera_shape = get_selected_camera()
    if not camera_shape:
        raise RuntimeError("please select one camera!")

    # get the camera transform node
    camera_transform = camera_shape.getParent()

    frustum_curve = create_frustum_curve(camera_transform)
    handle = create_camera_space_locator(frustum_curve)

    # connect the locator tx and ty to film offset x and y
    handle.tx >> camera_shape.pan.horizontalPan
    handle.ty >> camera_shape.pan.verticalPan

    handle.sy.set(lock=False)
    handle.sz.set(lock=False)
    handle.sx >> handle.sy
    handle.sx >> handle.sz
    handle.sy.set(lock=True, keyable=False)
    handle.sz.set(lock=True, keyable=False)

    handle.sx >> camera_shape.zoom

    pm.transformLimits(handle, sx=(0.01, 2.0), esx=(True, True))

    handle.addAttr('enable', at='bool', dv=True, k=True)
    handle.enable >> camera_shape.panZoomEnabled


def create_camera_space_locator(frustum_curve):
    """Creates a locator under the given frame_curve

    :param frustum_curve:
    :return:
    """
    # create the locator
    locator = pm.spaceLocator()
    locator_shape = locator.getShape()
    pm.parent(locator, frustum_curve, r=True)
    locator.tz.set(lock=True, keyable=False)
    locator.rx.set(lock=True, keyable=False)
    locator.ry.set(lock=True, keyable=False)
    locator.rz.set(lock=True, keyable=False)
    pm.transformLimits(locator, tx=(-0.5, 0.5), etx=(True, True))
    pm.transformLimits(locator, ty=(-0.5, 0.5), ety=(True, True))
    locator_shape.localScaleZ.set(0)
    return locator


def create_frustum_curve(camera):
    """Creates a curve showing the frustum of the given camera

    :param camera:
    :return:
    """

    if isinstance(camera, pm.nt.Transform):
        camera_tranform = camera
        camera = camera_tranform.getShape()
    elif isinstance(camera, pm.nt.Camera):
        camera_tranform = camera.getParent()

    # validate the camera
    if not isinstance(camera, pm.nt.Camera):
        raise RuntimeError('Please select a camera')
    
    # create the outer box
    frame_curve = pm.curve(
        d=1,
        p=[(-0.5, 0.5, 0),
           (0.5, 0.5, 0),
           (0.5, -0.5, 0),
           (-0.5, -0.5, 0),
           (-0.5, 0.5, 0)],
        k=[0, 1, 2, 3, 4]
    )

    pm.parent(frame_curve, camera_tranform, r=True)
    # transform the frame curve
    frame_curve.tz.set(-10.0)

    exp = """float $flen = {camera}.focalLength;
float $hfa = {camera}.horizontalFilmAperture * 25.4;
{curve}.sx = {curve}.sy = -{curve}.translateZ * $hfa/ $flen;""".format(
        camera=camera.name(),
        curve=frame_curve.name()
    )
    pm.expression(s=exp, o='', ae=1, uc="all")

    return frame_curve


def get_selected_camera():
    """Returns the selected camera
    """
    for obj in pm.ls(sl=1, type=pm.nt.Transform):
        # if it is a transform node query for shapes
        if isinstance(obj, pm.nt.Transform):
            for shape in obj.listRelatives(s=True):
                if isinstance(shape, pm.nt.Camera):
                    return shape
        elif isinstance(obj, pm.nt.Camera):
            return obj


def camera_focus_plane_tool():
    """sets up a focus plane for the selected camera
    """
    camera = pm.ls(sl=1)[0]
    camera_shape = camera.getShape()

    frame = pm.nurbsPlane(
        n='focusPlane#',
        p=(0, 0, 0), ax=(0, 0, 1), w=1, lr=1, d=1, u=1, v=1, ch=0
    )[0]
    frame_shape = frame.getShape()
    pm.parent(frame, camera, r=True)

    #transform the frame surface
    frame.tz.set(-10.0)

    exp = """float $flen = %(camera)s.focalLength;
    float $hfa = %(camera)s.horizontalFilmAperture * 25.4;
    %(frame)s.sx = -%(frame)s.translateZ * $hfa/ $flen;
    %(frame)s.sy = %(frame)s.sx / defaultResolution.deviceAspectRatio;
    %(camera)s.focusDistance = -%(frame)s.tz;
    %(camera)s.aiFocusDistance = %(camera)s.focusDistance;
    %(camera)s.aiApertureSize = %(camera)s.focalLength / %(camera)s.fStop * 0.1;
    """ % {
        'camera': camera_shape.name(),
        'frame': frame.name()
    }
    pm.expression(s=exp, ae=1, uc="all")

    # set material
    surface_shader = pm.shadingNode('surfaceShader', asShader=1)
    pm.select(frame)
    pm.hyperShade(a=surface_shader.name())

    surface_shader.setAttr('outColor', (0.4, 0, 0))
    surface_shader.setAttr('outTransparency', (0.5, 0.5, 0.5))

    # prevent it from being rendered
    frame_shape.setAttr('castsShadows', 0)
    frame_shape.setAttr('receiveShadows', 0)
    frame_shape.setAttr('motionBlur', 0)
    frame_shape.setAttr('primaryVisibility', 0)
    frame_shape.setAttr('smoothShading', 0)
    frame_shape.setAttr('visibleInReflections', 0)
    frame_shape.setAttr('visibleInRefractions', 0)

    # Arnold attributes
    try:
        frame_shape.setAttr('aiSelfShadows', 0)
        frame_shape.setAttr('aiVisibleInDiffuse', 0)
        frame_shape.setAttr('aiVisibleInGlossy', 0)
    except pm.MayaAttributeError:
        pass

    # hide unnecessary attributes
    frame.setAttr('tx', lock=True, keyable=False)
    frame.setAttr('ty', lock=True, keyable=False)
    frame.setAttr('rx', lock=True, keyable=False)
    frame.setAttr('ry', lock=True, keyable=False)
    frame.setAttr('rz', lock=True, keyable=False)
    frame.setAttr('sx', lock=True, keyable=False)
    frame.setAttr('sy', lock=True, keyable=False)
    frame.setAttr('sz', lock=True, keyable=False)


def cam_to_chan(start_frame, end_frame):
    """Exports maya camera to nuke

    Select camera to export and call cam2Chan(startFrame, endFrame)


    :param start_frame: start frame
    :param end_frame: end frame
    :return:
    """
    selection = pm.ls(sl=1)
    chan_file = pm.fileDialog2(cap="Save", fm=0, ff="(*.chan)")[0]

    camera = selection[0]

    template = "%(frame)s\t%(posx)s\t%(posy)s\t%(posz)s\t" \
               "%(rotx)s\t%(roty)s\t%(rotz)s\t%(vfv)s"

    lines = []

    for i in range(start_frame, end_frame + 1):
        pm.currentTime(i, e=True)

        pos = pm.xform(camera, q=True, ws=True, t=True)
        rot = pm.xform(camera, q=True, ws=True, ro=True)
        vfv = pm.camera(camera, q=True, vfv=True)

        lines.append(
            template % {
                'frame': i,
                'posx': pos[0],
                'posy': pos[1],
                'posz': pos[2],
                'rotx': rot[0],
                'roty': rot[1],
                'rotz': rot[2],
                'vfv': vfv
            }
        )

    with open(chan_file, 'w') as f:
        f.writelines('\n'.join(lines))


def create_3dequalizer_points(width, height):
    """creates 3d equalizer points under the selected camera

    :param width: The width of the plate
    :param height: The height of the plate
    """
    width = float(width)
    height = float(height)

    # get the text file
    path = pm.fileDialog()

    # parse the file
    from anima import utils
    man = utils.C3DEqualizerPointManager()
    man.read(path)

    # get the camera
    cam_shape = get_selected_camera()

    pm.getAttr("defaultResolution.deviceAspectRatio")
    pm.getAttr("defaultResolution.pixelAspect")

    frustum_curve = create_frustum_curve(cam_shape)

    for point in man.points:
        # create a locator
        loc = create_camera_space_locator(frustum_curve)
        loc.rename('p%s' % point.name)

        # animate the locator
        for frame in point.data.keys():
            pm.currentTime(frame)
            frame_data = point.data[frame]
            local_x = frame_data[0] / width - 0.5
            local_y = frame_data[1] / width - 0.5 * height / width
            loc.tx.set(local_x)
            loc.ty.set(local_y)
            loc.tx.setKey()
            loc.ty.setKey()


def find_cut_info(cam):
    """Finds cuts of the given camera.

    This tool works only for

    :param cam: A Maya cameras transform node
    :return:
    """
    # check if this is really a camera
    cam_shape = cam.getShape()

    if not isinstance(cam_shape, pm.nt.Camera):
        raise RuntimeError("Please supply a camera")

    # find cuts
    # for now use the tx attribute to find the cuts
    keyframes = pm.keyframe(cam.tx, q=1, timeChange=True)

    cut_info = []

    i = 0
    iter_count = 0
    while i < range(len(keyframes) - 2) and iter_count < 1000:
        iter_count += 1
        start_frame = keyframes[i]

        try:
            end_frame = keyframes[i + 1]
        except IndexError:
            break

        j = 2
        while (i + j) < len(keyframes):
            start_frame_of_next_cam = keyframes[i + j]
            if int(start_frame_of_next_cam - end_frame) == 1:
                cut_info.append([start_frame, end_frame])
                # print(i, start_frame, end_frame)
                i += j
                break

            end_frame = start_frame_of_next_cam
            j += 1

    return cut_info