# -*- coding: utf-8 -*-

# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com>               *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   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 Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************

from __future__ import print_function

import FreeCAD
import FreeCADGui
import Path
import PathScripts
import PathScripts.PathLog as PathLog
import PathScripts.PathUtil as PathUtil
import json
import os
import xml.sax

from PySide import QtCore, QtGui

PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())

def translate(context, text, disambig=None):
    return QtCore.QCoreApplication.translate(context, text, disambig)

# Tooltable XML readers
class FreeCADTooltableHandler(xml.sax.ContentHandler):
    # http://www.tutorialspoint.com/python/python_xml_processing.htm

    def __init__(self):
        xml.sax.ContentHandler.__init__(self)
        self.tooltable = None
        self.tool = None
        self.number = None

    # Call when an element is found
    def startElement(self, name, attrs):
        if name == "Tooltable":
            self.tooltable = Path.Tooltable()
        elif name == "Toolslot":
            self.number = int(attrs["number"])
        elif name == "Tool":
            self.tool = Path.Tool()
            self.tool.Name = str(attrs["name"])
            self.tool.ToolType = str(attrs["type"])
            self.tool.Material = str(attrs["mat"])
            # for some reason without the following line I get an error
            #print attrs["diameter"]
            self.tool.Diameter = float(attrs["diameter"])
            self.tool.LengthOffset = float(attrs["length"])
            self.tool.FlatRadius = float(attrs["flat"])
            self.tool.CornerRadius = float(attrs["corner"])
            self.tool.CuttingEdgeAngle = float(attrs["angle"])
            self.tool.CuttingEdgeHeight = float(attrs["height"])

    # Call when an elements ends
    def endElement(self, name):
        if name == "Toolslot":
            if self.tooltable and self.tool and self.number:
                self.tooltable.setTool(self.number, self.tool)
                self.number = None
                self.tool = None


class HeeksTooltableHandler(xml.sax.ContentHandler):

    def __init__(self):
        xml.sax.ContentHandler.__init__(self)
        self.tooltable = Path.Tooltable()
        self.tool = None
        self.number = None

    # Call when an element is found
    def startElement(self, name, attrs):
        if name == "Tool":
            self.tool = Path.Tool()
            self.number = int(attrs["tool_number"])
            self.tool.Name = str(attrs["title"])
        elif name == "params":
            t = str(attrs["type"])
            if t == "drill":
                self.tool.ToolType = "Drill"
            elif t == "center_drill_bit":
                self.tool.ToolType = "CenterDrill"
            elif t == "end_mill":
                self.tool.ToolType = "EndMill"
            elif t == "slot_cutter":
                self.tool.ToolType = "SlotCutter"
            elif t == "ball_end_mill":
                self.tool.ToolType = "BallEndMill"
            elif t == "chamfer":
                self.tool.ToolType = "Chamfer"
            elif t == "engraving_bit":
                self.tool.ToolType = "Engraver"
            m = str(attrs["material"])
            if m == "0":
                self.tool.Material = "HighSpeedSteel"
            elif m == "1":
                self.tool.Material = "Carbide"
            # for some reason without the following line I get an error
            #print attrs["diameter"]
            self.tool.Diameter = float(attrs["diameter"])
            self.tool.LengthOffset = float(attrs["tool_length_offset"])
            self.tool.FlatRadius = float(attrs["flat_radius"])
            self.tool.CornerRadius = float(attrs["corner_radius"])
            self.tool.CuttingEdgeAngle = float(
                attrs["cutting_edge_angle"])
            self.tool.CuttingEdgeHeight = float(
                attrs["cutting_edge_height"])

    # Call when an elements ends
    def endElement(self, name):
        if name == "Tool":
            if self.tooltable and self.tool and self.number:
                self.tooltable.setTool(self.number, self.tool)
                self.number = None
                self.tool = None


