using ChocolArm64.State;
using Ryujinx.HLE.Logging;

using static Ryujinx.HLE.HOS.ErrorCode;

namespace Ryujinx.HLE.HOS.Kernel
{
    partial class SvcHandler
    {
        private void SvcSetHeapSize(AThreadState ThreadState)
        {
            ulong Size = ThreadState.X1;

            if ((Size & 0xFFFFFFFE001FFFFF) != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Heap size 0x{Size:x16} is not aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            long Result = Process.MemoryManager.TrySetHeapSize((long)Size, out long Position);

            ThreadState.X0 = (ulong)Result;

            if (Result == 0)
            {
                ThreadState.X1 = (ulong)Position;
            }
            else
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }
        }

        private void SvcSetMemoryAttribute(AThreadState ThreadState)
        {
            long Position = (long)ThreadState.X0;
            long Size     = (long)ThreadState.X1;

            if (!PageAligned(Position))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            MemoryAttribute AttributeMask  = (MemoryAttribute)ThreadState.X2;
            MemoryAttribute AttributeValue = (MemoryAttribute)ThreadState.X3;

            MemoryAttribute Attributes = AttributeMask | AttributeValue;

            if (Attributes != AttributeMask ||
               (Attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, "Invalid memory attributes!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMaskValue);

                return;
            }

            long Result = Process.MemoryManager.SetMemoryAttribute(
                Position,
                Size,
                AttributeMask,
                AttributeValue);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }
            else
            {
                Memory.StopObservingRegion(Position, Size);
            }

            ThreadState.X0 = (ulong)Result;
        }

        private void SvcMapMemory(AThreadState ThreadState)
        {
            long Dst  = (long)ThreadState.X0;
            long Src  = (long)ThreadState.X1;
            long Size = (long)ThreadState.X2;

            if (!PageAligned(Src | Dst))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (!InsideAddrSpace(Src, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (!InsideNewMapRegion(Dst, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);

                return;
            }

            long Result = Process.MemoryManager.Map(Src, Dst, Size);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }

            ThreadState.X0 = (ulong)Result;
        }

        private void SvcUnmapMemory(AThreadState ThreadState)
        {
            long Dst  = (long)ThreadState.X0;
            long Src  = (long)ThreadState.X1;
            long Size = (long)ThreadState.X2;

            if (!PageAligned(Src | Dst))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses are not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            if ((ulong)(Src + Size) <= (ulong)Src || (ulong)(Dst + Size) <= (ulong)Dst)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, "Addresses outside of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (!InsideAddrSpace(Src, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Src address 0x{Src:x16} out of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (!InsideNewMapRegion(Dst, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Dst address 0x{Dst:x16} out of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidMemRange);

                return;
            }

            long Result = Process.MemoryManager.Unmap(Src, Dst, Size);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }

            ThreadState.X0 = (ulong)Result;
        }

        private void SvcQueryMemory(AThreadState ThreadState)
        {
            long InfoPtr  = (long)ThreadState.X0;
            long Position = (long)ThreadState.X2;

            KMemoryInfo BlkInfo = Process.MemoryManager.QueryMemory(Position);

            Memory.WriteInt64(InfoPtr + 0x00, BlkInfo.Position);
            Memory.WriteInt64(InfoPtr + 0x08, BlkInfo.Size);
            Memory.WriteInt32(InfoPtr + 0x10, (int)BlkInfo.State & 0xff);
            Memory.WriteInt32(InfoPtr + 0x14, (int)BlkInfo.Attribute);
            Memory.WriteInt32(InfoPtr + 0x18, (int)BlkInfo.Permission);
            Memory.WriteInt32(InfoPtr + 0x1c, BlkInfo.IpcRefCount);
            Memory.WriteInt32(InfoPtr + 0x20, BlkInfo.DeviceRefCount);
            Memory.WriteInt32(InfoPtr + 0x24, 0);

            ThreadState.X0 = 0;
            ThreadState.X1 = 0;
        }

        private void SvcMapSharedMemory(AThreadState ThreadState)
        {
            int  Handle   =  (int)ThreadState.X0;
            long Position = (long)ThreadState.X1;
            long Size     = (long)ThreadState.X2;

            if (!PageAligned(Position))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            if ((ulong)(Position + Size) <= (ulong)Position)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            MemoryPermission Permission = (MemoryPermission)ThreadState.X3;

            if ((Permission | MemoryPermission.Write) != MemoryPermission.ReadAndWrite)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);

