//
// Copyright (c) 2022-present, Trail of Bits, Inc.
// All rights reserved.
//
// This source code is licensed in accordance with the terms specified in
// the LICENSE file found in the root directory of this source tree.
//
using BrightIdeasSoftware;
using NtApiDotNet.Ndr;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using NtApiDotNet;
using System.Diagnostics;
using System.Linq;

namespace RpcInvestigator
{
    using static TraceLogger;

    public class RpcLibraryProcedureList : TabPage
    {
        public FastObjectListView m_Listview;
        private RpcLibrary m_Library;

        public RpcLibraryProcedureList(RpcLibrary Library)
        {
            m_Library = Library;
            m_Listview = new FastObjectListView();
            Random random = new Random();
            string rand = random.Next().ToString();
            m_Listview.OwnerDraw = true;
            m_Listview.BorderStyle = BorderStyle.FixedSingle;
            m_Listview.CellEditUseWholeCell = false;
            m_Listview.Cursor = Cursors.Default;
            m_Listview.Dock = DockStyle.Fill;
            m_Listview.FullRowSelect = true;
            m_Listview.GridLines = true;
            m_Listview.HideSelection = false;
            m_Listview.Location = new Point(0, 0);
            m_Listview.Margin = new Padding(5);
            m_Listview.Name = "RpcLibraryProceduresListview" + rand;
            m_Listview.UseAlternatingBackColors = true;
            m_Listview.AlternateRowBackColor = Color.LightBlue;
            m_Listview.UseCompatibleStateImageBehavior = false;
            m_Listview.View = View.Details;
            m_Listview.VirtualMode = true;
            m_Listview.ShowGroups = false;
            m_Listview.UseFiltering = true;
            m_Listview.Alignment = ListViewAlignment.Left;
            Generator.GenerateColumns(m_Listview, typeof(RpcLibraryProcedure), true);
            foreach (var column in m_Listview.AllColumns)
            {
                column.MaximumWidth = -1;
            }
            m_Listview.CellRightClick += RightClickHandler;
            Controls.Add(m_Listview);
        }

        public async Task<bool> Build(
            RpcLibraryFilter Filter,
            ToolStripProgressBar ProgressBar,
            ToolStripStatusLabel ProgressLabel
            )
        {
            Text = "Procedures";
            Name = "Procedures";
            ImageIndex = 3;
            try
            {
                using (NtToken token = NtProcess.Current.OpenToken())
                {
                    var searchResults = new List<RpcLibraryProcedure>();
                    ProgressBar.Step = 1;
                    ProgressBar.Value = 0;
                    ProgressBar.Visible = true;
                    await Task.Run(() =>
                    {
                        var results = m_Library.Find(Filter);
                        foreach (var server in results)
                        {
                            foreach (var procedure in server.Procedures)
                            {
                                var formatter = DefaultNdrFormatter.Create(DefaultNdrFormatterFlags.RemoveComments);
                                string friendly = formatter.FormatProcedure(procedure);
                                searchResults.Add(new RpcLibraryProcedure()
                                {
                                    FilePath = server.FilePath,
                                    InterfaceId = server.InterfaceId,
                                    InterfaceVersion = server.InterfaceVersion,
                                    Name = friendly,
                                });
                            };
                        }
                    });

                    if (searchResults.Count > 0)
                    {
                        m_Listview.SetObjects(searchResults);
                        m_Listview.AutoResizeColumns();
                        m_Listview.RebuildColumns();
                    }
                    ProgressBar.Value = 0;
                    ProgressBar.Visible = false;
                }
            }
            catch (Exception ex)
            {
                Trace(TraceLoggerType.RpcLibraryProcedureList,
                    TraceEventType.Error,
                    "Unable to retrieve RPC library procedure list: " + ex.Message);
            }
            return true;
        }

        public int GetCount()
        {
            if (m_Listview.Objects == null)
            {
                return 0;
            }
            return m_Listview.Objects.Cast<RpcLibraryProcedure>().Count();
        }

        private
        void
        RightClickHandler(
            object Obj,
            CellRightClickEventArgs Args
            )
        {
            TabPages.ContextMenu.BuildRightClickMenu(Args, new List<ToolStripMenuItem>{
                new ToolStripMenuItem("New Client", null, ContextMenuNewClient),
                new ToolStripMenuItem("Reset", null, ContextMenuResetSearch)
            });
            TabPages.ContextMenu.AddSearchElements(Args);
        }

        private void ContextMenuResetSearch(object Sender, EventArgs Args)
        {
            m_Listview.ModelFilter = null;
        }

        private void ContextMenuNewClient(object Sender, EventArgs Args)
        {
            object tag = ((ToolStripMenuItem)Sender).Tag;
            if (tag == null)
            {
                return;
            }

            var args = (CellRightClickEventArgs)tag;

            //
            // We need to find the corresponding RpcServer object in the library.
            //
            var procedure = args.Model as RpcLibraryProcedure;
            var id = procedure.InterfaceId;
            var version = procedure.InterfaceVersion;
            var server = m_Library.Get(id, version);
            if (server == null)
            {
                MessageBox.Show("Unable to locate RPC server in library with UUID " + id +
                    " and version " + version);
                return;
            }
            var endpoint = RpcAlpcServerList.FindEndpoint(id, version);
            if (endpoint == null)
            {
                MessageBox.Show("Unable to locate an endpoint for RPC server UUID " +
                    id + " and version " + version);
                return;
            }
            var clientWindow = new Client(server, endpoint);
            clientWindow.ShowDialog();
        }
    }

    public class RpcLibraryProcedure
    {
        public string FilePath { get; set; }
        public Guid InterfaceId { get; set; }
        public Version InterfaceVersion { get; set; }
        public string Name { get; set; }

    }
}
