# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *   Copyright (c) 2017 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                                                                   *
# *                                                                         *
# ***************************************************************************

import FreeCAD
import Path
import Path.Base.SetupSheet as PathSetupSheet
import json
import sys

# Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())

from CAMTests.PathTestUtils import PathTestBase


def refstring(string):
    return string.replace(" u'", " '")


class SomeOp(object):
    def __init__(self, obj):
        Path.Log.track(obj, type(obj))
        obj.addProperty("App::PropertyPercent", "StepOver", "Base", "Some help you are")

    @classmethod
    def SetupProperties(cls):
        return ["StepOver"]

    @classmethod
    def Create(cls, name, obj=None, parentJob=None):
        Path.Log.track(name, obj)
        if obj is None:
            obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
        obj.Proxy = SomeOp(obj)
        return obj


class TestPathSetupSheet(PathTestBase):
    def setUp(self):
        self.doc = FreeCAD.newDocument("TestPathSetupSheet")

    def tearDown(self):
        FreeCAD.closeDocument(self.doc.Name)

    def test00(self):
        """Verify SetupSheet templateAttributes"""
        ss = PathSetupSheet.Create().Proxy
        self.doc.recompute()

        attrs = ss.templateAttributes(True, True)

        self.assertEqualLocale(attrs[PathSetupSheet.Template.HorizRapid], "0.00 mm/s")
        self.assertEqualLocale(attrs[PathSetupSheet.Template.VertRapid], "0.00 mm/s")
        self.assertEqualLocale(attrs[PathSetupSheet.Template.SafeHeightOffset], "3.00 mm")
        self.assertEqual(
            attrs[PathSetupSheet.Template.SafeHeightExpression],
            "OpStockZMax+SetupSheet.SafeHeightOffset",
        )
        self.assertEqualLocale(attrs[PathSetupSheet.Template.ClearanceHeightOffset], "5.00 mm")
        self.assertEqual(
            attrs[PathSetupSheet.Template.ClearanceHeightExpression],
            "OpStockZMax+SetupSheet.ClearanceHeightOffset",
        )

    def test01(self):
        """Verify SetupSheet template attributes roundtrip."""
        o1 = PathSetupSheet.Create()
        self.doc.recompute()
        o1.VertRapid = "10 mm/s"
        o1.HorizRapid = "22 mm/s"
        o1.SafeHeightOffset = "18 mm"
        o1.SafeHeightExpression = "Hugo+Olga"
        o1.ClearanceHeightOffset = "23 mm"
        o1.ClearanceHeightExpression = "Peter+Paul"
        o1.StartDepthExpression = "Alpha"
        o1.FinalDepthExpression = "Omega"
        o1.StepDownExpression = "1"

        o2 = PathSetupSheet.Create()
        self.doc.recompute()
        o2.Proxy.setFromTemplate(o1.Proxy.templateAttributes())
        self.doc.recompute()

        # Need to compare the UserString's due to rounding errors depending on the
        # user's unit settings - should have no impact on the validity

        self.assertEqual(o1.VertRapid.UserString, o2.VertRapid.UserString)
        self.assertEqual(o1.HorizRapid.UserString, o2.HorizRapid.UserString)
        self.assertEqual(o1.SafeHeightOffset.UserString, o2.SafeHeightOffset.UserString)
        self.assertEqual(o1.SafeHeightExpression, o2.SafeHeightExpression)
        self.assertEqual(o1.ClearanceHeightOffset.UserString, o2.ClearanceHeightOffset.UserString)
        self.assertEqual(o1.ClearanceHeightExpression, o2.ClearanceHeightExpression)
        self.assertEqual(o1.StartDepthExpression, o2.StartDepthExpression)
        self.assertEqual(o1.FinalDepthExpression, o2.FinalDepthExpression)
        self.assertEqual(o1.StepDownExpression, o2.StepDownExpression)

    def test02(self):
        """Verify default value detection logic."""
        obj = PathSetupSheet.Create()
        ss = obj.Proxy

        self.assertTrue(ss.hasDefaultToolRapids())
        self.assertTrue(ss.hasDefaultOperationHeights())
        self.assertTrue(ss.hasDefaultOperationDepths())

        obj.VertRapid = "1 mm/s"
        self.assertFalse(ss.hasDefaultToolRapids())
        obj.VertRapid = "0 mm/s"
        self.assertTrue(ss.hasDefaultToolRapids())
        obj.HorizRapid = "1 mm/s"
        self.assertFalse(ss.hasDefaultToolRapids())
        obj.HorizRapid = "0 mm/s"
        self.assertTrue(ss.hasDefaultToolRapids())

        obj.SafeHeightOffset = "0 mm"
        self.assertFalse(ss.hasDefaultOperationHeights())
        obj.SafeHeightOffset = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultSafeHeightOffset
        )
        self.assertTrue(ss.hasDefaultOperationHeights())
        obj.ClearanceHeightOffset = "0 mm"
        self.assertFalse(ss.hasDefaultOperationHeights())
        obj.ClearanceHeightOffset = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultClearanceHeightOffset
        )
        self.assertTrue(ss.hasDefaultOperationHeights())

        obj.SafeHeightExpression = "0 mm"
        self.assertFalse(ss.hasDefaultOperationHeights())
        obj.SafeHeightExpression = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultSafeHeightExpression
        )
        self.assertTrue(ss.hasDefaultOperationHeights())
        obj.ClearanceHeightExpression = "0 mm"
        self.assertFalse(ss.hasDefaultOperationHeights())
        obj.ClearanceHeightExpression = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultClearanceHeightExpression
        )
        self.assertTrue(ss.hasDefaultOperationHeights())

        obj.StartDepthExpression = ""
        self.assertFalse(ss.hasDefaultOperationDepths())
        obj.StartDepthExpression = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultStartDepthExpression
        )
        self.assertTrue(ss.hasDefaultOperationDepths())
        obj.FinalDepthExpression = ""
        self.assertFalse(ss.hasDefaultOperationDepths())
        obj.FinalDepthExpression = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultFinalDepthExpression
        )
        self.assertTrue(ss.hasDefaultOperationDepths())
        obj.StepDownExpression = ""
        self.assertFalse(ss.hasDefaultOperationDepths())
        obj.StepDownExpression = ss.decodeAttributeString(
            PathSetupSheet.SetupSheet.DefaultStepDownExpression
        )
        self.assertTrue(ss.hasDefaultOperationDepths())

    def test10(self):
        """Verify template attributes encoding/decoding of floats."""
        ss = PathSetupSheet.Create().Proxy

        self.assertEqual(ss.expressionReference(), "SetupSheet")

        self.assertEqual(str(ss.encodeTemplateAttributes({"00": 13.00})), "{'00': 13.0}")
        self.assertEqual(str(ss.decodeTemplateAttributes({"00": 13.00})), "{'00': 13.0}")

    def test11(self):
        """Verify template attributes encoding/decoding of strings."""
        ss = PathSetupSheet.Create().Proxy

        self.assertEqual(
            str(ss.encodeTemplateAttributes({"00": "hugo"})),
            refstring("{'00': u'hugo'}"),
        )
        self.assertEqual(
            str(ss.encodeTemplateAttributes({"00": "SetupSheet"})),
            refstring("{'00': u'${SetupSheet}'}"),
        )
        self.assertEqual(
            str(ss.encodeTemplateAttributes({"00": "SetupSheet.y"})),
            refstring("{'00': u'${SetupSheet}.y'}"),
        )
        self.assertEqual(
            str(ss.encodeTemplateAttributes({"00": "${hugo}"})),
            refstring("{'00': u'${hugo}'}"),
        )

        self.assertEqual(
            str(ss.decodeTemplateAttributes({"00": "hugo"})),
            refstring("{'00': u'hugo'}"),
        )
        self.assertEqual(
            str(ss.decodeTemplateAttributes({"00": "${SetupSheet}"})),
            refstring("{'00': u'SetupSheet'}"),
        )
        self.assertEqual(
            str(ss.decodeTemplateAttributes({"00": "${SetupSheet}.y"})),
            refstring("{'00': u'SetupSheet.y'}"),
        )
        self.assertEqual(
            str(ss.decodeTemplateAttributes({"00": "${SetupSheet}.y - ${SetupSheet}.z"})),
            refstring("{'00': u'SetupSheet.y - SetupSheet.z'}"),
        )

    def test12(self):
        """Verify template attributes encoding/decoding of dictionaries."""
        ss = PathSetupSheet.Create().Proxy

        self.assertEqual(
            str(ss.encodeTemplateAttributes({"00": {"01": "hugo"}})),
            refstring("{'00': {'01': u'hugo'}}"),
        )
        self.assertEqual(
            str(ss.encodeTemplateAttributes({"00": {"01": "SetupSheet.y - SetupSheet.z"}})),
            refstring("{'00': {'01': u'${SetupSheet}.y - ${SetupSheet}.z'}}"),
        )

        self.assertEqual(
            str(ss.decodeTemplateAttributes({"00": {"01": "hugo"}})),
            refstring("{'00': {'01': u'hugo'}}"),
        )
        self.assertEqual(
            str(ss.decodeTemplateAttributes({"00": {"01": "${SetupSheet}.y - ${SetupSheet}.z"}})),
            refstring("{'00': {'01': u'SetupSheet.y - SetupSheet.z'}}"),
        )

    def test13(self):
        """Verify template attributes encoding/decoding of lists."""
        ss = PathSetupSheet.Create().Proxy

        attrs = {}
        attrs["00"] = "x.SetupSheet"
        attrs["01"] = [{"10": "SetupSheet", "11": "SetupSheet.y"}, {"20": "SetupSheet"}]
        attrs["02"] = [
            {
                "a": [{"b": "SetupSheet"}, {"c": "SetupSheet"}],
                "b": [{"b": "SetupSheet"}],
            }
        ]

        encoded = ss.encodeTemplateAttributes(attrs)
        self.assertEqual(encoded["00"], "x.${SetupSheet}")
        self.assertEqual(len(encoded["01"]), 2)
        self.assertEqual(encoded["01"][0]["10"], "${SetupSheet}")
        self.assertEqual(encoded["01"][0]["11"], "${SetupSheet}.y")
        self.assertEqual(str(encoded["01"][1]), refstring("{'20': u'${SetupSheet}'}"))
        self.assertEqual(len(encoded["02"]), 1)
        self.assertEqual(len(encoded["02"][0]["a"]), 2)
        self.assertEqual(str(encoded["02"][0]["a"][0]), refstring("{'b': u'${SetupSheet}'}"))
        self.assertEqual(str(encoded["02"][0]["a"][1]), refstring("{'c': u'${SetupSheet}'}"))
        self.assertEqual(len(encoded["02"][0]["b"]), 1)
        self.assertEqual(str(encoded["02"][0]["b"][0]), refstring("{'b': u'${SetupSheet}'}"))

        decoded = ss.decodeTemplateAttributes(encoded)
        self.assertEqual(len(decoded), len(attrs))
        self.assertEqual(decoded["00"], attrs["00"])
        self.assertEqual(len(decoded["01"]), len(attrs["01"]))
        self.assertEqual(decoded["01"][0]["10"], attrs["01"][0]["10"])
        self.assertEqual(decoded["01"][0]["11"], attrs["01"][0]["11"])
        self.assertEqual(decoded["01"][1]["20"], attrs["01"][1]["20"])
        self.assertEqual(len(decoded["02"]), len(attrs["02"]))
        self.assertEqual(len(decoded["02"][0]["a"]), len(attrs["02"][0]["a"]))
        self.assertEqual(decoded["02"][0]["a"][0]["b"], attrs["02"][0]["a"][0]["b"])
        self.assertEqual(decoded["02"][0]["a"][1]["c"], attrs["02"][0]["a"][1]["c"])
        self.assertEqual(len(decoded["02"][0]["b"]), len(attrs["02"][0]["b"]))
        self.assertEqual(decoded["02"][0]["b"][0]["b"], attrs["02"][0]["b"][0]["b"])

        # just to be safe ...
        s2 = PathSetupSheet.Create().Proxy
        self.doc.recompute()
        s2.setFromTemplate(ss.templateAttributes())
        self.assertEqual(s2.expressionReference(), "SetupSheet001")
        dec = s2.decodeTemplateAttributes(encoded)
        # pick one
        self.assertEqual(dec["01"][0]["11"], "SetupSheet001.y")

    def test20(self):
        """Verify SetupSheet template op attributes roundtrip."""

        opname = "whoop"

        o1 = PathSetupSheet.Create()

        PathSetupSheet.RegisterOperation(opname, SomeOp.Create, SomeOp.SetupProperties)
        ptt = PathSetupSheet._RegisteredOps[opname].prototype("whoopsy")
        pptt = ptt.getProperty("StepOver")
        pptt.setupProperty(
            o1,
            PathSetupSheet.OpPropertyName(opname, pptt.name),
            PathSetupSheet.OpPropertyGroup(opname),
            75,
        )

        # save setup sheet in json "file"
        attrs = o1.Proxy.templateAttributes(False, False, False, False, [opname])
        encdd = o1.Proxy.encodeTemplateAttributes(attrs)
        j1 = json.dumps({"SetupSheet": encdd}, sort_keys=True, indent=2)

        # restore setup sheet from json "file"
        j2 = json.loads(j1)

        o2 = PathSetupSheet.Create()
        o2.Proxy.setFromTemplate(j2["SetupSheet"])

        op = SomeOp.Create(opname)
        self.assertEqual(op.StepOver, 0)
        o2.Proxy.setOperationProperties(op, opname)
        self.assertEqual(op.StepOver, 75)