                return;
            }

            KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);

            if (SharedMemory == null)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);

                return;
            }

            if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (SharedMemory.Size != Size)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} does not match shared memory size 0x{SharedMemory.Size:16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            long Result = Process.MemoryManager.MapSharedMemory(SharedMemory, Permission, Position);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }

            ThreadState.X0 = (ulong)Result;
        }

        private void SvcUnmapSharedMemory(AThreadState ThreadState)
        {
            int  Handle   =  (int)ThreadState.X0;
            long Position = (long)ThreadState.X1;
            long Size     = (long)ThreadState.X2;

            if (!PageAligned(Position))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            if ((ulong)(Position + Size) <= (ulong)Position)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            KSharedMemory SharedMemory = Process.HandleTable.GetData<KSharedMemory>(Handle);

            if (SharedMemory == null)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid shared memory handle 0x{Handle:x8}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);

                return;
            }

            if (!InsideAddrSpace(Position, Size) || InsideMapRegion(Position, Size) || InsideHeapRegion(Position, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} out of range!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            long Result = Process.MemoryManager.UnmapSharedMemory(Position, Size);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }

            ThreadState.X0 = (ulong)Result;
        }

        private void SvcCreateTransferMemory(AThreadState ThreadState)
        {
            long Position = (long)ThreadState.X1;
            long Size     = (long)ThreadState.X2;

            if (!PageAligned(Position))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if ((ulong)(Position + Size) <= (ulong)Position)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            MemoryPermission Permission = (MemoryPermission)ThreadState.X3;

            if (Permission > MemoryPermission.ReadAndWrite || Permission == MemoryPermission.Write)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid permission {Permission}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidPermission);

                return;
            }

            Process.MemoryManager.ReserveTransferMemory(Position, Size, Permission);

            KTransferMemory TransferMemory = new KTransferMemory(Position, Size);

            int Handle = Process.HandleTable.OpenHandle(TransferMemory);

            ThreadState.X0 = 0;
            ThreadState.X1 = (ulong)Handle;
        }

        private void SvcMapPhysicalMemory(AThreadState ThreadState)
        {
            long Position = (long)ThreadState.X0;
            long Size     = (long)ThreadState.X1;

            if (!PageAligned(Position))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            if ((ulong)(Position + Size) <= (ulong)Position)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (!InsideAddrSpace(Position, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            long Result = Process.MemoryManager.MapPhysicalMemory(Position, Size);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }

            ThreadState.X0 = (ulong)Result;
        }

        private void SvcUnmapPhysicalMemory(AThreadState ThreadState)
        {
            long Position = (long)ThreadState.X0;
            long Size     = (long)ThreadState.X1;

            if (!PageAligned(Position))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Address 0x{Position:x16} is not page aligned!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);

                return;
            }

            if (!PageAligned(Size) || Size == 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Size 0x{Size:x16} is not page aligned or is zero!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidSize);

                return;
            }

            if ((ulong)(Position + Size) <= (ulong)Position)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid region address 0x{Position:x16} / size 0x{Size:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            if (!InsideAddrSpace(Position, Size))
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address {Position:x16}!");

                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.NoAccessPerm);

                return;
            }

            long Result = Process.MemoryManager.UnmapPhysicalMemory(Position, Size);

            if (Result != 0)
            {
                Device.Log.PrintWarning(LogClass.KernelSvc, $"Operation failed with error 0x{Result:x}!");
            }

            ThreadState.X0 = (ulong)Result;
        }

        private static bool PageAligned(long Position)
        {
            return (Position & (KMemoryManager.PageSize - 1)) == 0;
        }

        private bool InsideAddrSpace(long Position, long Size)
        {
            ulong Start = (ulong)Position;
            ulong End   = (ulong)Size + Start;

            return Start >= (ulong)Process.MemoryManager.AddrSpaceStart &&
                   End   <  (ulong)Process.MemoryManager.AddrSpaceEnd;
        }

        private bool InsideMapRegion(long Position, long Size)
        {
            ulong Start = (ulong)Position;
            ulong End   = (ulong)Size + Start;

            return Start >= (ulong)Process.MemoryManager.MapRegionStart &&
                   End   <  (ulong)Process.MemoryManager.MapRegionEnd;
        }

        private bool InsideHeapRegion(long Position, long Size)
        {
            ulong Start = (ulong)Position;
            ulong End   = (ulong)Size + Start;

            return Start >= (ulong)Process.MemoryManager.HeapRegionStart &&
                   End   <  (ulong)Process.MemoryManager.HeapRegionEnd;
        }

        private bool InsideNewMapRegion(long Position, long Size)
        {
            ulong Start = (ulong)Position;
            ulong End   = (ulong)Size + Start;

            return Start >= (ulong)Process.MemoryManager.NewMapRegionStart &&
                   End   <  (ulong)Process.MemoryManager.NewMapRegionEnd;
        }
    }
}