#!/usr/bin/python

import os.path
import math

from xml.sax import make_parser
from xml.sax.handler import ContentHandler

# interpolation type constants
INT_LINEAR = 0
INT_LOG = 1
INT_INVLOG = 2
INT_COS = 3


def getAttrOrDefault(attrs, name, default):
    x = attrs.get(name)
    if x is None:
        x = default
    return x


def getAttrOrElse(attrs, name):
    x = attrs.get(name)
    if x is None:
        raise ValueError(
            "Invalid file: Cannot find required attribute '%s'" % name)
    return x


class KeyFrame:
    def __init__(self, filename, duration, stop,
                 int_type, flags=(0, 0, 0, 0, 0, 0)):
        self.filename = filename
        self.duration = duration
        self.stop = stop
        self.int_type = int_type
        self.flags = flags

    def save(self, fh):
        fh.write(
            '\t\t<keyframe filename="%s" duration="%d" stop="%d" inttype="%d" ' % (self.filename, self.duration, self.stop, self.int_type))
        fh.write(
            'xy="%d" xz="%d" xw="%d" yz="%d" yw="%d" zw="%d"' % self.flags)
        fh.write('/>\n')

    @staticmethod
    def load_from_xml(attrs):
        kf = KeyFrame(
            getAttrOrElse(attrs, "filename"),
            int(getAttrOrElse(attrs, "duration")),
            int(getAttrOrElse(attrs, "stop")),
            int(getAttrOrElse(attrs, "inttype")),
            (int(getAttrOrDefault(attrs, "xy", 0)),
             int(getAttrOrDefault(attrs, "xz", 0)),
             int(getAttrOrDefault(attrs, "xw", 0)),
             int(getAttrOrDefault(attrs, "yz", 0)),
             int(getAttrOrDefault(attrs, "yw", 0)),
             int(getAttrOrDefault(attrs, "zw", 0))))
        return kf


