using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace MultiWheelC.TrajectoryPlanning.EMPlanner;

internal static class OsqpNativeMethods
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate IntPtr VersionDelegate();

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate void SetDefaultSettingsDelegate(IntPtr settings);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate int SetupDelegate(
        out IntPtr solver,
        IntPtr upperTriangularP,
        IntPtr linearCost,
        IntPtr constraints,
        IntPtr lowerBounds,
        IntPtr upperBounds,
        int constraintCount,
        int variableCount,
        IntPtr settings);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate int WarmStartDelegate(IntPtr solver, IntPtr primal, IntPtr dual);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate int SolveDelegate(IntPtr solver);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate int CleanupDelegate(IntPtr solver);

    public static IntPtr LoadLibrary(string absolutePath)
    {
        return LoadLibraryW(absolutePath);
    }

    public static bool FreeModule(IntPtr moduleHandle)
    {
        return FreeLibrary(moduleHandle);
    }

    public static string LastErrorMessage()
    {
        int error = Marshal.GetLastWin32Error();
        return new Win32Exception(error).Message;
    }

    public static OsqpNativeApi ResolveApi(IntPtr moduleHandle)
    {
        return new OsqpNativeApi(
            Resolve<VersionDelegate>(moduleHandle, "osqp_version"),
            Resolve<SetDefaultSettingsDelegate>(moduleHandle, "osqp_set_default_settings"),
            Resolve<SetupDelegate>(moduleHandle, "osqp_setup"),
            Resolve<WarmStartDelegate>(moduleHandle, "osqp_warm_start"),
            Resolve<SolveDelegate>(moduleHandle, "osqp_solve"),
            Resolve<CleanupDelegate>(moduleHandle, "osqp_cleanup"));
    }

    private static T Resolve<T>(IntPtr moduleHandle, string exportName) where T : Delegate
    {
        IntPtr procedure = GetProcAddress(moduleHandle, exportName);
        if (procedure == IntPtr.Zero)
            throw new InvalidOperationException("Missing OSQP export " + exportName + ".");
        return Marshal.GetDelegateForFunctionPointer<T>(procedure);
    }

    [DllImport("kernel32", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    private static extern IntPtr LoadLibraryW(string fileName);

    [DllImport("kernel32", ExactSpelling = true, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool FreeLibrary(IntPtr moduleHandle);

    [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr moduleHandle, string procedureName);
}

internal sealed class OsqpNativeApi
{
    public OsqpNativeApi(
        OsqpNativeMethods.VersionDelegate version,
        OsqpNativeMethods.SetDefaultSettingsDelegate setDefaultSettings,
        OsqpNativeMethods.SetupDelegate setup,
        OsqpNativeMethods.WarmStartDelegate warmStart,
        OsqpNativeMethods.SolveDelegate solve,
        OsqpNativeMethods.CleanupDelegate cleanup)
    {
        Version = version;
        SetDefaultSettings = setDefaultSettings;
        Setup = setup;
        WarmStart = warmStart;
        Solve = solve;
        Cleanup = cleanup;
    }

    public OsqpNativeMethods.VersionDelegate Version { get; }

    public OsqpNativeMethods.SetDefaultSettingsDelegate SetDefaultSettings { get; }

    public OsqpNativeMethods.SetupDelegate Setup { get; }

    public OsqpNativeMethods.WarmStartDelegate WarmStart { get; }

    public OsqpNativeMethods.SolveDelegate Solve { get; }

    public OsqpNativeMethods.CleanupDelegate Cleanup { get; }
}
