using Elwig.Windows;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace Elwig.Helpers.Printing {
    public static class Pdf {

        private static readonly string WinziPrint = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ?
            Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Installer/Files/WinziPrint.exe") :
            new string[] { App.InstallPath }
                .Union(Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [])
                .Select(x => Path.Combine(x, "WinziPrint.exe"))
                .Where(File.Exists)
                .FirstOrDefault() ?? throw new FileNotFoundException("WiniPrint executable not found");
        private static Process? WinziPrintProc;
        public static bool IsReady => WinziPrintProc != null;

        public static async Task Init(Action? evtHandler = null) {
            PdfiumNative.FPDF_InitLibrary();
            // NOTE: If the WinziPrint daemon is already running this will succeed, but the process will fail.
            //       Should be no problem, as long as the daemon is not closed
            var p = new Process() { StartInfo = new() {
                FileName = WinziPrint,
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardOutput = true,
            } };
            p.StartInfo.ArgumentList.Add("-D");
            p.StartInfo.ArgumentList.Add("-d");
            p.StartInfo.ArgumentList.Add(App.TempPath);
            p.Start();
            await p.StandardOutput.ReadLineAsync();
            WinziPrintProc = p;
            evtHandler?.Invoke();
        }

        public static Task Cleanup() {
            WinziPrintProc?.Kill(true);
            WinziPrintProc?.Close();
            PdfiumNative.FPDF_DestroyLibrary();
            return Task.CompletedTask;
        }

        public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(string htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
            return await Convert([htmlPath], pdfPath, doublePaged, cancelToken, progress);
        }

        public static async Task<(int Pages, IEnumerable<int> PerDoc)> Convert(IEnumerable<string> htmlPath, string pdfPath, bool doublePaged = false, CancellationToken? cancelToken = null, IProgress<double>? progress = null) {
            if (WinziPrintProc == null) throw new InvalidOperationException("The WinziPrint process has not been initialized yet");
            progress?.Report(0.0);
            using var client = new TcpClient("127.0.0.1", 30983);
            using var stream = client.GetStream();
            string cnxId;
            using var reader = new StreamReader(stream);
            var first = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
            if (first.StartsWith("id:")) {
                cnxId = first[3..].Trim();
            } else {
                throw new IOException("Invalid response from WinziPrint");
            }
            await stream.WriteAsync(Utils.UTF8.GetBytes(
                "-e utf-8;-p;" + (doublePaged ? "-2;" : "") +
                $"{string.Join(';', htmlPath)};{pdfPath}" +
                "\r\n"));
            bool cancelled = false;
            while (true) {
                if (!cancelled && (cancelToken?.IsCancellationRequested ?? false)) {
                    try {
                        using var cancelClient = new TcpClient("127.0.0.1", 30983);
                        using var cancelStream = cancelClient.GetStream();
                        using var cancelReader = new StreamReader(cancelStream);
                        await cancelReader.ReadLineAsync();
                        await cancelStream.WriteAsync(Utils.UTF8.GetBytes($"cancel;{cnxId}\r\n"));
                    } catch { }
                    cancelled = true;
                }
                var line = await reader.ReadLineAsync() ?? throw new IOException("Invalid response from WinziPrint");
                if (line.StartsWith("error:")) {
                    var msg = line[6..].Trim();
                    if (msg == "aborted")
                        throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!");
                    throw new IOException($"WinziPrint: {msg}");
                } else if (line.StartsWith("progress:")) {
                    var parts = line[9..].Trim().Split('/').Select(int.Parse).ToArray();
                    progress?.Report(100.0 * parts[0] / parts[1]);
                } else if (line.StartsWith("success:")) {
                    var m = Regex.Match(line, @"([0-9]+) pages \(([0-9, ]+)\)");
                    return (int.Parse(m.Groups[1].Value), m.Groups[2].Value.Split(", ").Select(int.Parse).ToList());
                }
            }
        }

        public static void Show(TempFile file, string title) {
            App.MainDispatcher.BeginInvoke(() => {
                var w = new DocumentViewerWindow(title, file);
                w.Show();
            });
        }

        public static void Show(string path, string title) {
            App.MainDispatcher.BeginInvoke(() => {
                var w = new DocumentViewerWindow(title, path);
                w.Show();
            });
        }

        public static async Task Print(string path, int copies = 1, bool doublePaged = false) {
            await Print(path, new() {
                Copies = (short)copies,
                Collate = true,
                Duplex = doublePaged ? Duplex.Vertical : Duplex.Simplex,
            });
        }

        public static Task Print(string path, PrinterSettings settings) {
            try {
                using var printDoc = new PdfPrintDocument(path) {
                    PrinterSettings = settings,
                };
                printDoc.Print();
            } catch (Exception e) {
                MessageBox.Show("Beim Drucken ist ein Fehler aufgetreten:\n\n" + e.Message, "Fehler beim Drucken", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            return Task.CompletedTask;
        }
    }
}
