using ChocolArm64.Memory;
using Ryujinx.OsHle.Handles;
using Ryujinx.OsHle.Objects;
using Ryujinx.OsHle.Services;
using System;
using System.Collections.Generic;
using System.IO;

namespace Ryujinx.OsHle.Ipc
{
    static class IpcHandler
    {
        private delegate long ServiceProcessRequest(ServiceCtx Context);

        private static Dictionary<(string, int), ServiceProcessRequest> ServiceCmds =
                   new Dictionary<(string, int), ServiceProcessRequest>()
        {
            { ( "acc:u0",      3), Service.AccU0ListOpenUsers                       },
            { ( "acc:u0",      5), Service.AccU0GetProfile                          },
            { ( "acc:u0",    100), Service.AccU0InitializeApplicationInfo           },
            { ( "acc:u0",    101), Service.AccU0GetBaasAccountManagerForApplication },
            { ( "apm",         0), Service.ApmOpenSession                           },
            { ( "apm:p",       0), Service.ApmOpenSession                           },
            { ( "appletOE",    0), Service.AppletOpenApplicationProxy               },
            { ( "audout:u",    0), Service.AudOutListAudioOuts                      },
            { ( "audout:u",    1), Service.AudOutOpenAudioOut                       },
            { ( "audren:u",    0), Service.AudRenOpenAudioRenderer                  },
            { ( "audren:u",    1), Service.AudRenGetAudioRendererWorkBufferSize     },
            { ( "friend:a",    0), Service.FriendCreateFriendService                },
            { ( "fsp-srv",     1), Service.FspSrvInitialize                         },
            { ( "fsp-srv",    18), Service.FspSrvMountSdCard                        },
            { ( "fsp-srv",    51), Service.FspSrvMountSaveData                      },
            { ( "fsp-srv",   200), Service.FspSrvOpenDataStorageByCurrentProcess    },
            { ( "fsp-srv",   203), Service.FspSrvOpenRomStorage                     },
            { ( "fsp-srv",  1005), Service.FspSrvGetGlobalAccessLogMode             },
            { ( "hid",         0), Service.HidCreateAppletResource                  },
            { ( "hid",        11), Service.HidActivateTouchScreen                   },
            { ( "hid",       100), Service.HidSetSupportedNpadStyleSet              },
            { ( "hid",       102), Service.HidSetSupportedNpadIdType                },
            { ( "hid",       103), Service.HidActivateNpad                          },
            { ( "hid",       120), Service.HidSetNpadJoyHoldType                    },
            { ( "lm",          0), Service.LmInitialize                             },
            { ( "nvdrv",       0), Service.NvDrvOpen                                },
            { ( "nvdrv",       1), Service.NvDrvIoctl                               },
            { ( "nvdrv",       2), Service.NvDrvClose                               },
            { ( "nvdrv",       3), Service.NvDrvInitialize                          },
            { ( "nvdrv",       4), Service.NvDrvQueryEvent                          },
            { ( "nvdrv",       8), Service.NvDrvSetClientPid                        },
            { ( "nvdrv:a",     0), Service.NvDrvOpen                                },
            { ( "nvdrv:a",     1), Service.NvDrvIoctl                               },
            { ( "nvdrv:a",     2), Service.NvDrvClose                               },
            { ( "nvdrv:a",     3), Service.NvDrvInitialize                          },
            { ( "nvdrv:a",     4), Service.NvDrvQueryEvent                          },
            { ( "nvdrv:a",     8), Service.NvDrvSetClientPid                        },
            { ( "pctl:a",      0), Service.PctlCreateService                        },
            { ( "pl:u",        1), Service.PlGetLoadState                           },
            { ( "pl:u",        2), Service.PlGetFontSize                            },
            { ( "pl:u",        3), Service.PlGetSharedMemoryAddressOffset           },
            { ( "pl:u",        4), Service.PlGetSharedMemoryNativeHandle            },
            { ( "set",         1), Service.SetGetAvailableLanguageCodes             },
            { ( "sm:",         0), Service.SmInitialize                             },
            { ( "sm:",         1), Service.SmGetService                             },
            { ( "time:u",      0), Service.TimeGetStandardUserSystemClock           },
            { ( "time:u",      1), Service.TimeGetStandardNetworkSystemClock        },
            { ( "time:u",      2), Service.TimeGetStandardSteadyClock               },
            { ( "time:u",      3), Service.TimeGetTimeZoneService                   },
            { ( "time:s",      0), Service.TimeGetStandardUserSystemClock           },
            { ( "time:s",      1), Service.TimeGetStandardNetworkSystemClock        },
            { ( "time:s",      2), Service.TimeGetStandardSteadyClock               },
            { ( "time:s",      3), Service.TimeGetTimeZoneService                   },
            { ( "vi:m",        2), Service.ViGetDisplayService                      },
        };

