#!/usr/bin/env python

'''
Inkscape extension to subdivide the selected bezier paths based on max length value or count

Copyright (C) 2018  Shrinivas Kulkarni

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 inkex, cubicsuperpath, bezmisc, simpletransform, sys
import copy
from math import ceil

DEF_ERR_MARGIN = 0.0001

def getPartsFromCubicSuper(cspath):
    parts = []
    for subpath in cspath:
        part = []
        prevBezPt = None            
        for i, bezierPt in enumerate(subpath):
            if(prevBezPt != None):
                seg = [prevBezPt[1], prevBezPt[2], bezierPt[0], bezierPt[1]]
                part.append(seg)
            prevBezPt = bezierPt
        parts.append(part)
    return parts
    
def getCubicSuperFromParts(parts):
    cbsuper = []
    for part in parts:
        subpath = []
        lastPt = None
        pt = None
        for seg in part:
            if(pt == None):
                ptLeft = seg[0]
                pt = seg[0]
            ptRight = seg[1]
            subpath.append([ptLeft, pt, ptRight])
            ptLeft = seg[2]
            pt = seg[3]
        subpath.append([ptLeft, pt, pt])
        cbsuper.append(subpath)
    return cbsuper
        
def floatCmpWithMargin(float1, float2, margin = DEF_ERR_MARGIN):
    return abs(float1 - float2) < margin 
        

class SubdividePathEffect(inkex.Effect):

    def __init__(self):
        inkex.Effect.__init__(self)

        self.OptionParser.add_option('--maxLength', action = 'store',
          type = 'float', dest = 'maxLength', default = '10',
          help = 'Maximum Length of New Segments')

        self.OptionParser.add_option('--unit', action = 'store',
          type = 'string', dest = 'unit', default = 'mm',
          help = 'Unit of Measurement')

        self.OptionParser.add_option('--precision', action = 'store',
          type = 'int', dest = 'precision', default = '5',
          help = 'Number of significant digits')

        self.OptionParser.add_option("--tab", action="store", 
          type="string", dest="tab", default="sampling", help="Tab") 
          
        self.OptionParser.add_option("--separateSegs",
                        action="store", type="inkbool", 
                        dest="separateSegs", default=True)

    def effect(self):
        
        maxL = None
        separateSegs = self.options.separateSegs
        
        if(self.options.unit != 'perc' and self.options.unit != 'count'):
            maxL = self.options.maxLength * self.unittouu('1'+self.options.unit)
            
        # ~ inkex.errormsg(_(str(maxL)))
        tolerance = 10 ** (-1 * self.options.precision)
        
        selections = self.selected        
        pathNodes = self.document.xpath('//svg:path',namespaces=inkex.NSS)

        paths = [(pathNode.get('id'), cubicsuperpath.parsePath(pathNode.get('d'))) \
            for pathNode in  pathNodes if (pathNode.get('id') in selections.keys())]

        if(len(paths) > 0):
            for key, cspath in paths:
                parts = getPartsFromCubicSuper(cspath)
                partsSplit = False
                
                for i, part in enumerate(parts):
                    
                    newSegs = []
                    for j, seg in enumerate(part):
                                                
                        segL = bezmisc.bezierlengthSimpson((seg[0], seg[1], 
                            seg[2], seg[3]), tolerance = tolerance)
                            
                        if(maxL != None):
                            divL = maxL
                        elif(self.options.unit == 'perc'):
                            divL = segL * self.options.maxLength / 100 
                        else:
                            divL = segL / ceil(self.options.maxLength)
                            
                        if(segL > divL):
                            
                            coveredL = 0
                            s = seg
                            s1 = None
                            s2 = DEF_ERR_MARGIN #Just in case
                            
                            while(not floatCmpWithMargin(segL, coveredL)):
                                if(s == seg):
                                    sL = segL
                                else:
                                    sL = bezmisc.bezierlengthSimpson((s[0], s[1], 
                                        s[2], s[3]), tolerance = tolerance)
                                    
                                if(floatCmpWithMargin(segL, coveredL + divL)):
                                    s2 = s
                                    break                                    
                                else:
                                    if(segL > (coveredL + divL)):                                                        
                                        t1L = divL 
                                    else:
                                        t1L = segL - coveredL
                                        
                                    t1 = bezmisc.beziertatlength((s[0], s[1], s[2], s[3]), 
                                        l = t1L / sL , tolerance = tolerance)                
                                    s1, s2 = bezmisc.beziersplitatt((s[0], s[1], s[2], s[3]), t1)
                                    coveredL += t1L                            
                                    newSegs.append(s1)
                                    s = s2
                            newSegs.append(s2)
                        else:
                            newSegs.append(seg)
                            
                    if(len(newSegs) > len(part)):
                        parts[i] = newSegs
                        partsSplit = True
                
                if(partsSplit or separateSegs):
                    elem = selections[key]
                    if(separateSegs):
                        parent = self.getParentNode(elem)
                        idx = parent.index(elem)
                        parent.remove(elem)
                        allSegs = [seg for part in parts for seg in part]
                        idSuffix = 0
                        for seg in allSegs:
                            cspath = getCubicSuperFromParts([[seg]])
                            newElem = copy.copy(elem)
                            oldId = newElem.get('id')
                            newElem.set('d', cubicsuperpath.formatPath(cspath))
                            newElem.set('id', oldId + str(idSuffix).zfill(5))
                            parent.insert(idx, newElem)
                            idSuffix += 1
                    else:
                        cspath = getCubicSuperFromParts(parts)
                        elem.set('d', cubicsuperpath.formatPath(cspath))
                        
effect = SubdividePathEffect()
effect.affect()
