# -*- 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 Path
import PathScripts
import PathScripts.PathLog as PathLog
import PathScripts.PathUtil as PathUtil
import json
import os
import xml.sax
from PySide import QtGui

if False:
    PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
    PathLog.trackModule(PathLog.thisModule())
else:
    PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())

translate = FreeCAD.Qt.translate

# 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:
            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:
                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
