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

# ***************************************************************************
# *   Copyright (c) 2013 Juergen Riegel <FreeCAD@juergen-riegel.net>        *
# *                                                                         *
# *   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                                                                   *
# *                                                                         *
# ***************************************************************************


__title__ = "FreeCAD material card importer"
__author__ = "Juergen Riegel"
__url__ = "https://www.freecad.org"


import os
import glob
import uuid
from pathlib import Path
import xml.etree.ElementTree as ET

def decode(name):
    "decodes encoded strings"
    try:
        decodedName = (name.decode("utf8"))
    except UnicodeDecodeError:
        try:
            decodedName = (name.decode("latin1"))
        except UnicodeDecodeError:
            print("Error: Couldn't determine character encoding")
            decodedName = name
    return decodedName

def read(filename):
    "reads a FCMat file and returns a dictionary from it"

    # the reader returns a dictionary in any case even if the file  has problems
    # an empty dict is returned in such case

    card_name_file = os.path.splitext(os.path.basename(filename))[0]
    f = open(filename, encoding="utf8")
    try:
        content = f.readlines()
    except Exception:
        # https://forum.freecad.org/viewtopic.php?f=18&t=56912#p489721
        # older FreeCAD do not write utf-8 for special character on windows
        # I have seen "ISO-8859-15" or "windows-1252"
        # explicit utf-8 writing, https://github.com/FreeCAD/FreeCAD/commit/9a564dd906f
        print("Error on card loading. File might not utf-8.")
        error_message = "Error on loading. Material file '{}' might not utf-8.".format(filename)
        print("{}\n".format(error_message))
        return {}
    d = {}
    d["Meta"] = {}
    d["General"] = {}
    d["Mechanical"] = {}
    d["Fluidic"] = {}
    d["Thermal"] = {}
    d["Electromagnetic"] = {}
    d["Architectural"] = {}
    d["Rendering"] = {}
    d["VectorRendering"] = {}
    d["Cost"] = {}
    d["UserDefined"] = {}
    d["Meta"]["CardName"] = card_name_file  # CardName is the MatCard file name
    section = ''
    for ln, line in enumerate(content):
        # line numbers are used for CardName and AuthorAndLicense
        # the use of line number is not smart for a data model
        # a wrong user edit could break the file

        # comment
        if line.startswith('#'):
            # a '#' is assumed to be a comment which is ignored
            continue
        # CardName
        if line.startswith(';') and ln == 0:
            pass

        # AuthorAndLicense
        elif line.startswith(';') and ln == 1:
            v = line.split(";")[1].strip()  # Line 2
            if hasattr(v, "decode"):
                v = v.decode('utf-8')
            d["General"]["AuthorAndLicense"] = v # Move the field to the general group

        # rest
        else:
            # ; is a Comment
            # [ is a Section
            if line[0] == '[':
                line = line[1:]
                k = line.split("]", 1)
                if len(k) >= 2:
                    v = k[0].strip()
                    if hasattr(v, "decode"):
                        v = v.decode('utf-8')
                    section = v
            elif line[0] not in ";":
                # split once on first occurrence
                # a link could contain a '=' and thus would be split
                k = line.split("=", 1)
                if len(k) == 2:
                    v = k[1].strip()
                    if hasattr(v, "decode"):
                        v = v.decode('utf-8')
                    d[section][k[0].strip()] = v
    return d

def yamGeneral(card):
    father = ""
    materialStandard = ""
    yamModels = ""
    yam = "# File created by ConvertFCMat.py\n"
    yam += "General:\n"

    # Add UUIDs
    yam += '  UUID: "{0}"\n'.format(uuid.uuid4())
    for param in card:
        if param in ["Name", "AuthorAndLicense", "Description", "ReferenceSource", "SourceURL"]:
            yam += '  {0}: "{1}"\n'.format(param, card[param])
        elif param  in ["Father"]:
            father += '    {0}: "{1}"\n'.format(param, card[param])
        elif param  in ["KindOfMaterial", "MaterialNumber", "Norm", "StandardCode"]:
            if param == "Norm": # Handle the name change
                materialStandard += '    {0}: "{1}"\n'.format("StandardCode", card[param])
            else:
                materialStandard += '    {0}: "{1}"\n'.format(param, card[param])

    if len(father) > 0:
        yamModels += "  {0}:\n".format('Father')
        yamModels += "    UUID: '{0}'\n".format('9cdda8b6-b606-4778-8f13-3934d8668e67')
        yamModels += father
    if len(materialStandard) > 0:
        yamModels += "  {0}:\n".format('MaterialStandard')
        yamModels += "    UUID: '{0}'\n".format('1e2c0088-904a-4537-925f-64064c07d700')
        yamModels += materialStandard

    return yam, yamModels

def yamSection(card, header, uuid):
    if len(card) > 0:
        yam = "  {0}:\n".format(header)
        yam += "    UUID: '{0}'\n".format(uuid)
        for param in card:
            yam += '    {0}: "{1}"\n'.format(param, card[param])
    else:
        yam = ""

    return yam

