using System.Runtime.InteropServices;

namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS;

/// <summary>
///     P/Invoke surface for Fanuc FWLIB (<c>Fwlib32.dll</c>). Declarations extracted from
///     <c>fwlib32.h</c> in the strangesast/fwlib repo; the licensed DLL itself is NOT shipped
///     with OtOpcUa — the deployment places <c>Fwlib32.dll</c> next to the server executable
///     or on <c>PATH</c>.
/// </summary>
/// <remarks>
///     Deliberately narrow — only the calls <see cref="FwlibFocasClient"/> actually makes.
///     FOCAS has 800+ functions in <c>fwlib32.h</c>; pulling in every one would bloat the
///     P/Invoke surface + signal more coverage than this driver provides. Expand as capabilities
///     are added.
/// </remarks>
internal static class FwlibNative
{
    private const string Library = "Fwlib32.dll";

    // ---- Handle lifetime ----

    /// <summary>Open an Ethernet FWLIB handle. Returns EW_OK (0) on success; handle written out.</summary>
    [DllImport(Library, EntryPoint = "cnc_allclibhndl3", CharSet = CharSet.Ansi, ExactSpelling = true)]
    public static extern short AllcLibHndl3(
        [MarshalAs(UnmanagedType.LPStr)] string ipaddr,
        ushort port,
        int timeout,
        out ushort handle);

    [DllImport(Library, EntryPoint = "cnc_freelibhndl", ExactSpelling = true)]
    public static extern short FreeLibHndl(ushort handle);

    // ---- PMC ----

    /// <summary>PMC range read. <paramref name="addrType"/> is the ADR_* enum; <paramref name="dataType"/> is 0 byte / 1 word / 2 long.</summary>
    [DllImport(Library, EntryPoint = "pmc_rdpmcrng", ExactSpelling = true)]
    public static extern short PmcRdPmcRng(
        ushort handle,
        short addrType,
        short dataType,
        ushort startNumber,
        ushort endNumber,
        ushort length,
        ref IODBPMC buffer);

    [DllImport(Library, EntryPoint = "pmc_wrpmcrng", ExactSpelling = true)]
    public static extern short PmcWrPmcRng(
        ushort handle,
        ushort length,
        ref IODBPMC buffer);

    // ---- Parameters ----

    [DllImport(Library, EntryPoint = "cnc_rdparam", ExactSpelling = true)]
    public static extern short RdParam(
        ushort handle,
        ushort number,
        short axis,
        short length,
        ref IODBPSD buffer);

    [DllImport(Library, EntryPoint = "cnc_wrparam", ExactSpelling = true)]
    public static extern short WrParam(
        ushort handle,
        short length,
        ref IODBPSD buffer);

    // ---- Macro variables ----

    [DllImport(Library, EntryPoint = "cnc_rdmacro", ExactSpelling = true)]
    public static extern short RdMacro(
        ushort handle,
        short number,
        short length,
        ref ODBM buffer);

    [DllImport(Library, EntryPoint = "cnc_wrmacro", ExactSpelling = true)]
    public static extern short WrMacro(
        ushort handle,
        short number,
        short length,
        int macroValue,
        short decimalPointCount);

    // ---- Status ----

    [DllImport(Library, EntryPoint = "cnc_statinfo", ExactSpelling = true)]
    public static extern short StatInfo(ushort handle, ref ODBST buffer);

    // ---- Structs ----

    /// <summary>
    ///     IODBPMC — PMC range I/O buffer. 8-byte header + 40-byte union. We marshal the union
    ///     as a fixed byte buffer + interpret per <see cref="FocasDataType"/> on the managed side.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct IODBPMC
    {
        public short TypeA;
        public short TypeD;
        public ushort DatanoS;
        public ushort DatanoE;
        // 40-byte union: cdata[5] / idata[5] / ldata[5] / fdata[5] / dbdata[5] — dbdata is the widest.
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
        public byte[] Data;
    }

    /// <summary>
    ///     IODBPSD — CNC parameter I/O buffer. Axis-aware; for non-axis parameters pass axis=0.
    ///     Union payload is bytes / shorts / longs — we marshal 32 bytes as the widest slot.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct IODBPSD
    {
        public short Datano;
        public short Type; // axis index (0 for non-axis)
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public byte[] Data;
    }

    /// <summary>ODBM — macro variable read buffer. Value = <c>McrVal / 10^DecVal</c>.</summary>
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct ODBM
    {
        public short Datano;
        public short Dummy;
        public int McrVal;      // long in C; 32-bit signed
        public short DecVal;    // decimal-point count
    }

    /// <summary>ODBST — CNC status info. Machine state, alarm flags, automatic / edit mode.</summary>
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct ODBST
    {
        public short Dummy;
        public short TmMode;
        public short Aut;
        public short Run;
        public short Motion;
        public short Mstb;
        public short Emergency;
        public short Alarm;
        public short Edit;
    }
}

/// <summary>
///     PMC address-letter → FOCAS <c>ADR_*</c> numeric code. Per Fanuc FOCAS/2 spec the codes
///     are: G=0, F=1, Y=2, X=3, A=4, R=5, T=6, K=7, C=8, D=9, E=10. Exposed internally +
///     tested so the FwlibFocasClient translation is verifiable without the DLL loaded.
/// </summary>
internal static class FocasPmcAddrType
{
    public static short? FromLetter(string letter) => letter.ToUpperInvariant() switch
    {
        "G" => 0,
        "F" => 1,
        "Y" => 2,
        "X" => 3,
        "A" => 4,
        "R" => 5,
        "T" => 6,
        "K" => 7,
        "C" => 8,
        "D" => 9,
        "E" => 10,
        _ => null,
    };
}

/// <summary>PMC data-type numeric codes per FOCAS/2: 0 = byte, 1 = word, 2 = long, 4 = float, 5 = double.</summary>
internal static class FocasPmcDataType
{
    public const short Byte = 0;
    public const short Word = 1;
    public const short Long = 2;
    public const short Float = 4;
    public const short Double = 5;

    public static short FromFocasDataType(FocasDataType t) => t switch
    {
        FocasDataType.Bit or FocasDataType.Byte => Byte,
        FocasDataType.Int16 => Word,
        FocasDataType.Int32 => Long,
        FocasDataType.Float32 => Float,
        FocasDataType.Float64 => Double,
        _ => Byte,
    };
}
