import nuke
import os
import math
import pyblish.api
import pype.api
from avalon.nuke import lib as anlib
from pprint import pformat


class ExtractCamera(pype.api.Extractor):
    """ 3D camera exctractor
    """
    label = 'Exctract Camera'
    order = pyblish.api.ExtractorOrder
    families = ["camera"]
    hosts = ["nuke"]

    # presets
    write_geo_knobs = [
        ("file_type", "abc"),
        ("storageFormat", "Ogawa"),
        ("writeGeometries", False),
        ("writePointClouds", False),
        ("writeAxes", False)
    ]

    def process(self, instance):
        handle_start = instance.context.data["handleStart"]
        handle_end = instance.context.data["handleEnd"]
        first_frame = int(nuke.root()["first_frame"].getValue())
        last_frame = int(nuke.root()["last_frame"].getValue())
        step = 1
        output_range = str(nuke.FrameRange(first_frame, last_frame, step))

        self.log.info("instance.data: `{}`".format(
            pformat(instance.data)))

        rm_nodes = list()
        self.log.info("Crating additional nodes")
        subset = instance.data["subset"]
        staging_dir = self.staging_dir(instance)

        # get extension form preset
        extension = next((k[1] for k in self.write_geo_knobs
                          if k[0] == "file_type"), None)
        if not extension:
            raise RuntimeError(
                "Bad config for extension in presets. "
                "Talk to your supervisor or pipeline admin")

        # create file name and path
        filename = subset + ".{}".format(extension)
        file_path = os.path.join(staging_dir, filename).replace("\\", "/")

        with anlib.maintained_selection():
            # bake camera with axeses onto word coordinate XYZ
            rm_n = bakeCameraWithAxeses(
                nuke.toNode(instance.data["name"]), output_range)
            rm_nodes.append(rm_n)

            # create scene node
            rm_n = nuke.createNode("Scene")
            rm_nodes.append(rm_n)

            # create write geo node
            wg_n = nuke.createNode("WriteGeo")
            wg_n["file"].setValue(file_path)
            # add path to write to
            for k, v in self.write_geo_knobs:
                wg_n[k].setValue(v)
            rm_nodes.append(wg_n)

            # write out camera
            nuke.execute(
                wg_n,
                int(first_frame),
                int(last_frame)
            )
            # erase additional nodes
            for n in rm_nodes:
                nuke.delete(n)

            self.log.info(file_path)

        # create representation data
        if "representations" not in instance.data:
            instance.data["representations"] = []

        representation = {
            'name': extension,
            'ext': extension,
            'files': filename,
            "stagingDir": staging_dir,
            "frameStart": first_frame,
            "frameEnd": last_frame
        }
        instance.data["representations"].append(representation)

        instance.data.update({
            "path": file_path,
            "outputDir": staging_dir,
            "ext": extension,
            "handleStart": handle_start,
            "handleEnd": handle_end,
            "frameStart": first_frame + handle_start,
            "frameEnd": last_frame - handle_end,
            "frameStartHandle": first_frame,
            "frameEndHandle": last_frame,
        })

        self.log.info("Extracted instance '{0}' to: {1}".format(
            instance.name, file_path))


def bakeCameraWithAxeses(camera_node, output_range):
    """ Baking all perent hiearchy of axeses into camera
    with transposition onto word XYZ coordinance
    """
    bakeFocal = False
    bakeHaperture = False
    bakeVaperture = False

    camera_matrix = camera_node['world_matrix']

    new_cam_n = nuke.createNode("Camera2")
    new_cam_n.setInput(0, None)
    new_cam_n['rotate'].setAnimated()
    new_cam_n['translate'].setAnimated()

    old_focal = camera_node['focal']
    if old_focal.isAnimated() and not (old_focal.animation(0).constant()):
        new_cam_n['focal'].setAnimated()
        bakeFocal = True
    else:
        new_cam_n['focal'].setValue(old_focal.value())

    old_haperture = camera_node['haperture']
    if old_haperture.isAnimated() and not (
            old_haperture.animation(0).constant()):
        new_cam_n['haperture'].setAnimated()
        bakeHaperture = True
    else:
        new_cam_n['haperture'].setValue(old_haperture.value())

    old_vaperture = camera_node['vaperture']
    if old_vaperture.isAnimated() and not (
            old_vaperture.animation(0).constant()):
        new_cam_n['vaperture'].setAnimated()
        bakeVaperture = True
    else:
        new_cam_n['vaperture'].setValue(old_vaperture.value())

    new_cam_n['win_translate'].setValue(camera_node['win_translate'].value())
    new_cam_n['win_scale'].setValue(camera_node['win_scale'].value())

    for x in nuke.FrameRange(output_range):
        math_matrix = nuke.math.Matrix4()
        for y in range(camera_matrix.height()):
            for z in range(camera_matrix.width()):
                matrix_pointer = z + (y * camera_matrix.width())
                math_matrix[matrix_pointer] = camera_matrix.getValueAt(
                    x, (y + (z * camera_matrix.width())))

        rot_matrix = nuke.math.Matrix4(math_matrix)
        rot_matrix.rotationOnly()
        rot = rot_matrix.rotationsZXY()

        new_cam_n['rotate'].setValueAt(math.degrees(rot[0]), x, 0)
        new_cam_n['rotate'].setValueAt(math.degrees(rot[1]), x, 1)
        new_cam_n['rotate'].setValueAt(math.degrees(rot[2]), x, 2)
        new_cam_n['translate'].setValueAt(
            camera_matrix.getValueAt(x, 3), x, 0)
        new_cam_n['translate'].setValueAt(
            camera_matrix.getValueAt(x, 7), x, 1)
        new_cam_n['translate'].setValueAt(
            camera_matrix.getValueAt(x, 11), x, 2)

        if bakeFocal:
            new_cam_n['focal'].setValueAt(old_focal.getValueAt(x), x)
        if bakeHaperture:
            new_cam_n['haperture'].setValueAt(old_haperture.getValueAt(x), x)
        if bakeVaperture:
            new_cam_n['vaperture'].setValueAt(old_vaperture.getValueAt(x), x)

    return new_cam_n