class T:
    def __init__(self, compiler, userConfig):
        self.compiler = compiler
        self.userConfig = userConfig
        self.reset()

    def reset(self):
        self.avi_file = ""
        self.width = 640
        self.height = 480
        self.framerate = 25
        self.redblue = False
        # keyframes is a list of KeyFrame objects
        self.keyframes = []

    def get_fct_enabled(self):
        return self.userConfig.getboolean("director", "fct_enabled")

    def set_fct_enabled(self, fct_enabled):
        if fct_enabled:
            self.userConfig.set("director", "fct_enabled", "1")
        else:
            self.userConfig.set("director", "fct_enabled", "0")

    def get_fct_dir(self):
        return self.userConfig.get("director", "fct_dir")

    def set_fct_dir(self, dir):
        self.userConfig.set("director", "fct_dir", dir)

    def get_png_dir(self):
        return self.userConfig.get("director", "png_dir")

    def set_png_dir(self, dir):
        self.userConfig.set("director", "png_dir", dir)

    def get_avi_file(self):
        return self.avi_file

    def set_avi_file(self, file):
        if file is not None:
            self.avi_file = file
        else:
            self.avi_file = ""

    def get_width(self):
        return self.width

    def set_width(self, width):
        if width is not None:
            self.width = int(width)
        else:
            self.width = 640

    def get_height(self):
        return self.height

    def set_height(self, height):
        if height is not None:
            self.height = int(height)
        else:
            self.height = 480

    def get_framerate(self):
        return self.framerate

    def set_framerate(self, fr):
        if fr is not None:
            self.framerate = int(fr)
        else:
            self.framerate = 25

    def get_redblue(self):
        return self.redblue

    def set_redblue(self, rb):
        if rb is not None:
            if rb == 1:
                self.redblue = True
            elif rb == 0:
                self.redblue = False
                self.redblue = rb
        else:
            self.redblue = True

    def add_keyframe(self, filename, duration, stop, int_type, index=None):
        kf = KeyFrame(filename, duration, stop, int_type)
        if index is None:
            self.keyframes.append(kf)
        else:
            self.keyframes.insert(index, kf)

    def remove_keyframe(self, index):
        del self.keyframes[index:index + 1]

    def change_keyframe(self, index, duration, stop, int_type):
        if index < len(self.keyframes):
            kf = self.keyframes[index]
            kf.duration = duration
            kf.stop = stop
            kf.int_type = int_type

    def get_keyframe(self, index):
        return self.keyframes[index]

    def get_keyframe_filename(self, index):
        return self.keyframes[index].filename

    def get_keyframe_duration(self, index):
        return self.keyframes[index].duration

    def set_keyframe_duration(self, index, duration):
        if index < len(self.keyframes):
            self.keyframes[index].duration = duration

    def get_keyframe_stop(self, index):
        return self.keyframes[index].stop

    def set_keyframe_stop(self, index, stop):
        if index < len(self.keyframes):
            self.keyframes[index].stop = stop

    def get_keyframe_int(self, index):
        return self.keyframes[index].int_type

    def set_keyframe_int(self, index, int_type):
        if index < len(self.keyframes):
            self.keyframes[index].int_type = int_type

    def get_directions(self, index):
        return self.keyframes[index].flags

    def set_directions(self, index, drct):
        if index < len(self.keyframes):
            self.keyframes[index].flags = drct

    def keyframes_count(self):
        return len(self.keyframes)

    def __getstate__(self):
        odict = self.__dict__.copy()  # copy the dict since we change it
        # del odict['fh']              # remove filehandle entry
        return odict

    def __setstate__(self, dict):
        self.keyframes = []
        self.__dict__.update(dict)   # update attributes

    def load_animation(self, file):
        # save __dict__ if there was error
        odict = self.__dict__.copy()
        try:
            self.keyframes = []
            parser = make_parser()
            ah = AnimationHandler(self)
            parser.setContentHandler(ah)
            with open(file) as f:
                parser.parse(f)
        except Exception as err:
            # retrieve previous__dict__
            self.__setstate__(odict)
            raise

    def save_animation(self, file):
        with open(file, "w") as fh:
            fh.write('<?xml version="1.0"?>\n')
            fh.write("<animation>\n")
            fh.write('\t<keyframes>\n')
            for kf in self.keyframes:
                kf.save(fh)
            fh.write('\t</keyframes>\n')
            fh.write('\t<output filename="%s" framerate="%d" width="%d" height="%d" swap="%d"/>\n' %
                     (self.avi_file, self.framerate, self.width, self.height, self.redblue))
            fh.write("</animation>\n")

    # leftover from debugging purposes
    def pr(self):
        print(self.__dict__)

    def get_image_filename(self, n):
        "The filename of the image containing the Nth frame"
        return os.path.join(self.get_png_dir(), "image_%07d.png" % n)

    def get_fractal_filename(self, n):
        "The filename of the .fct file which generates the Nth frame"
        return os.path.join(self.get_fct_dir(), "file_%07d.fct" % n)

    def get_mu(self, int_type, x):
        if int_type == INT_LINEAR:
            mu = x
        elif int_type == INT_LOG:
            mu = math.log(x + 1, 2)
        elif int_type == INT_INVLOG:
            mu = (math.exp(x) - 1) / (math.e - 1)
        elif int_type == INT_COS:
            mu = (1 - math.cos(x * math.pi)) / 2
        else:
            raise ValueError("Unknown interpolation type %d" % int_type)
        return mu

    # create a list containing all the filenames of the frames
    def create_list(self):
        framelist = []
        current = 0
        for i in range(self.keyframes_count()):
            for j in range(
                    self.get_keyframe_stop(i)):  # output keyframe 'stop' times
                framelist.append(self.get_image_filename(current))
            current += 1

            if i < self.keyframes_count() - 1:
                # skip final frame which has no transitions following it
                for j in range(self.get_keyframe_duration(
                        i)):  # output all transition files minus keyframe
                    framelist.append(self.get_image_filename(current))
                    current += 1

        return framelist

    def get_keyframe_durations(self):
        durations = []
        for i in range(self.keyframes_count()):
            durations.append(self.get_keyframe_duration(i))

        return durations

    def get_total_frames(self):
        count = 0
        nframes = self.keyframes_count()
        for i in range(nframes):
            count += self.get_keyframe_stop(i)
            if i < nframes - 1:
                # don't count the last frame's duration
                count += self.get_keyframe_duration(i)
        return count


class AnimationHandler(ContentHandler):
    def __init__(self, animation):
        super().__init__()
        self.animation = animation

    def startElement(self, name, attrs):
        if name == "output":
            self.animation.set_avi_file(attrs.get("filename"))
            self.animation.set_framerate(attrs.get("framerate"))
            self.animation.set_width(attrs.get("width"))
            self.animation.set_height(attrs.get("height"))
            self.animation.set_redblue(int(attrs.get("swap")))
        elif name == "keyframe":
            kf = KeyFrame.load_from_xml(attrs)
            self.animation.keyframes.append(kf)
        return
