using System;
using System.Runtime.InteropServices;
using System.Text;

namespace CloudPointRpc
{
    public static partial class CrpcTestApi
    {
        private const string DllName = "test_cloud_point";

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate IntPtr RpcStringCallback(IntPtr rpcString);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_test_init();

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_test_deinit();

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_test_add_method(RpcStringCallback cb, IntPtr name);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern int crpc_test_remove_method(IntPtr name);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_test_schedule_call(IntPtr name);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_test_change_duration(ulong durationMs);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern ulong crpc_test_duration();

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_test_auto_call(uint state);
    }

    public static partial class CrpcServerApi
    {
        private const string DllName = "libcloud_point_rpc";

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr crpc_str_get_data(IntPtr rpcString);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern ulong crpc_str_get_size(IntPtr rpcString);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern IntPtr crpc_str_create(byte[] data, ulong size);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_str_destroy(IntPtr rpcString);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        public static extern void crpc_init(byte[] configPath);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_deinit();

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void crpc_add_method(CrpcTestApi.RpcStringCallback cb, IntPtr name);
    }

    public sealed class RpcString : IDisposable
    {
        private IntPtr _handle;
        private bool _disposed;

        public IntPtr Handle => _handle;

        public RpcString(string data)
        {
            var bytes = Encoding.ASCII.GetBytes(data);
            _handle = CrpcServerApi.crpc_str_create(bytes, (ulong)bytes.Length);
        }

        internal RpcString(IntPtr handle)
        {
            _handle = handle;
        }

        public string Data
        {
            get
            {
                if (_disposed || _handle == IntPtr.Zero)
                    return string.Empty;

                var dataPtr = CrpcServerApi.crpc_str_get_data(_handle);
                var size = CrpcServerApi.crpc_str_get_size(_handle);

                if (dataPtr == IntPtr.Zero || size == 0)
                    return string.Empty;

                return Marshal.PtrToStringAnsi(dataPtr, (int)size) ?? string.Empty;
            }
        }

        public void Dispose()
        {
            if (!_disposed && _handle != IntPtr.Zero)
            {
                CrpcServerApi.crpc_str_destroy(_handle);
                _handle = IntPtr.Zero;
                _disposed = true;
            }
        }

        ~RpcString()
        {
            Dispose();
        }
    }

    public static class CrpcExtensions
    {
        public static string CrpcStrGetData(IntPtr rpcString)
        {
            var dataPtr = CrpcServerApi.crpc_str_get_data(rpcString);
            var size = CrpcServerApi.crpc_str_get_size(rpcString);
            if (dataPtr == IntPtr.Zero || size == 0)
                return string.Empty;
            return Marshal.PtrToStringAnsi(dataPtr, (int)size) ?? string.Empty;
        }

        public static ulong CrpcStrGetSize(IntPtr rpcString)
        {
            return CrpcServerApi.crpc_str_get_size(rpcString);
        }

        public static IntPtr CrpcStrCreate(string data)
        {
            var bytes = Encoding.UTF8.GetBytes(data);
            return CrpcServerApi.crpc_str_create(bytes, (ulong)bytes.Length);
        }

        public static void CrpcStrDestroy(IntPtr rpcString)
        {
            CrpcServerApi.crpc_str_destroy(rpcString);
        }

        public static void CrpcInit(string configPath)
        {
            var bytes = Encoding.UTF8.GetBytes(configPath + '\0');
            CrpcServerApi.crpc_init(bytes);
        }

        public static void CrpcDeinit()
        {
            CrpcServerApi.crpc_deinit();
        }

        public static void CrpcAddMethod(CrpcTestApi.RpcStringCallback cb, string name)
        {
            using var nameRpc = new RpcString(name);
            CrpcServerApi.crpc_add_method(cb, nameRpc.Handle);
        }
    }

    public static class CrpcTestExtensions
    {
        public static void CrpcTestAddMethod(CrpcTestApi.RpcStringCallback cb, string name)
        {
            using var nameRpc = new RpcString(name);
            CrpcTestApi.crpc_test_add_method(cb, nameRpc.Handle);
        }

        public static int CrpcTestRemoveMethod(string name)
        {
            using var nameRpc = new RpcString(name);
            return CrpcTestApi.crpc_test_remove_method(nameRpc.Handle);
        }

        public static void CrpcTestScheduleCall(string name)
        {
            using var nameRpc = new RpcString(name);
            CrpcTestApi.crpc_test_schedule_call(nameRpc.Handle);
        }

        public static void CrpcAddMethod(CrpcTestApi.RpcStringCallback cb, string name)
        {
            using var nameRpc = new RpcString(name);
            CrpcServerApi.crpc_add_method(cb, nameRpc.Handle);
        }

        public static void CrpcInit(string configPath)
        {
            var bytes = Encoding.UTF8.GetBytes(configPath + '\0');
            CrpcServerApi.crpc_init(bytes);
        }
    }
}