        private static Dictionary<(Type, int), ServiceProcessRequest> ObjectCmds =
                   new Dictionary<(Type, int), ServiceProcessRequest>()
        {
            //IManagerForApplication
            { (typeof(AccIManagerForApplication), 0), AccIManagerForApplication.CheckAvailability },
            { (typeof(AccIManagerForApplication), 1), AccIManagerForApplication.GetAccountId      },

            //IProfile
            { (typeof(AccIProfile), 1), AccIProfile.GetBase },

            //IApplicationFunctions
            { (typeof(AmIApplicationFunctions),  1), AmIApplicationFunctions.PopLaunchParameter },
            { (typeof(AmIApplicationFunctions), 20), AmIApplicationFunctions.EnsureSaveData     },
            { (typeof(AmIApplicationFunctions), 21), AmIApplicationFunctions.GetDesiredLanguage },
            { (typeof(AmIApplicationFunctions), 40), AmIApplicationFunctions.NotifyRunning      },

            //IApplicationProxy
            { (typeof(AmIApplicationProxy),    0), AmIApplicationProxy.GetCommonStateGetter    },
            { (typeof(AmIApplicationProxy),    1), AmIApplicationProxy.GetSelfController       },
            { (typeof(AmIApplicationProxy),    2), AmIApplicationProxy.GetWindowController     },
            { (typeof(AmIApplicationProxy),    3), AmIApplicationProxy.GetAudioController      },
            { (typeof(AmIApplicationProxy),    4), AmIApplicationProxy.GetDisplayController    },
            { (typeof(AmIApplicationProxy),   11), AmIApplicationProxy.GetLibraryAppletCreator },
            { (typeof(AmIApplicationProxy),   20), AmIApplicationProxy.GetApplicationFunctions },
            { (typeof(AmIApplicationProxy), 1000), AmIApplicationProxy.GetDebugFunctions       },

            //ICommonStateGetter
            { (typeof(AmICommonStateGetter), 0), AmICommonStateGetter.GetEventHandle       },
            { (typeof(AmICommonStateGetter), 1), AmICommonStateGetter.ReceiveMessage       },
            { (typeof(AmICommonStateGetter), 5), AmICommonStateGetter.GetOperationMode     },
            { (typeof(AmICommonStateGetter), 6), AmICommonStateGetter.GetPerformanceMode   },
            { (typeof(AmICommonStateGetter), 9), AmICommonStateGetter.GetCurrentFocusState },

            //ISelfController
            { (typeof(AmISelfController), 11), AmISelfController.SetOperationModeChangedNotification   },
            { (typeof(AmISelfController), 12), AmISelfController.SetPerformanceModeChangedNotification },
            { (typeof(AmISelfController), 13), AmISelfController.SetFocusHandlingMode                  },
            { (typeof(AmISelfController), 16), AmISelfController.SetOutOfFocusSuspendingEnabled        },

            //IStorage
            { (typeof(AmIStorage), 0), AmIStorage.Open },

            //IStorageAccessor
            { (typeof(AmIStorageAccessor),  0), AmIStorageAccessor.GetSize },
            { (typeof(AmIStorageAccessor), 11), AmIStorageAccessor.Read    },

            //IWindowController
            { (typeof(AmIWindowController),  1), AmIWindowController.GetAppletResourceUserId },
            { (typeof(AmIWindowController), 10), AmIWindowController.AcquireForegroundRights },

            //ISession
            { (typeof(ApmISession), 0), ApmISession.SetPerformanceConfiguration },

            //IAudioRenderer
            { (typeof(AudIAudioRenderer), 4), AudIAudioRenderer.RequestUpdateAudioRenderer },
            { (typeof(AudIAudioRenderer), 5), AudIAudioRenderer.StartAudioRenderer         },
            { (typeof(AudIAudioRenderer), 7), AudIAudioRenderer.QuerySystemEvent           },

            //IFile
            { (typeof(FspSrvIFile), 0), FspSrvIFile.Read  },
            { (typeof(FspSrvIFile), 1), FspSrvIFile.Write },

            //IFileSystem
            { (typeof(FspSrvIFileSystem),  7), FspSrvIFileSystem.GetEntryType },
            { (typeof(FspSrvIFileSystem),  8), FspSrvIFileSystem.OpenFile     },
            { (typeof(FspSrvIFileSystem), 10), FspSrvIFileSystem.Commit       },

            //IStorage
            { (typeof(FspSrvIStorage), 0), FspSrvIStorage.Read },

            //IAppletResource
            { (typeof(HidIAppletResource), 0), HidIAppletResource.GetSharedMemoryHandle },

            //ISystemClock
            { (typeof(TimeISystemClock), 0), TimeISystemClock.GetCurrentTime },

            //IApplicationDisplayService
            { (typeof(ViIApplicationDisplayService),  100), ViIApplicationDisplayService.GetRelayService                      },
            { (typeof(ViIApplicationDisplayService),  101), ViIApplicationDisplayService.GetSystemDisplayService              },
            { (typeof(ViIApplicationDisplayService),  102), ViIApplicationDisplayService.GetManagerDisplayService             },
            { (typeof(ViIApplicationDisplayService),  103), ViIApplicationDisplayService.GetIndirectDisplayTransactionService },
            { (typeof(ViIApplicationDisplayService), 1010), ViIApplicationDisplayService.OpenDisplay                          },
            { (typeof(ViIApplicationDisplayService), 2020), ViIApplicationDisplayService.OpenLayer                            },
            { (typeof(ViIApplicationDisplayService), 2030), ViIApplicationDisplayService.CreateStrayLayer                     },
            { (typeof(ViIApplicationDisplayService), 2101), ViIApplicationDisplayService.SetLayerScalingMode                  },
            { (typeof(ViIApplicationDisplayService), 5202), ViIApplicationDisplayService.GetDisplayVSyncEvent                 },

            //IHOSBinderDriver
            { (typeof(ViIHOSBinderDriver), 0), ViIHOSBinderDriver.TransactParcel  },
            { (typeof(ViIHOSBinderDriver), 1), ViIHOSBinderDriver.AdjustRefcount  },
            { (typeof(ViIHOSBinderDriver), 2), ViIHOSBinderDriver.GetNativeHandle },

            //IManagerDisplayService
            { (typeof(ViIManagerDisplayService), 2010), ViIManagerDisplayService.CreateManagedLayer },
            { (typeof(ViIManagerDisplayService), 6000), ViIManagerDisplayService.AddToLayerStack    },

            //ISystemDisplayService
            { (typeof(ViISystemDisplayService), 2205), ViISystemDisplayService.SetLayerZ },
        };