def yamMechanical(card):
    # Check which model we need
    useDensity = False
    useIso = False
    useLinearElastic = False
    for param in card:
        if param in ["Density"]:
            useDensity = True
        elif param in ["BulkModulus", "PoissonRatio", "ShearModulus", "YoungsModulus"]:
            useIso = True
        elif param in ["AngleOfFriction", "CompressiveStrength", "FractureToughness",
                       "UltimateStrain", "UltimateTensileStrength", "YieldStrength", "Stiffness", "Hardness"]:
            useLinearElastic = True

    yam = ""
    if useLinearElastic:
        return yamSection(card, 'LinearElastic', '7b561d1d-fb9b-44f6-9da9-56a4f74d7536')
    if useIso:
        yam = yamSection(card, 'IsotropicLinearElastic', 'f6f9e48c-b116-4e82-ad7f-3659a9219c50')
    if useDensity:
        return yam + yamSection(card, 'Density', '454661e5-265b-4320-8e6f-fcf6223ac3af')

    # default mechanical model
    return ""

def yamFluid(card):
    # Split out density
    for param in card:
        if param not in ["Density"]:
            return yamSection(card, 'Fluid', '1ae66d8c-1ba1-4211-ad12-b9917573b202')

    return yamSection(card, 'Density', '454661e5-265b-4320-8e6f-fcf6223ac3af')

def yamThermal(card):
    return yamSection(card, 'Thermal', '9959d007-a970-4ea7-bae4-3eb1b8b883c7')

def yamElectromagnetic(card):
    return yamSection(card, 'Electromagnetic', 'b2eb5f48-74b3-4193-9fbb-948674f427f3')

def yamArchitectural(card):
    return yamSection(card, 'Architectural', '32439c3b-262f-4b7b-99a8-f7f44e5894c8')

def yamCost(card):
    return yamSection(card, 'Costs', '881df808-8726-4c2e-be38-688bb6cce466')

def yamRendering(card):
    # Check which model we need
    useTexture = False
    useAdvanced = False
    for param in card:
        if param in ["TexturePath", "TextureScaling"]:
            useTexture = True
        elif param in ["FragmentShader", "VertexShader"]:
            useAdvanced = True

    if useAdvanced:
        return yamSection(card, 'AdvancedRendering', 'c880f092-cdae-43d6-a24b-55e884aacbbf')
    if useTexture:
        return yamSection(card, 'TextureRendering', 'bbdcc65b-67ca-489c-bd5c-a36e33d1c160')

    # default rendering model
    return yamSection(card, 'BasicRendering', 'f006c7e4-35b7-43d5-bbf9-c5d572309e6e')

def yamVectorRendering(card):
    return yamSection(card, 'VectorRendering', 'fdf5a80e-de50-4157-b2e5-b6e5f88b680e')

def saveYaml(card, output):
    yam, yamModels = yamGeneral(card["General"])
    if len(card["Mechanical"]) > 0 or \
        len(card["Fluidic"]) > 0 or \
        len(card["Thermal"]) > 0 or \
        len(card["Electromagnetic"]) > 0 or \
        len(card["Architectural"]) > 0 or \
        len(card["Cost"]) > 0 or \
        len(yamModels) > 0:
        yam += "Models:\n"
        yam += yamModels
        if "Mechanical" in card:
            yam += yamMechanical(card["Mechanical"])
        if "Fluidic" in card:
            yam += yamFluid(card["Fluidic"])
        if "Thermal" in card:
            yam += yamThermal(card["Thermal"])
        if "Electromagnetic" in card:
            yam += yamElectromagnetic(card["Electromagnetic"])
        if "Architectural" in card:
            yam += yamArchitectural(card["Architectural"])
        if "Cost" in card:
            yam += yamCost(card["Cost"])
    if len(card["Rendering"]) > 0 or len(card["VectorRendering"]) > 0:
        yam += "AppearanceModels:\n"
        if "Rendering" in card:
            yam += yamRendering(card["Rendering"])
        if "VectorRendering" in card:
            yam += yamVectorRendering(card["VectorRendering"])

    file = open(output, "w", encoding="utf-8")
    file.write(yam)
    file.close()

def convert(infolder, outfolder):
    a_path = infolder + '/**/*.FCMat'
    dir_path_list = glob.glob(a_path, recursive=True)

    for a_path in dir_path_list:
        p = Path(a_path)
        relative = p.relative_to(infolder)
        out = Path(outfolder) / relative
        print("('{0}', '{1}') -> {2}".format(infolder, relative, out))

        try:
            card = read(p)
        except Exception:
            print("Error converting card '{0}'. Skipped.")
            continue

        out.parent.mkdir(parents=True, exist_ok=True)
        saveYaml(card, out)

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("infolder", help="Input folder containing older material cards")
parser.add_argument("outfolder", help="Output folder to place the converted material cards")
args = parser.parse_args()

print("Input folder '{0}'".format(args.infolder))
print("Output folder '{0}'".format(args.outfolder))

convert(args.infolder, args.outfolder)