// PDF-Decrypt - Windows installer.
//
// Copies the app to %LOCALAPPDATA%\PDF-Decrypt\ and places a shortcut on
// the Desktop. No administrator password required: all writes go to
// per-user folders.
//
// Patient flow:
//   1. Unzip the PDF-Decrypt folder anywhere.
//   2. Double-click Install.exe inside it.
//   3. Click Install (or Update if already installed).
//   4. The PDF-Decrypt icon appears on the Desktop.
//   5. Drag encrypted PDF invoices onto the icon to remove the password.
//
// The UI follows the Windows display language (German or English), matching
// the launcher and the main app.
//
// Build (build.bat passes /codepage:65001 so the German strings compile as UTF-8):
//   csc /nologo /codepage:65001 /target:winexe /out:Install.exe /win32icon:icon.ico
//       /reference:System.Windows.Forms.dll /reference:System.Drawing.dll
//       Installer.cs

using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace PdfDecrypt
{
    // ─────────────────────────────────────────────────────────────────────────
    // Minimal COM interop for creating a .lnk shortcut natively.
    // Only the members we call are exercised, but the full vtable must be
    // declared in order so the runtime binds the right slots.
    // ─────────────────────────────────────────────────────────────────────────
    [ComImport, Guid("00021401-0000-0000-C000-000000000046")]
    class ShellLink { }

    [ComImport,
     InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
     Guid("000214F9-0000-0000-C000-000000000046")]
    interface IShellLinkW
    {
        void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile,
                     int cchMaxPath, IntPtr pfd, uint fFlags);
        void GetIDList(out IntPtr ppidl);
        void SetIDList(IntPtr pidl);
        void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName,
                            int cchMaxName);
        void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
        void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir,
                                 int cchMaxPath);
        void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
        void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs,
                          int cchMaxPath);
        void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
        void GetHotkey(out short pwHotkey);
        void SetHotkey(short wHotkey);
        void GetShowCmd(out int piShowCmd);
        void SetShowCmd(int iShowCmd);
        void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
                             int cchIconPath, out int piIcon);
        void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
        void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
        void Resolve(IntPtr hwnd, uint fFlags);
        void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
    }

    [ComImport,
     InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
     Guid("0000010B-0000-0000-C000-000000000046")]
    interface IPersistFile
    {
        void GetClassID(out Guid pClassID);
        [PreserveSig] int IsDirty();
        void Load([MarshalAs(UnmanagedType.LPWStr)] string pszFileName, int dwMode);
        void Save([MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
                  [MarshalAs(UnmanagedType.Bool)] bool fRemember);
        void SaveCompleted([MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
        void GetCurFile([MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Branded setup form
    // ─────────────────────────────────────────────────────────────────────────
    sealed class SetupForm : Form
    {
        const string APP_VERSION = "1.2.0";

        // Brand colors — identical to the Tkinter UI
        static readonly Color TEAL  = Color.FromArgb(0x00, 0x80, 0x80);
        static readonly Color GOLD  = Color.FromArgb(0xF2, 0xB7, 0x05);
        static readonly Color BG    = Color.FromArgb(0xF7, 0xF7, 0xF5);
        static readonly Color LINE  = Color.FromArgb(0xDF, 0xE4, 0xE6);
        static readonly Color INK   = Color.FromArgb(0x1F, 0x29, 0x37);
        static readonly Color MUTED = Color.FromArgb(0x9A, 0xA5, 0xB1);

        // Layout constants (BODY_H is only a minimum; the body grows to fit text)
        const int W        = 480;
        const int HEADER_H = 56;
        const int RULE_H   = 3;
        const int BODY_H   = 120;
        const int SEP_H    = 1;
        const int FOOTER_H = 64;

        readonly string sourceDir;
        readonly string targetDir;
        readonly string targetExe;
        readonly string desktopLink;
        readonly bool   alreadyInstalled;

        Label  lblBody;
        Button btnAction;
        Button btnCancel;

        // ── Localization ─────────────────────────────────────────────────────
        static bool De
        {
            get
            {
                return CultureInfo.CurrentUICulture.TwoLetterISOLanguageName
                    .Equals("de", StringComparison.OrdinalIgnoreCase);
            }
        }

        static string Pick(string en, string de) { return De ? de : en; }

        internal SetupForm()
        {
            sourceDir   = AppDir();
            targetDir   = Path.Combine(
                            Environment.GetFolderPath(
                                Environment.SpecialFolder.LocalApplicationData),
                            "PDF-Decrypt");
            targetExe   = Path.Combine(targetDir, "PDF-Decrypt.exe");
            desktopLink = Path.Combine(
                            Environment.GetFolderPath(
                                Environment.SpecialFolder.Desktop),
                            "PDF-Decrypt.lnk");
            alreadyInstalled =
                Directory.Exists(targetDir) && File.Exists(targetExe);

            BuildUI();
        }

        void BuildUI()
        {
            Text            = "PDF-Decrypt";
            FormBorderStyle = FormBorderStyle.FixedDialog;
            MaximizeBox     = false;
            MinimizeBox     = false;
            StartPosition   = FormStartPosition.CenterScreen;
            BackColor       = BG;
            Font            = new Font("Segoe UI", 9.5f);

            // App icon
            string icoPath = Path.Combine(sourceDir, "icon.ico");
            if (File.Exists(icoPath))
                try { Icon = new Icon(icoPath); } catch { }

            // Body label: AutoSize so it grows to exactly fit its wrapped text
            // and can never clip a line. MaximumSize.Width sets the wrap width;
            // the height is free. The window is then sized from the label's own
            // preferred height, so it is always correct for either language and
            // any display-scaling setting.
            lblBody = new Label
            {
                Left = 24, Top = 18,
                AutoSize    = true,
                MaximumSize = new Size(W - 48, 0),
                Text        = BodyText(),
                ForeColor   = INK,
                Font        = new Font("Segoe UI", 9.5f),
            };
            int textH = lblBody.GetPreferredSize(new Size(W - 48, 0)).Height;
            int bodyH = Math.Max(BODY_H, textH + 36);

            ClientSize = new Size(W, HEADER_H + RULE_H + bodyH + SEP_H + FOOTER_H);

            // ── teal header ──────────────────────────────────────────────────
            var header = new Panel
            {
                Left = 0, Top = 0, Width = W, Height = HEADER_H,
                BackColor = TEAL
            };
            header.Controls.Add(new Label
            {
                Text      = "PDF-Decrypt",
                ForeColor = Color.White,
                Font      = new Font("Segoe UI", 13f, FontStyle.Bold),
                AutoSize  = true,
                Left = 24, Top = 15,
            });
            Controls.Add(header);

            // ── gold rule ────────────────────────────────────────────────────
            Controls.Add(new Panel
            {
                Left = 0, Top = HEADER_H, Width = W, Height = RULE_H,
                BackColor = GOLD
            });

            // ── white body ───────────────────────────────────────────────────
            var body = new Panel
            {
                Left = 0, Top = HEADER_H + RULE_H,
                Width = W, Height = bodyH,
                BackColor = Color.White
            };
            body.Controls.Add(lblBody);
            Controls.Add(body);

            // ── separator ────────────────────────────────────────────────────
            Controls.Add(new Panel
            {
                Left = 0, Top = HEADER_H + RULE_H + bodyH,
                Width = W, Height = SEP_H,
                BackColor = LINE
            });

            // ── footer with buttons ──────────────────────────────────────────
            var footer = new Panel
            {
                Left = 0, Top = HEADER_H + RULE_H + bodyH + SEP_H,
                Width = W, Height = FOOTER_H,
                BackColor = BG
            };

            int btnW   = 100;
            int btnTop = (FOOTER_H - 36) / 2;

            // Muted version label, bottom-left.
            footer.Controls.Add(new Label
            {
                Text      = "v" + APP_VERSION,
                ForeColor = MUTED,
                AutoSize  = true,
                Left = 24, Top = btnTop + 10,
                Font = new Font("Segoe UI", 8.5f),
            });

            btnCancel = new Button
            {
                Text      = Pick("Cancel", "Abbrechen"),
                Left      = W - 24 - btnW - 10 - btnW,
                Top       = btnTop,
                Width     = btnW,
                Height    = 36,
                FlatStyle = FlatStyle.Flat,
                BackColor = Color.White,
                ForeColor = INK,
                Font      = new Font("Segoe UI", 9.5f),
            };
            btnCancel.FlatAppearance.BorderColor = LINE;
            btnCancel.Click += (s, e) => Close();
            footer.Controls.Add(btnCancel);

            btnAction = new Button
            {
                Text      = alreadyInstalled
                                ? Pick("Update",  "Aktualisieren")
                                : Pick("Install", "Installieren"),
                Left      = W - 24 - btnW,
                Top       = btnTop,
                Width     = btnW,
                Height    = 36,
                FlatStyle = FlatStyle.Flat,
                BackColor = TEAL,
                ForeColor = Color.White,
                Font      = new Font("Segoe UI", 9.5f),
            };
            btnAction.FlatAppearance.BorderSize = 0;
            btnAction.Click += DoInstall;
            footer.Controls.Add(btnAction);

            Controls.Add(footer);
        }

        string BodyText()
        {
            if (alreadyInstalled)
                return Pick(
                    "PDF-Decrypt is already installed.\r\n\r\n" +
                    "Click Update to refresh the installation and restore the " +
                    "Desktop shortcut.",

                    "PDF-Decrypt ist bereits installiert.\r\n\r\n" +
                    "Klicken Sie auf Aktualisieren, um die Installation zu " +
                    "erneuern und die Desktop-Verknüpfung wiederherzustellen.");

            return Pick(
                "PDF-Decrypt will be installed for your Windows account only.\r\n" +
                "No administrator password is required.\r\n\r\n" +
                "A shortcut will be placed on your Desktop. Drag your encrypted " +
                "PDF invoices onto it and enter the password — a decrypted copy " +
                "will appear next to each original.",

                "PDF-Decrypt wird nur für Ihr Windows-Konto installiert.\r\n" +
                "Es ist kein Administrator-Passwort erforderlich.\r\n\r\n" +
                "Auf Ihrem Desktop wird eine Verknüpfung erstellt. Ziehen Sie " +
                "Ihre verschlüsselten PDF-Rechnungen darauf und geben Sie das " +
                "Passwort ein — eine entschlüsselte Kopie erscheint neben dem " +
                "jeweiligen Original.");
        }

        // ─────────────────────────────────────────────────────────────────────
        // Install action
        // ─────────────────────────────────────────────────────────────────────
        void DoInstall(object sender, EventArgs e)
        {
            btnAction.Enabled = false;
            btnCancel.Enabled = false;
            lblBody.Text = Pick("Installing, please wait…",
                                "Installation läuft, bitte warten…");
            Application.DoEvents();

            try
            {
                // If already running from the target location, skip copying
                // and just refresh the shortcut.
                if (!string.Equals(sourceDir, targetDir,
                                   StringComparison.OrdinalIgnoreCase))
                {
                    CopyDir(sourceDir, targetDir,
                            "Install.exe", "Installer.cs", "PDFDecrypt.cs",
                            "build.bat", "README.md", "CHANGELOG.md");
                }
                CreateShortcut(desktopLink, targetExe, targetDir);

                MessageBox.Show(
                    Pick(
                        "PDF-Decrypt is installed.\r\n\r\n" +
                        "The icon is now on your Desktop.\r\n\r\n" +
                        "To remove the password from a PDF invoice: drag it onto " +
                        "the PDF-Decrypt icon on your Desktop and enter the password.",

                        "PDF-Decrypt ist installiert.\r\n\r\n" +
                        "Das Symbol befindet sich jetzt auf Ihrem Desktop.\r\n\r\n" +
                        "So entfernen Sie das Passwort von einer PDF-Rechnung: " +
                        "Ziehen Sie sie auf das PDF-Decrypt-Symbol auf Ihrem " +
                        "Desktop und geben Sie das Passwort ein."),
                    "PDF-Decrypt",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
                Close();
            }
            catch (Exception ex)
            {
                lblBody.Text      = BodyText();
                btnAction.Enabled = true;
                btnCancel.Enabled = true;
                MessageBox.Show(
                    Pick("Installation failed:\r\n\r\n",
                         "Installation fehlgeschlagen:\r\n\r\n") + ex.Message,
                    "PDF-Decrypt",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
        }

        // ─────────────────────────────────────────────────────────────────────
        // Helpers
        // ─────────────────────────────────────────────────────────────────────

        /// <summary>Recursively copy src to dst, skipping named files at the top level.</summary>
        static void CopyDir(string src, string dst, params string[] skipFiles)
        {
            Directory.CreateDirectory(dst);
            foreach (string file in Directory.GetFiles(src))
            {
                string name = Path.GetFileName(file);
                bool skip = false;
                foreach (string s in skipFiles)
                    if (string.Equals(name, s, StringComparison.OrdinalIgnoreCase))
                    { skip = true; break; }
                if (!skip)
                    File.Copy(file, Path.Combine(dst, name), overwrite: true);
            }
            foreach (string sub in Directory.GetDirectories(src))
                CopyDir(sub, Path.Combine(dst, Path.GetFileName(sub)));
        }

        /// <summary>Create a .lnk Desktop shortcut natively via IShellLink
        /// (no PowerShell, no temp script, nothing spawned).</summary>
        static void CreateShortcut(string lnkPath, string targetPath, string workDir)
        {
            var link = (IShellLinkW)new ShellLink();
            try
            {
                link.SetPath(targetPath);
                link.SetWorkingDirectory(workDir);
                link.SetIconLocation(targetPath, 0);
                link.SetDescription(
                    Pick("PDF-Decrypt – Remove password from encrypted PDFs",
                         "PDF-Decrypt – Passwortschutz aus PDF-Dateien entfernen"));
                ((IPersistFile)link).Save(lnkPath, true);
            }
            finally
            {
                Marshal.ReleaseComObject(link);
            }
        }

        static string AppDir()
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Entry point
    // ─────────────────────────────────────────────────────────────────────────
    static class InstallerProgram
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new SetupForm());
        }
    }
}