        private const long SfciMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24;
        private const long SfcoMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24;

        public static void ProcessRequest(
            Switch     Ns,
            AMemory    Memory,
            HSession   Session,
            IpcMessage Request,
            long       CmdPtr,
            int        HndId)
        {
            IpcMessage Response = new IpcMessage(Request.IsDomain);

            using (MemoryStream Raw = new MemoryStream(Request.RawData))
            {
                BinaryReader ReqReader = new BinaryReader(Raw);

                if (Request.Type == IpcMessageType.Request)
                {
                    string ServiceName = Session.ServiceName;

                    ServiceProcessRequest ProcReq = null;

                    bool IgnoreNullPR = false;

                    string DbgServiceName = string.Empty;

                    if (Session is HDomain Dom)
                    {
                        if (Request.DomCmd == IpcDomCmd.SendMsg)
                        {
                            long Magic =      ReqReader.ReadInt64();
                            int  CmdId = (int)ReqReader.ReadInt64();

                            object Obj = Dom.GetObject(Request.DomObjId);

                            if (Obj is HDomain)
                            {
                                DbgServiceName = $"{ServiceName} {CmdId}";

                                ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
                            }
                            else if (Obj != null)
                            {
                                DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {CmdId}";

                                ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq);
                            }
                        }
                        else if (Request.DomCmd == IpcDomCmd.DeleteObj)
                        {
                            Dom.DeleteObject(Request.DomObjId);

                            Response = FillResponse(Response, 0);

                            IgnoreNullPR = true;
                        }
                    }
                    else
                    {
                        long Magic =      ReqReader.ReadInt64();
                        int  CmdId = (int)ReqReader.ReadInt64();

                        if (Session is HSessionObj)
                        {
                            object Obj = ((HSessionObj)Session).Obj;

                            DbgServiceName = $"{ServiceName} {Obj.GetType().Name} {CmdId}";

                            ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq);
                        }
                        else
                        {
                            DbgServiceName = $"{ServiceName} {CmdId}";

                            ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq);
                        }
                    }

