using Ryujinx.Common.Memory;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Ns;
using Ryujinx.Horizon.Sdk.Sf.Cmif;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using System;
using System.Runtime.CompilerServices;

namespace Ryujinx.Horizon.Sdk.Arp
{
    class ArpApi : IDisposable
    {
        private const string ArpRName = "arp:r";

        private readonly HeapAllocator _allocator;
        private int _sessionHandle;

        public ArpApi(HeapAllocator allocator)
        {
            _allocator = allocator;
        }

        private void InitializeArpRService()
        {
            if (_sessionHandle == 0)
            {
                using var smApi = new SmApi();

                smApi.Initialize();
                smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(ArpRName)).AbortOnFailure();
            }
        }

        public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong applicationPid)
        {
            Span<byte> data = stackalloc byte[8];
            SpanWriter writer = new(data);

            writer.Write(applicationPid);

            InitializeArpRService();

            Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 3, sendPid: false, data);
            if (result.IsFailure)
            {
                applicationInstanceId = 0;

                return result;
            }

            SpanReader reader = new(response.Data);

            applicationInstanceId = reader.Read<ulong>();

            return Result.Success;
        }

        public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId)
        {
            applicationLaunchProperty = default;

            Span<byte> data = stackalloc byte[8];
            SpanWriter writer = new(data);

            writer.Write(applicationInstanceId);

            InitializeArpRService();

            Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 0, sendPid: false, data);
            if (result.IsFailure)
            {
                return result;
            }

            SpanReader reader = new(response.Data);

            applicationLaunchProperty = reader.Read<ApplicationLaunchProperty>();

            return Result.Success;
        }

        public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId)
        {
            applicationControlProperty = default;

            Span<byte> data = stackalloc byte[8];
            SpanWriter writer = new(data);

            writer.Write(applicationInstanceId);

            ulong bufferSize = (ulong)Unsafe.SizeOf<ApplicationControlProperty>();
            ulong bufferAddress = _allocator.Allocate(bufferSize);

            InitializeArpRService();

            Result result = ServiceUtil.SendRequest(
                out CmifResponse response,
                _sessionHandle,
                1,
                sendPid: false,
                data,
                stackalloc[] { HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize },
                stackalloc[] { new PointerAndSize(bufferAddress, bufferSize) });

            if (result.IsFailure)
            {
                return result;
            }

            applicationControlProperty = HorizonStatic.AddressSpace.Read<ApplicationControlProperty>(bufferAddress);

            _allocator.Free(bufferAddress, bufferSize);

            return Result.Success;
        }

        public void Dispose()
        {
            if (_sessionHandle != 0)
            {
                HorizonStatic.Syscall.CloseHandle(_sessionHandle);

                _sessionHandle = 0;
            }

            GC.SuppressFinalize(this);
        }
    }
}