using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace MultiWheelC.TrajectoryPlanning.EMPlanner;

public sealed class OsqpNativeLoadResult
{
    internal OsqpNativeLoadResult(QpSolveStatus status, string version, IntPtr moduleHandle, string diagnostic)
    {
        Status = status;
        Version = version ?? string.Empty;
        ModuleHandle = moduleHandle;
        Diagnostic = diagnostic ?? string.Empty;
    }

    public QpSolveStatus Status { get; }

    public string Version { get; }

    public IntPtr ModuleHandle { get; }

    public string Diagnostic { get; }
}

public static class OsqpNativeLoader
{
    private static readonly object Sync = new object();
    private static OsqpNativeLoadResult cachedResult;
    private static OsqpNativeApi loadedApi;

    public static OsqpNativeLoadResult Load()
    {
        lock (Sync)
        {
            if (cachedResult != null)
                return cachedResult;

            if (IntPtr.Size != 8)
                return CacheFailure(QpSolveStatus.SolverUnavailable, "OSQP v1.0.0 requires a 64-bit process.");

            try
            {
                OsqpNativeStructures.ValidatePinnedLayout();
                string nativePath = ResolveNativePath();
                if (!File.Exists(nativePath))
                    return CacheFailure(QpSolveStatus.SolverUnavailable, "OSQP native library was not found at " + nativePath + ".");

                IntPtr moduleHandle = OsqpNativeMethods.LoadLibrary(nativePath);
                if (moduleHandle == IntPtr.Zero)
                    return CacheFailure(QpSolveStatus.SolverUnavailable,
                        "OSQP native library could not be loaded from " + nativePath + ": " + OsqpNativeMethods.LastErrorMessage());

                try
                {
                    OsqpNativeApi api = OsqpNativeMethods.ResolveApi(moduleHandle);
                    string version = Marshal.PtrToStringAnsi(api.Version()) ?? string.Empty;
                    if (!string.Equals(version, "1.0.0", StringComparison.Ordinal))
                    {
                        OsqpNativeMethods.FreeModule(moduleHandle);
                        return CacheFailure(QpSolveStatus.SolverUnavailable,
                            "OSQP native library version " + version + " does not match required version 1.0.0.");
                    }

                    loadedApi = api;
                    cachedResult = new OsqpNativeLoadResult(QpSolveStatus.Solved, version, moduleHandle, string.Empty);
                    return cachedResult;
                }
                catch (Exception exception)
                {
                    OsqpNativeMethods.FreeModule(moduleHandle);
                    return CacheFailure(QpSolveStatus.SolverUnavailable,
                        "OSQP native library exports could not be initialized: " + exception.Message);
                }
            }
            catch (Exception exception)
            {
                return CacheFailure(QpSolveStatus.NativeError, "OSQP native loader failed: " + exception.Message);
            }
        }
    }

    internal static bool TryGetLoadedApi(out OsqpNativeApi api, out OsqpNativeLoadResult result)
    {
        result = Load();
        api = loadedApi;
        return result.Status == QpSolveStatus.Solved && api != null;
    }

    private static OsqpNativeLoadResult CacheFailure(QpSolveStatus status, string diagnostic)
    {
        cachedResult = new OsqpNativeLoadResult(status, string.Empty, IntPtr.Zero, diagnostic);
        return cachedResult;
    }

    private static string ResolveNativePath()
    {
        Assembly assembly = typeof(OsqpNativeLoader).Assembly;
        string assemblyLocation = assembly.Location;
        if (string.IsNullOrEmpty(assemblyLocation) || !Path.IsPathRooted(assemblyLocation))
            throw new InvalidOperationException("The EM Planner assembly location must be an absolute file path.");

        string pluginDirectory = Path.GetDirectoryName(assemblyLocation);
        if (string.IsNullOrEmpty(pluginDirectory))
            throw new InvalidOperationException("The EM Planner assembly location has no plugin directory.");

        return Path.GetFullPath(Path.Combine(pluginDirectory, "osqp.dll"));
    }
}