                    if (ProcReq != null)
                    {
                        using (MemoryStream ResMS = new MemoryStream())
                        {
                            BinaryWriter ResWriter = new BinaryWriter(ResMS);

                            ServiceCtx Context = new ServiceCtx(
                                Ns,
                                Memory,
                                Session,
                                Request,
                                Response,
                                ReqReader,
                                ResWriter);

                            long Result = ProcReq(Context);

                            Response = FillResponse(Response, Result, ResMS.ToArray());
                        }
                    }
                    else if (!IgnoreNullPR)
                    {   
                        throw new NotImplementedException(DbgServiceName);
                    }
                }
                else if (Request.Type == IpcMessageType.Control)
                {
                    long Magic = ReqReader.ReadInt64();
                    long CmdId = ReqReader.ReadInt64();

                    switch (CmdId)
                    {
                        case 0: Request = IpcConvertSessionToDomain(Ns, Session, Response, HndId); break;
                        case 3: Request = IpcQueryBufferPointerSize(Response);                     break;
                        case 4: Request = IpcDuplicateSessionEx(Ns, Session, Response, ReqReader); break;

                        default: throw new NotImplementedException(CmdId.ToString());
                    }
                }
                else if (Request.Type == IpcMessageType.Unknown2)
                {
                    //TODO
                }
                else
                {
                    throw new NotImplementedException(Request.Type.ToString());
                }

                AMemoryHelper.WriteBytes(Memory, CmdPtr, Response.GetBytes(CmdPtr));
            }
        }

        private static IpcMessage IpcConvertSessionToDomain(
            Switch     Ns,
            HSession   Session,
            IpcMessage Response,
            int        HndId)
        {
            HDomain Dom = new HDomain(Session);

            Ns.Os.Handles.ReplaceData(HndId, Dom);

            return FillResponse(Response, 0, Dom.GenertateObjectId(Dom));
        }

        private static IpcMessage IpcDuplicateSessionEx(
            Switch       Ns,
            HSession     Session,
            IpcMessage   Response,
            BinaryReader ReqReader)
        {
            int Unknown = ReqReader.ReadInt32();

            int Handle = Ns.Os.Handles.GenerateId(Session);

            Response.HandleDesc = IpcHandleDesc.MakeMove(Handle);

            return FillResponse(Response, 0);
        }

        private static IpcMessage IpcQueryBufferPointerSize(IpcMessage Response)
        {
            return FillResponse(Response, 0, 0x500);
        }

        private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values)
        {
            using (MemoryStream MS = new MemoryStream())
            {
                BinaryWriter Writer = new BinaryWriter(MS);

                foreach (int Value in Values)
                {
                    Writer.Write(Value);
                }

                return FillResponse(Response, Result, MS.ToArray());
            }
        }

        private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null)
        {
            Response.Type = IpcMessageType.Response;

            using (MemoryStream MS = new MemoryStream())
            {
                BinaryWriter Writer = new BinaryWriter(MS);

                Writer.Write(SfcoMagic);
                Writer.Write(Result);

                if (Data != null)
                {
                    Writer.Write(Data);
                }

                Response.RawData = MS.ToArray();
            }

            return Response;
        }
    }
}