// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Win32.Msi.Manual.Tests
{
    /// <summary>
    /// Simple test application to illustrate basic MSI API calls and wiring up an external UI handler to
    /// generate a progress bar.
    /// </summary>
    public class Program
    {
        int ProgressBarWidth;
        int ProgressTotal;
        int ProgressPhase;
        int ProgressCompleted;
        int ActionTop;
        int ProgressBarTop;
        bool ForwardProgress;
        bool ActionDataEnabled;
        string CurrentAction;
        bool ProgressDone;

        int ActionDataStep;
        double Progress;

        static void Main(string[] args)
        {
            Program p = new();
            p.Run(args);
        }

        void Run(string[] args)
        {
            try
            {
                Console.CursorVisible = false;
                ProgressBarWidth = Console.WindowWidth - 4;

                switch (args[0])
                {
                    case "install":
                        InstallMsi(args[1]);
                        break;
                    default:
                        Console.WriteLine($"Unknown command.");
                        break;
                }
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e.Message);
                Console.CursorVisible = true;
                Environment.Exit(e.HResult);
            }
            finally
            {
                Console.CursorVisible = true;
            }
        }

        void InstallMsi(string path)
        {
            // Configure event handler for install messages
            UserInterfaceHandler ux = new(InstallLogMode.ACTIONDATA | InstallLogMode.ACTIONSTART | InstallLogMode.PROGRESS);

            ux.ActionData += OnActionData;
            ux.ActionStart += OnActionStart;
            ux.Progress += OnProgress;

            ProgressPhase = 0;

            Console.WriteLine();
            ActionTop = Console.CursorTop;
            ProgressBarTop = ActionTop + 1;

            // Make sure we run quietly. Be careful, we won't be prompted to elevate.
            WindowsInstaller.SetInternalUI(InstallUILevel.None);

            uint error = WindowsInstaller.InstallProduct(path, "MSIFASTINSTALL=7 REBOOT=ReallySuppress");

            Console.CursorTop = ProgressBarTop + 1;
            Console.CursorLeft = 0;

            Console.CursorTop = ActionTop;
            ClearLine();
            Console.CursorTop = ProgressBarTop;
            ClearLine();

            if ((error != Error.SUCCESS) && (error != Error.SUCCESS_REBOOT_INITIATED) && (error != Error.SUCCESS_REBOOT_REQUIRED))
            {
                throw new WindowsInstallerException((int)error);
            }
            else
            {
                Console.WriteLine("Done!");
            }
        }

        void ClearLine()
        {
            int top = Console.CursorTop;
            Console.SetCursorPosition(0, top);
            Console.Write(new string(' ', Console.WindowWidth));
            Console.SetCursorPosition(0, top);
        }

        void OnActionData(object sender, ActionDataEventArgs e)
        {
            if (ActionDataEnabled)
            {
                ProgressCompleted += ForwardProgress ? ActionDataStep : -ActionDataStep;
                UpdateProgress();
            }

            e.Result = DialogResult.IDOK;
        }

        void OnActionStart(object send, ActionStartEventArgs e)
        {
            if (ActionDataEnabled)
            {
                ActionDataEnabled = false;
            }

            if (e.ActionName != CurrentAction)
            {
                CurrentAction = e.ActionName;
                Console.CursorTop = ActionTop;
                Console.CursorLeft = 0;
                ClearLine();
                Console.Write(e.ActionDescription);
            }

            e.Result = DialogResult.IDOK;
        }

        void OnProgress(object send, ProgressEventArgs e)
        {
            e.Result = DialogResult.IDOK;

            switch (e.ProgressType)
            {
                case ProgressType.Reset:
                    DrawProgressBar();
                    ProgressTotal = e.Fields[1];
                    // Field 3 contains the direction.
                    ForwardProgress = e.Fields[2] == 0;
                    // Field 2 contains the total expected number of ticks. If we're moving forward, reset to 0,
                    // otherwise reset to the total ticks since we're going in reverse, e.g. install is rolling back.
                    ProgressCompleted = ForwardProgress ? 0 : ProgressTotal;
                    ActionDataEnabled = false;
                    ProgressPhase++;
                    UpdateProgress();
                    break;

                case ProgressType.ActionInfo:
                    ActionDataEnabled = e.Fields[2] != 0;

                    if (ActionDataEnabled)
                    {
                        // Field 2 indicates the amount of ticks to progress for each ActionData message
                        ActionDataStep = e.Fields[1];
                    }

                    break;

                case ProgressType.ProgressReport:
                    if ((ProgressTotal == 0) || (ProgressPhase == 0))
                    {
                        return;
                    }

                    ProgressCompleted += ForwardProgress ? e.Fields[1] : -e.Fields[1];
                    UpdateProgress();
                    break;

                default:
                    // Cancel the install if we get a bogus progress sub-type message.
                    e.Result = DialogResult.IDCANCEL;
                    break;
            }
        }

        void DrawProgressBar()
        {
            Console.CursorLeft = 0;
            Console.CursorTop = ProgressBarTop;
            ClearLine();
            Console.CursorTop = ProgressBarTop;
            ConsoleColor fg = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.Write(new string('\u2591', ProgressBarWidth));
            Console.ForegroundColor = fg;
        }

        void UpdateProgress()
        {
            if (ProgressPhase == 0)
            {
                Progress = 0;
                DrawProgressBar();
            }
            else if (ProgressPhase == 1)
            {
                Progress = (double)Math.Min(ProgressCompleted, ProgressTotal) / ProgressTotal;

                int blocks = Convert.ToInt32(ProgressBarWidth * Progress);

                Console.CursorLeft = 0;
                Console.CursorTop = ProgressBarTop;
                ConsoleColor fg = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write(new string('\u2588', blocks));
                Console.ForegroundColor = fg;
            }
            else
            {
                Progress = 100;

                if (!ProgressDone)
                {
                    Console.SetCursorPosition(0, ProgressBarTop);
                    ConsoleColor fg = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine(new string('\u2588', ProgressBarWidth));
                    Console.ForegroundColor = fg;
                    ProgressDone = true;
                }
            }

            Thread.Sleep(15);
        }
    }
}
