//
// Copyright (c) 2022 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Xml.Linq;

namespace TermSharp.Example
{
    public static class DllMap
    {
        // Register a call-back for native library resolution.
        public static void Register(Assembly assembly)
        {
            NativeLibrary.SetDllImportResolver(assembly, MapAndLoad);
        }

        // The callback which loads the mapped libray in place of the original.
        private static IntPtr MapAndLoad(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
        {
            var wasMapped = MapLibraryName(assembly.Location, libraryName, out var mappedName);
            if(!wasMapped)
            {
                mappedName = libraryName;
            }
            return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
        }

        // Parse the dll.config file and map the old name to the new name of a library.
        private static bool MapLibraryName(string assemblyLocation, string originalLibName, out string mappedLibName)
        {
            string xmlPath = Path.Combine(Path.GetDirectoryName(assemblyLocation), Path.GetFileNameWithoutExtension(assemblyLocation) + ".dll.config");
            mappedLibName = null;

            if (!File.Exists(xmlPath))
            {
                return false;
            }

            var currOsAttr = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" : 
                             RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "windows" : 
                             RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" : null;

            XElement root = XElement.Load(xmlPath);
            var map = (
                from el in root.Elements("dllmap")
                where (string)el.Attribute("dll") == originalLibName
                let osAttr = (string)el.Attribute("os")
                where (osAttr == null || osAttr == currOsAttr || (osAttr.StartsWith("!") && !osAttr.Contains(currOsAttr)))
                select el
            ).SingleOrDefault();

            if (map != null)
            {
                mappedLibName = map.Attribute("target").Value;
            }

            return (mappedLibName != null);
        }
    }
}