class ToolLibraryManager():
    '''
    The Tool Library is a list of individual tool tables.  Each
    Tool Table can contain n tools.  The tool library will be persisted to user
    preferences and all or part of the library can be exported to other formats
    '''

    TooltableTypeJSON     = translate("PathToolLibraryManager", "Tooltable JSON (*.json)")
    TooltableTypeXML      = translate("PathToolLibraryManager", "Tooltable XML (*.xml)")
    TooltableTypeHeekscad = translate("PathToolLibraryManager", "HeeksCAD tooltable (*.tooltable)")
    TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)")

    PreferenceMainLibraryXML = "ToolLibrary"
    PreferenceMainLibraryJSON = "ToolLibrary-Main"

    def __init__(self):
        self.prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
        self.toolTables = []
        self.currentTableName = None
        self.loadToolTables()

    def getToolTables(self):
        ''' Return tool table list '''
        return self.toolTables

    def getCurrentTableName(self):
        ''' return the name of the currently loaded tool table '''
        return self.currentTableName

    def getCurrentTable(self):
        ''' returns an object of the current tool table '''
        return self.getTableFromName(self.currentTableName)
    
    def getTableFromName(self, name):
        ''' get the tool table object from the name '''
        for table in self.toolTables:
            if table.Name == name:
                return table

    def getNextToolTableName(self, tableName='Tool Table'):
        ''' get a unique name for a new tool table '''
        iter = 1
        tempName = tableName[-2:] 

        if tempName[0] == '-' and tempName[-1].isdigit():
            tableName = tableName[:-2]

        while any(table.Name == tableName + '-' + str(iter) for table in self.toolTables):
            iter += 1              
        
        return tableName + '-' + str(iter) 

    def addNewToolTable(self):
        ''' creates a new tool table '''
        tt = Path.Tooltable()
        tt.Version = 1
        name = self.getNextToolTableName()
        tt.Name = name
        self.toolTables.append(tt)
        self.saveMainLibrary()
        return name

    def deleteToolTable(self):
        ''' deletes the selected tool table '''
        if len(self.toolTables):
            index = next((index for (index, d) in enumerate(self.toolTables) if d.Name == self.currentTableName), None) 
            self.toolTables.pop(index)
            self.saveMainLibrary()

    def renameToolTable(self, newName, index):
        ''' renames a tool table with the new name'''
        currentTableName = self.toolTables[index].Name
        if newName == currentTableName:
            PathLog.error(translate('PathToolLibraryManager', "Tool Table Same Name"))
            return False
        if newName in self.toolTables:
            PathLog.error(translate('PathToolLibraryManager', "Tool Table Name Exists"))
            return False
        tt = self.getTableFromName(currentTableName)
        if tt:
            tt.Name = newName
            self.saveMainLibrary()
            return True
        

    def templateAttrs(self):
        ''' gets the tool table arributes '''
        toolTables = []
        for tt in self.toolTables:
            tableData = {}
            tableData['Version'] = 1
            tableData['TableName'] = tt.Name

            toolData = {}
            for tool in tt.Tools:
                toolData[tool] = tt.Tools[tool].templateAttrs()

            tableData['Tools'] = toolData
            toolTables.append(tableData)
                  
        return toolTables

    def tooltableFromAttrs(self, stringattrs):
        if stringattrs.get('Version') and 1 == int(stringattrs['Version']):
            
            tt = Path.Tooltable()
            tt.Version = 1
            tt.Name = self.getNextToolTableName()

            if stringattrs.get('Version'):
                tt.Version = stringattrs.get('Version')

            if stringattrs.get('TableName'):
                tt.Name = stringattrs.get('TableName')
                if any(table.Name == tt.Name for table in self.toolTables):
                    tt.Name = self.getNextToolTableName(tt.Name)
            
            for key, attrs in PathUtil.keyValueIter(stringattrs['Tools']):
                    tool = Path.Tool() 
                    tool.Name = str(attrs["name"])
                    tool.ToolType = str(attrs["tooltype"])
                    tool.Material = str(attrs["material"])
                    tool.Diameter = float(attrs["diameter"])
                    tool.LengthOffset = float(attrs["lengthOffset"])
                    tool.FlatRadius = float(attrs["flatRadius"])
                    tool.CornerRadius = float(attrs["cornerRadius"])
                    tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"])
                    tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"])
                    tt.setTool(int(key), tool)

            return tt
        else:
            PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable template version %s") % stringattrs.get('Version'))
        return None

    def loadToolTables(self):
        ''' loads the tool tables from the stored data '''
        self.toolTables = []
        self.currentTableName = ''

        def addTable(tt):
            if tt:
                self.toolTables.append(tt)
            else:
                PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable"))

        prefString = self.prefs.GetString(self.PreferenceMainLibraryJSON, "")

        if not prefString:
            return

        prefsData = json.loads(prefString)

        if isinstance(prefsData, dict):
            tt = self.tooltableFromAttrs(prefsData)
            addTable(tt)

        if isinstance(prefsData, list):
            for table in prefsData:
                tt = self.tooltableFromAttrs(table)
                addTable(tt)

        if len(self.toolTables):
            self.currentTableName = self.toolTables[0].Name

    def saveMainLibrary(self):
        '''Persists the permanent library to FreeCAD user preferences'''
        tmpstring = json.dumps(self.templateAttrs())
        self.prefs.SetString(self.PreferenceMainLibraryJSON, tmpstring)
        self.loadToolTables()
        return True

    def getJobList(self):
        '''Builds the list of all Tool Table lists'''
        tablelist = []

        for o in FreeCAD.ActiveDocument.Objects:
            if hasattr(o, "Proxy"):
                if isinstance(o.Proxy, PathScripts.PathJob.ObjectJob):
                    tablelist.append(o.Label)
                    
        return tablelist

    def getTool(self, listname, toolnum):
        ''' gets the tool object '''
        tt = self.getTableFromName(listname)
        return tt.getTool(toolnum)

    def getTools(self, tablename):
        '''returns the tool data for a given table'''
        tooldata = []
        tableExists =  any(table.Name == tablename for table in self.toolTables)
        if tableExists:
            self.currentTableName = tablename
        else:
            return None

        tt = self.getTableFromName(tablename)
        headers = ["","Tool Num.","Name","Tool Type","Diameter"]
        model = QtGui.QStandardItemModel()
        model.setHorizontalHeaderLabels(headers)

        def unitconv(ivalue):
            val = FreeCAD.Units.Quantity(ivalue, FreeCAD.Units.Length)
            displayed_val = val.UserString      #just the displayed value-not the internal one
            return displayed_val

        if tt:
            if len(tt.Tools) == 0:
                tooldata.append([])
            for number, t in PathUtil.keyValueIter(tt.Tools):

                itemcheck = QtGui.QStandardItem()
                itemcheck.setCheckable(True)
                itemNumber =  QtGui.QStandardItem(str(number))
                itemName =  QtGui.QStandardItem(t.Name)
                itemToolType =  QtGui.QStandardItem(t.ToolType)
                itemDiameter =  QtGui.QStandardItem(unitconv(t.Diameter))

                row = [itemcheck, itemNumber, itemName, itemToolType, itemDiameter]
                model.appendRow(row)

        return model

    # methods for importing and exporting
    def read(self, filename, listname):
        "imports a tooltable from a file"

        importedTables = []

        try:
            fileExtension = os.path.splitext(filename[0])[1].lower()
            xmlHandler = None
            if fileExtension == '.tooltable':
                xmlHandler = HeeksTooltableHandler()
            if fileExtension == '.xml':
                xmlHandler = FreeCADTooltableHandler()

            if xmlHandler:
                parser = xml.sax.make_parser()
                parser.setFeature(xml.sax.handler.feature_namespaces, 0)
                parser.setContentHandler(xmlHandler)
                parser.parse(PathUtil.toUnicode(filename[0]))
                if not xmlHandler.tooltable:
                    return None

                ht = xmlHandler.tooltable
            else:
                with open(PathUtil.toUnicode(filename[0]), "rb") as fp:
                    tableData = json.load(fp)

                    if isinstance(tableData, dict):
                        ht = self.tooltableFromAttrs(tableData)
                        if ht:
                            importedTables.append(ht)

                    if isinstance(tableData, list):
                        for table in tableData:
                            ht = self.tooltableFromAttrs(table)                 
                            if ht:
                                importedTables.append(ht)

            if importedTables:
                for tt in importedTables:
                    self.toolTables.append(tt)
                
                self.saveMainLibrary()
                return True
            else:
                return False

        except Exception as e: # pylint: disable=broad-except
            print("could not parse file", e)


    def write(self, filename, listname):
        "exports the tooltable to a file"
        tt = self.getTableFromName(listname)
        if tt:
            try:
                def openFileWithExtension(name, ext):
                    fext = os.path.splitext(name)[1].lower()
                    if fext != ext:
                        name = "{}{}".format(name, ext)
                    return (open(PathUtil.toUnicode(name), 'w'), name)

                if filename[1] == self.TooltableTypeXML:
                    fp,fname = openFileWithExtension(filename[0], '.xml')
                    fp.write('<?xml version="1.0" encoding="UTF-8"?>\n')
                    fp.write(tt.Content)
                elif filename[1] == self.TooltableTypeLinuxCNC:
                    fp,fname = openFileWithExtension(filename[0], '.tbl')
                    for key in tt.Tools:
                        t = tt.Tools[key]
                        fp.write("T{0} P{0} Y{1} Z{2} A{3} B{4} C{5} U{6} V{7} W{8} D{9} I{10} J{11} Q{12} ;{13}\n".format(key,0,t.LengthOffset,0,0,0,0,0,0,t.Diameter,0,0,0,t.Name))
                else:
                    fp,fname = openFileWithExtension(filename[0], '.json')
                    json.dump(self.templateAttrs(), fp, sort_keys=True, indent=2)

                fp.close()
                print("Written ", PathUtil.toUnicode(fname))

            except Exception as e: # pylint: disable=broad-except
                print("Could not write file:", e)

    def addnew(self, listname, tool, position = None):
        "adds a new tool at the end of the table"
        tt = self.getTableFromName(listname)
        if not tt:
            tt = Path.Tooltable()
        if position is None:
            tt.addTools(tool)
            newID = list(tt.Tools)[-1]
        else:
            tt.setTool(position, tool)
            newID = position

        if listname == self.getCurrentTableName():
            self.saveMainLibrary()
        return newID

    def updateTool(self, listname, toolnum, tool):
        '''updates tool data'''
        tt = self.getTableFromName(listname)
        tt.deleteTool(toolnum)
        tt.setTool(toolnum, tool)
        if listname == self.getCurrentTableName():
            return self.saveMainLibrary()
        return True

    def moveup(self, number, listname):
        "moves a tool to a lower number, if possible"
        target = number - 1
        if number < 2:
            return False, target
        target = number - 1
        tt = self.getTableFromName(listname)
        t1 = tt.getTool(number).copy()
        tt.deleteTool(number)
        if target in tt.Tools.keys():
            t2 = tt.getTool(target).copy()
            tt.deleteTool(target)
            tt.setTool(number, t2)
        tt.setTool(target, t1)
        if listname == self.getCurrentTableName():
            self.saveMainLibrary()
        return True, target

    def movedown(self, number, listname):
        "moves a tool to a higher number, if possible"
        tt = self.getTableFromName(listname)
        target = number + 1
        t1 = tt.getTool(number).copy()
        tt.deleteTool(number)
        if target in tt.Tools.keys():
            t2 = tt.getTool(target).copy()
            tt.deleteTool(target)
            tt.setTool(number, t2)
        tt.setTool(target, t1)
        if listname == self.getCurrentTableName():
            self.saveMainLibrary()
        return True, target

    def duplicate(self, number, listname):
        ''' duplicates the selected tool in the selected tool table '''
        tt = self.getTableFromName(listname)
        tool = tt.getTool(number).copy()
        tt.addTools(tool)

        newID = list(tt.Tools)[-1]

        if listname == self.getCurrentTableName():
            self.saveMainLibrary()
        return True, newID

    def moveToTable(self, number, listname):
        ''' Moves the tool to selected tool table '''
        fromTable = self.getTableFromName(self.getCurrentTableName())
        toTable = self.getTableFromName(listname)
        tool = fromTable.getTool(number).copy()
        toTable.addTools(tool)
        fromTable.deleteTool(number)

    def delete(self, number, listname):
        '''deletes a tool from the current list'''
        tt = self.getTableFromName(listname)
        tt.deleteTool(number)
        if listname == self.getCurrentTableName():
            self.saveMainLibrary()
        return True