#!/usr/bin/env python
# coding=utf-8
#
# Copyright (C) 2005 Aaron Spike, aaron@ekips.org
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#

import copy

import inkex
from inkex.utils import pairwise
from inkex.paths import CubicSuperPath
from inkex.tween import tweenstyleunit, tweenstylefloat, tweenstylecolor, interppoints
from inkex.bezier import csplength, cspbezsplitatlength, cspbezsplit, bezlenapprx

class Interp(inkex.EffectExtension):
    def add_arguments(self, pars):
        pars.add_argument("-e", "--exponent", type=float, default=0.0,\
            help="values other than zero give non linear interpolation")
        pars.add_argument("-s", "--steps", type=int, default=5,\
            help="number of interpolation steps")
        pars.add_argument("-m", "--method", type=int, default=2,\
            help="method of interpolation")
        pars.add_argument("-d", "--dup", type=inkex.Boolean, default=True,\
            help="duplicate endpaths")
        pars.add_argument("--style", type=inkex.Boolean, default=True,\
            help="try interpolation of some style properties")
        pars.add_argument("--zsort", type=inkex.Boolean, default=False,\
            help="use z-order instead of selection order")

    def effect(self):
        exponent = self.options.exponent
        if exponent >= 0:
            exponent += 1.0
        else:
            exponent = 1.0 / (1.0 - exponent)
        steps = [1.0 / (self.options.steps + 1.0)]
        for i in range(self.options.steps - 1):
            steps.append(steps[0] + steps[-1])
        steps = [step ** exponent for step in steps]

        if self.options.zsort:
            # work around selection order swapping with Live Preview
            objects = self.svg.get_z_selected()
        else:
            # use selection order (default)
            objects = self.svg.selected

        objects = [node for node in objects.values() if isinstance(node, inkex.PathElement)]

        # prevents modification of original objects
        objects = copy.deepcopy(objects)

        for node in objects:
            node.apply_transform()

        for (elem1, elem2) in pairwise(objects, start=False):
            start = elem1.path.to_superpath()
            end = elem2.path.to_superpath()
            sst = copy.deepcopy(elem1.style)
            est = copy.deepcopy(elem2.style)
            basestyle = copy.deepcopy(sst)

            if 'stroke-width' in basestyle:
                basestyle['stroke-width'] = tweenstyleunit(self.svg, 'stroke-width', sst, est, 0)

            # prepare for experimental style tweening
            if self.options.style:
                dostroke = True
                dofill = True
                styledefaults = inkex.Style(
                    'opacity:1.0;stroke-opacity:1.0;fill-opacity:1.0;'
                    'stroke-width:1.0;stroke:none;fill:none')
                for key in styledefaults:
                    sst.setdefault(key, styledefaults[key])
                    est.setdefault(key, styledefaults[key])
                isnotplain = lambda x: not (x == 'none' or x[:1] == '#')
                if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke'] == 'none' and est['stroke'] == 'none'):
                    dostroke = False
                if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill'] == 'none' and est['fill'] == 'none'):
                    dofill = False
                if dostroke:
                    if sst['stroke'] == 'none':
                        sst['stroke-width'] = '0.0'
                        sst['stroke-opacity'] = '0.0'
                        sst['stroke'] = est['stroke']
                    elif est['stroke'] == 'none':
                        est['stroke-width'] = '0.0'
                        est['stroke-opacity'] = '0.0'
                        est['stroke'] = sst['stroke']
                if dofill:
                    if sst['fill'] == 'none':
                        sst['fill-opacity'] = '0.0'
                        sst['fill'] = est['fill']
                    elif est['fill'] == 'none':
                        est['fill-opacity'] = '0.0'
                        est['fill'] = sst['fill']

            if self.options.method == 2:
                # subdivide both paths into segments of relatively equal lengths
                slengths, stotal = csplength(start)
                elengths, etotal = csplength(end)
                lengths = {}
                t = 0
                for sp in slengths:
                    for l in sp:
                        t += l / stotal
                        lengths.setdefault(t, 0)
                        lengths[t] += 1
                t = 0
                for sp in elengths:
                    for l in sp:
                        t += l / etotal
                        lengths.setdefault(t, 0)
                        lengths[t] += -1
                sadd = [k for (k, v) in lengths.items() if v < 0]
                sadd.sort()
                eadd = [k for (k, v) in lengths.items() if v > 0]
                eadd.sort()

                t = 0
                s = [[]]
                for sp in slengths:
                    if not start[0]:
                        s.append(start.pop(0))
                    s[-1].append(start[0].pop(0))
                    for l in sp:
                        pt = t
                        t += l / stotal
                        if sadd and t > sadd[0]:
                            while sadd and sadd[0] < t:
                                nt = (sadd[0] - pt) / (t - pt)
                                bezes = cspbezsplitatlength(s[-1][-1][:], start[0][0][:], nt)
                                s[-1][-1:] = bezes[:2]
                                start[0][0] = bezes[2]
                                pt = sadd.pop(0)
                        s[-1].append(start[0].pop(0))
                t = 0
                e = [[]]
                for sp in elengths:
                    if not end[0]:
                        e.append(end.pop(0))
                    e[-1].append(end[0].pop(0))
                    for l in sp:
                        pt = t
                        t += l / etotal
                        if eadd and t > eadd[0]:
                            while eadd and eadd[0] < t:
                                nt = (eadd[0] - pt) / (t - pt)
                                bezes = cspbezsplitatlength(e[-1][-1][:], end[0][0][:], nt)
                                e[-1][-1:] = bezes[:2]
                                end[0][0] = bezes[2]
                                pt = eadd.pop(0)
                        e[-1].append(end[0].pop(0))
                start = s[:]
                end = e[:]
            else:
                # which path has fewer segments?
                lengthdiff = len(start) - len(end)
                # swap shortest first
                if lengthdiff > 0:
                    start, end = end, start
                # subdivide the shorter path
                for x in range(abs(lengthdiff)):
                    maxlen = 0
                    subpath = 0
                    segment = 0
                    for y in range(len(start)):
                        for z in range(1, len(start[y])):
                            leng = bezlenapprx(start[y][z - 1], start[y][z])
                            if leng > maxlen:
                                maxlen = leng
                                subpath = y
                                segment = z
                    sp1, sp2 = start[subpath][segment - 1:segment + 1]
                    start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
                # if swapped, swap them back
                if lengthdiff > 0:
                    start, end = end, start

            # break paths so that corresponding subpaths have an equal number of segments
            s = [[]]
            e = [[]]
            while start and end:
                if start[0] and end[0]:
                    s[-1].append(start[0].pop(0))
                    e[-1].append(end[0].pop(0))
                elif end[0]:
                    s.append(start.pop(0))
                    e[-1].append(end[0][0])
                    e.append([end[0].pop(0)])
                elif start[0]:
                    e.append(end.pop(0))
                    s[-1].append(start[0][0])
                    s.append([start[0].pop(0)])
                else:
                    s.append(start.pop(0))
                    e.append(end.pop(0))

            if self.options.dup:
                steps = [0] + steps + [1]
            # create an interpolated path for each interval
            group = self.svg.get_current_layer().add(inkex.Group())
            for time in steps:
                interp = []
                # process subpaths
                for ssp, esp in zip(s, e):
                    if not (ssp or esp):
                        break
                    interp.append([])
                    # process superpoints
                    for sp, ep in zip(ssp, esp):
                        if not (sp or ep):
                            break
                        interp[-1].append([])
                        # process points
                        for p1, p2 in zip(sp, ep):
                            if not (sp or ep):
                                break
                            interp[-1][-1].append(interppoints(p1, p2, time))

                # remove final subpath if empty.
                if not interp[-1]:
                    del interp[-1]

                # basic style tweening
                if self.options.style:
                    basestyle['opacity'] = tweenstylefloat('opacity', sst, est, time)
                    if dostroke:
                        basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity', sst, est, time)
                        basestyle['stroke-width'] = tweenstyleunit(self.svg, 'stroke-width', sst, est, time)
                        basestyle['stroke'] = tweenstylecolor('stroke', sst, est, time)
                    if dofill:
                        basestyle['fill-opacity'] = tweenstylefloat('fill-opacity', sst, est, time)
                        basestyle['fill'] = tweenstylecolor('fill', sst, est, time)

                new = group.add(inkex.PathElement())
                new.style = basestyle
                new.path = CubicSuperPath(interp)


if __name__ == '__main__':
    Interp().run()
