import bpy

from math import degrees, tan, atan
from mathutils import Vector, Matrix

from .util import escape_name, mat2str, ExportCancelled

class World:
    def __init__(self, render_engine, scene, visible_layers, aspect_ratio):
        self.background_shader = BackgroundShader(render_engine, scene.world)
        self.camera = Camera(render_engine, scene.camera, aspect_ratio)
        self.lights = []

        # Collect infinite-extent light sources.
        # TODO: also get sun lamps inside group instances.
        for ob in scene.objects:
            if ob.type == 'LAMP' and ob.data.type == 'SUN':
                name = escape_name(ob.name)
                self.lights += [DistantDiskLamp(ob, name)]

    def take_sample(self, render_engine, scene, time):
        self.camera.take_sample(render_engine, scene, time)

        for light in self.lights:
            # Check if render is cancelled
            if render_engine.test_break():
                raise ExportCancelled()
            light.take_sample(render_engine, scene, time)

    def export(self, render_engine, w):
        self.camera.export(render_engine, w)

        w.write("World {\n")
        w.indent()

        self.background_shader.export(render_engine, w)

        for light in self.lights:
            light.export(render_engine, w)

        w.unindent()
        w.write("}\n")

    def cleanup(self):
        # For future use.  This is run by the calling code when finished,
        # even if export did not succeed.
        pass

#================================================================

class Camera:
    def __init__(self, render_engine, ob, aspect_ratio):
        self.ob = ob
        self.aspect_ratio = aspect_ratio

        self.fovs = []
        self.aperture_radii = []
        self.focal_distances = []
        self.xforms = []

    def take_sample(self, render_engine, scene, time):
        render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))

        # Fov
        if self.aspect_ratio >= 1.0:
            self.fovs += [degrees(self.ob.data.angle)]
        else:
            self.fovs += [degrees(2.0 * atan(tan(self.ob.data.angle * 0.5) * self.aspect_ratio))]

        # Aperture radius
        self.aperture_radii += [self.ob.data.psychopath.aperture_radius]

        # Dof distance
        if self.ob.data.dof_object == None:
            self.focal_distances += [self.ob.data.dof_distance]
        else:
            # TODO: implement DoF object tracking here
            self.focal_distances += [0.0]
            print("WARNING: DoF object tracking not yet implemented.")

        # Transform
        mat = self.ob.matrix_world.copy()
        matz = Matrix()
        matz[2][2] = -1
        self.xforms += [mat * matz]

    def export(self, render_engine, w):
        render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
        w.write("Camera {\n")
        w.indent()

        for fov in self.fovs:
            w.write("Fov [%f]\n" % fov)

        for rad in self.aperture_radii:
            w.write("ApertureRadius [%f]\n" % rad)

        for dist in self.focal_distances:
            w.write("FocalDistance [%f]\n" % dist)

        for mat in self.xforms:
            w.write("Transform [%s]\n" % mat2str(mat))

        w.unindent()
        w.write("}\n")


class BackgroundShader:
    def __init__(self, render_engine, world):
        self.world = world
        if self.world != None:
            self.color = (world.horizon_color[0], world.horizon_color[1], world.horizon_color[2])

    def export(self, render_engine, w):
        if self.world != None:
            w.write("BackgroundShader {\n")
            w.indent();
            w.write("Type [Color]\n")
            w.write("Color [rec709, %f %f %f]\n" % self.color)
            w.unindent()
            w.write("}\n")


class DistantDiskLamp:
    def __init__(self, ob, name):
        self.ob = ob
        self.name = name
        self.time_col = []
        self.time_dir = []
        self.time_rad = []

    def take_sample(self, render_engine, scene, time):
        render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
        self.time_dir += [tuple(self.ob.matrix_world.to_3x3() * Vector((0, 0, -1)))]

        if self.ob.data.psychopath.color_type == 'Rec709':
            self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)]
        elif self.ob.data.psychopath.color_type == 'Blackbody':
            self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
        elif self.ob.data.psychopath.color_type == 'ColorTemperature':
            self.time_col += [('ColorTemperature', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]

        self.time_rad += [self.ob.data.shadow_soft_size]

    def export(self, render_engine, w):
        render_engine.update_stats("", "Psychopath: Exporting %s" % self.ob.name)
        w.write("DistantDiskLight $%s {\n" % self.name)
        w.indent()
        for direc in self.time_dir:
            w.write("Direction [%f %f %f]\n" % (direc[0], direc[1], direc[2]))
        for col in self.time_col:
            if col[0] == 'Rec709':
                w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2]))
            elif col[0] == 'Blackbody':
                w.write("Color [blackbody, %f %f]\n" % (col[1], col[2]))
            elif col[0] == 'ColorTemperature':
                w.write("Color [color_temperature, %f %f]\n" % (col[1], col[2]))
        for rad in self.time_rad:
            w.write("Radius [%f]\n" % rad)

        w.unindent()
        w.write("}\n")
