using C1.WPF;
using C1.WPF.Document;
using C1.WPF.PdfViewer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;

namespace WPF_Forschung_PDF_ComponentOne
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        const char WHITE_SPACE_100s = '\u2007';
        const char WHITE_SPACE_10s = '\u2001';
        const char WHITE_SPACE_1s = '\u2000';
        static char[] WHITE_SPACES = new char[] { WHITE_SPACE_100s, WHITE_SPACE_10s, WHITE_SPACE_1s };
        const int HEADING_KEY_NUMBER_LENGTH = 3; // Länge der Kapitel-Nr (z.B. #QSC111 -> 111 = 3)
        const string HEADING_KEY = "#QSC";
        const string LINK_KEY = "#QSC.";

        C1TextSearchManager tsm;

        public UserControl1()
        {
            InitializeComponent();


            C1PdfDocumentSource pds = new C1PdfDocumentSource();
            //tsm = new C1TextSearchManager(pds);
            //tsm.FoundPositionsChanged += Tsm_FoundPositionsChanged;
        }

        private void pdfViewer_Loaded(object sender, RoutedEventArgs e)
        {
            //LoadPdf();
        }

        private void OpenPDFInternal_Click(object sender, RoutedEventArgs e)
        {
            LoadPdf();
        }

        private void LoadPdf()
        {
            PdfPath.Text = PdfPath.Text.Replace("\"", "");

            if (!File.Exists(PdfPath.Text))
            {
                MessageBox.Show("PDF not found!");
                return;
            }

            // ACHTUNG: Wenn man ein Dokument über einen Stream lädt, ist bei der Ausführung von Links die Uri + Target immer Null!!!
            // Lädt man das Dokument über einen Pfad, funktioniert das ohne Probleme...
            pdfViewer.LoadDocument(PdfPath.Text);

            //byte[] bytes = File.ReadAllBytes(@"\\qsc-systems\qs1\Data\44a\CQDMS40DEV\C1HIBD\000000\a988f0d28b5243a8b147ba5bdcc4ef1c.pdf");
            //using (MemoryStream ms = new MemoryStream(bytes))
            //{
            //    pdfViewer.LoadDocument(ms);
            //}

            // TODO AS Wenn möglich mit Binding lösen
            lblPageCount.Text = pdfViewer.PageCount.ToString();
        }

        private void PdfPath_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                LoadPdf();
            }
        }

        private void TextmarkText_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                JumpToBookmark_Text();
            }
        }

        private void JumpToTextmarkText_Click(object sender, RoutedEventArgs e)
        {
            JumpToBookmark_Text();
        }

        private void TextmarkIndex_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                JumpToBookmark_Index();
            }
        }

        private void JumpToTextmarkIndex_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Test");
            JumpToBookmark_Index();
        }

        private void JumpToBookmark_Text()
        {
            // ACHTUNG: Bookmarks hat nur die Top-Level-Bookmarks - alle anderen müssen über bookmark.Children abgefragt werden
            IEnumerable<QscPdfBookmark> topLevelBookmarks = this.pdfViewer.Bookmarks.Select(b => new QscPdfBookmark(b));

            string searchText = TextmarkText.Text;
            if (string.IsNullOrEmpty(searchText))
            {
                MessageBox.Show("Error: Enter text!");
                return;
            }

            if (searchText.StartsWith("#QSC"))
            {
                // Auto-Umwandlung von QSC-Key zu White-Spaces (Usability)
                string whitespaces = qscKeyToWhitespaceSequence(searchText);
                searchText = whitespaces;

                var bookmark = getBookmarkByTextSimplified(topLevelBookmarks, null, searchText);
                if (bookmark != null)
                {
                    pdfViewer.GoToBookmark(bookmark.GetMultiLevelTitle());
                }
            }
            else
            {
                pdfViewer.GoToBookmark(searchText);
            }
        }

        private string qscKeyToWhitespaceSequence(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
                return "";

            key = key.Substring(key.Length - HEADING_KEY_NUMBER_LENGTH);
            int qscKeyNumber;

            if (!int.TryParse(key, out qscKeyNumber))
                return "";

            if (qscKeyNumber == 0)
                return "";

            int numberOfHundreds = 0;
            if (qscKeyNumber >= 100)
            {
                numberOfHundreds = qscKeyNumber / 100;
                qscKeyNumber -= (numberOfHundreds * 100);
            }
            int numberOfTens = qscKeyNumber / 10;
            int numberOfOnes = qscKeyNumber % 10;

            StringBuilder whiteSpaces = new StringBuilder();
            for (int i = 0; i < numberOfHundreds; i++)
            {
                whiteSpaces.Append(WHITE_SPACE_100s);
            }
            for (int i = 0; i < numberOfTens; i++)
            {
                whiteSpaces.Append(WHITE_SPACE_10s);
            }
            for (int i = 0; i < numberOfOnes; i++)
            {
                whiteSpaces.Append(WHITE_SPACE_1s);
            }

            return whiteSpaces.ToString();
        }

        private void JumpToBookmark_Index()
        {
            if (Int32.TryParse(TextmarkIndex.Text, out int index))
            {
                QscPdfBookmark bookmark = getBookmarkByIndex(index);

                if (bookmark != null)
                {
                    this.pdfViewer.GoToBookmark(bookmark.GetMultiLevelTitle()); String.Join(";", bookmark.GetMultiLevelTitle()).ToCharArray();
                }
            }
            else
            {
                MessageBox.Show("Error: Not a number!");
            }
        }

        private QscPdfBookmark getBookmarkByTextSimplified(IEnumerable<QscPdfBookmark> bookmarks, QscPdfBookmark parent, string searchText)
        {
            // Mittels Trim werden die zero-width no-break space chars (char nr. 65279) entfernt -> diese haben im Test-Projekt bei der Überschrift "Änderungen" ein Problem gemacht
            searchText = searchText.Trim(new char[] { '\uFEFF', '\u200B' });

            foreach (QscPdfBookmark bookmark in bookmarks)
            {
                if (parent != null)
                {
                    bookmark.Parent = parent;
                }

                // Mittels Trim werden die zero-width no-break space chars (65279) für den Vergleich entfernt
                // Hintergrund: Bei manchen Tests hatten diverse Überschriften diesen Character enthalten und daher wurde die Überschrift nicht gefunden
                // TODO AS Wsl. gar nicht mehr benötigt, jetzt wo wir mit dem QSC-Key suchen
                string bookmarkTitle = bookmark.Title.Trim(new char[] { '\uFEFF', '\u200B' });

                int indexFirstWhitespace = bookmarkTitle.IndexOfAny(WHITE_SPACES);
                if (indexFirstWhitespace != -1)
                {
                    string whitespaceSequence = bookmarkTitle.Substring(indexFirstWhitespace);
                    if (searchText == whitespaceSequence)
                        return bookmark;
                }

                // Children abarbeiten
                if (bookmark.Children.Count() > 0)
                {
                    IEnumerable<QscPdfBookmark> children = bookmark.Children.Select(c => new QscPdfBookmark(c));
                    QscPdfBookmark child = getBookmarkByTextSimplified(children, bookmark, searchText);

                    if (child != null)
                    {
                        // Child-Bookmark innerhalb dieser Bookmark gefunden -> Child-Bookmark zurückgeben
                        return child;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// EQUALS-Suche (Volltext)
        /// </summary>
        private QscPdfBookmark getBookmarkByText(IEnumerable<QscPdfBookmark> bookmarks, QscPdfBookmark parent, string searchTerm, List<string> currentBookmarksTree)
        {
            // ------------------ PROBLEM BEI DIESER VARIANTE ------------------
            // Gibt es mehrere Überschriften mit gleichem Namen in der gleichen Ebene (z.B. 2x Überschrift "Allgemein" in Ebene-1), finden wir immer nur die erste
            // -> daher ist die Variante mit Index besser (getBookmarkByIndex)

            // Mittels Trim werden die zero-width no-break space chars (char nr. 65279) entfernt -> diese haben im Test-Projekt bei der Überschrift "Änderungen" ein Problem gemacht
            searchTerm = searchTerm.Trim(new char[] { '\uFEFF', '\u200B' });

            foreach (QscPdfBookmark bookmark in bookmarks)
            {
                if (parent != null)
                {
                    bookmark.Parent = parent;
                }

                // Aktuelle Bookmark in Tree einfügen
                currentBookmarksTree.Add(bookmark.Title);

                // Vergleich aller Ebenen (alle Ebenen müssen übereinstimmen)
                // Mittels Trim werden die zero-width no-break space chars (65279) für den Vergleich entfernt
                // Hintergrund: Bei diversen Tests hatten manche Übesrchriften diesen Character enthalten (evtl. bei Umlauten?) und daher wurde die Überschrift nicht gefunden
                string treeJoined = String.Join("|", currentBookmarksTree.Select(s => s.Trim(new char[] { '\uFEFF', '\u200B' })));

                if (searchTerm == treeJoined)
                    return bookmark;

                // Children abarbeiten
                if (bookmark.Children.Count() > 0)
                {
                    IEnumerable<QscPdfBookmark> children = bookmark.Children.Select(c => new QscPdfBookmark(c));
                    QscPdfBookmark child = getBookmarkByText(children, bookmark, searchTerm, currentBookmarksTree);

                    if (child == null)
                    {
                        // Keine entsprechende Child-Bookmark innerhalb dieser Bookmark gefunden
                    }
                    else
                    {
                        // Child-Bookmark innerhalb dieser Bookmark gefunden -> Child-Bookmark zurückgeben
                        return child;
                    }
                }

                // Aktuelle Bookmark nicht gefunden (auch nicht in den Children) -> wieder aus Tree löschen
                currentBookmarksTree.Remove(bookmark.Title);
            }

            return null;
        }

        /// <summary>
        /// CONTAINS-Suche (QSC-IDs z.B. #{QSC001})
        /// </summary>
        private QscPdfBookmark getBookmarkByText_Contains(IEnumerable<QscPdfBookmark> bookmarks, QscPdfBookmark parent, string searchTerm, List<string> currentBookmarksTree)
        {
            // ------------------ PROBLEM BEI DIESER VARIANTE ------------------
            // Gibt es mehrere Überschriften mit gleichem Namen in der gleichen Ebene (z.B. 2x Überschrift "Allgemein" in Ebene-1), finden wir immer nur die erste
            // -> daher ist die Variante mit Index besser (getBookmarkByIndex)

            // Mittels Trim werden die zero-width no-break space chars (char nr. 65279) entfernt -> diese haben im Test-Projekt bei der Überschrift "Änderungen" ein Problem gemacht
            searchTerm = searchTerm.Trim(new char[] { '\uFEFF', '\u200B' });

            foreach (QscPdfBookmark bookmark in bookmarks)
            {
                if (parent != null)
                {
                    bookmark.Parent = parent;
                }

                // Aktuelle Bookmark in Tree einfügen
                currentBookmarksTree.Add(bookmark.Title);

                // Vergleich aller Ebenen (alle Ebenen müssen übereinstimmen)
                // Mittels Trim werden die zero-width no-break space chars (65279) für den Vergleich entfernt
                // Hintergrund: Bei diversen Tests hatten manche Übesrchriften diesen Character enthalten (evtl. bei Umlauten?) und daher wurde die Überschrift nicht gefunden
                string treeJoined = String.Join("|", currentBookmarksTree.Select(s => s.Trim(new char[] { '\uFEFF', '\u200B' })));

                // Var. 2) Contains-Suche für Überschriften-Key, z.B. #{QSC001}
                if (treeJoined.Contains(searchTerm))
                    return bookmark;

                // Children abarbeiten
                if (bookmark.Children.Count() > 0)
                {
                    IEnumerable<QscPdfBookmark> children = bookmark.Children.Select(c => new QscPdfBookmark(c));
                    QscPdfBookmark child = getBookmarkByText_Contains(children, bookmark, searchTerm, currentBookmarksTree);

                    if (child == null)
                    {
                        // Keine entsprechende Child-Bookmark innerhalb dieser Bookmark gefunden
                    }
                    else
                    {
                        // Child-Bookmark innerhalb dieser Bookmark gefunden -> Child-Bookmark zurückgeben
                        return child;
                    }
                }

                // Aktuelle Bookmark nicht gefunden (auch nicht in den Children) -> wieder aus Tree löschen
                currentBookmarksTree.Remove(bookmark.Title);
            }

            return null;
        }

        private QscPdfBookmark getBookmarkByIndex(int index)
        {
            // TODO AS ggf. könnte man allBookmarks cachen, bzw. nur in DocumentChanged Event durchführen
            IEnumerable<Bookmark> topLevelBookmarks = this.pdfViewer.Bookmarks;
            List<QscPdfBookmark> allBookmarks = new List<QscPdfBookmark>();

            // Alle Bookmarks in flache Liste einlesen
            foreach (var bookmark in topLevelBookmarks)
            {
                QscPdfBookmark qscBookmark = new QscPdfBookmark(bookmark);
                addBookmarksToCollection(qscBookmark, null, allBookmarks);
            }

            if (index >= 0 && index < allBookmarks.Count)
                return allBookmarks[index]; allBookmarks[index].Title.ToCharArray();

            return null;
        }

        /// <summary>
        /// Fügt die Bookmark inkl. deren Sub-Bookmarks (falls vorhanden) in die übergebene Collection ein
        /// </summary>
        /// <param name="bookmark"></param>
        private void addBookmarksToCollection(QscPdfBookmark bookmark, QscPdfBookmark parent, List<QscPdfBookmark> collection)
        {
            collection.Add(bookmark);

            // Parent setzen, falls vorhanden
            if (parent != null)
            {
                bookmark.Parent = parent;
            }

            // Kinder einfügen (rekursiv)
            foreach (Bookmark child in bookmark.Children)
            {
                QscPdfBookmark childBookmark = new QscPdfBookmark(child);
                addBookmarksToCollection(childBookmark, bookmark, collection);
            }
        }

        private void SearchTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                SearchPdf(sender);
            }
        }

        private void SearchText_Click(object sender, RoutedEventArgs e)
        {
            SearchPdf(sender);
        }

        private void SearchPdf(object sender)
        {
            if (string.IsNullOrEmpty(SearchTextBox.Text))
            {
                MessageBox.Show("Error: No search text!");
                return;
            }

            // Highlight-Color setzen (funktioniert nur über XAML??)
            Brush brush = new SolidColorBrush(Color.FromRgb(0, 255, 0)) { Opacity = 0.5 };
            pdfViewer.SelectionBackground = brush;

            pdfViewer.FindText = SearchTextBox.Text;

            if (sender == SearchNext)
            {
                pdfViewer.FindNext();
                //pdfViewer.FindNext(); // TODO AS Test n-Mal weiterhüpfen
                //pdfViewer.FindNext();
            }
            else if (sender == SearchPrev)
            {
                pdfViewer.FindPrevious();
            }
        }

        //private void Tsm_FoundPositionsChanged(object sender, EventArgs e)
        //{
        //    int n = tsm.FoundPositions.Count;
        //    for (int i = listView1.Items.Count; i < n; i++)
        //    {
        //        C1FoundPosition fp = tsm.FoundPositions[i];
        //        var bounds = fp.GetBounds();

        //        listView1.Items.Add(new SearchItem
        //        {
        //            ID = i + 1,
        //            Page = fp.GetPage().PageNo.ToString(),
        //            Bounds = string.Format("{0}, {1}, {2}, {3}",
        //                     (int)Math.Round(bounds.Left),
        //                     (int)Math.Round(bounds.Top),
        //                     (int)Math.Round(bounds.Width),
        //                     (int)Math.Round(bounds.Height)),
        //            Position = fp.PositionInNearText.ToString(),
        //            NearText = fp.NearText
        //        });
        //    }
        //}



        private void pdfViewer_AttachmentClick(object sender, AttachmentClickEventArgs e)
        {
            MessageBox.Show("attachment clicked");
        }

        private void pdfViewer_RequestNavigate(object sender, C1.WPF.PdfViewer.RequestNavigateEventArgs e)
        {
            MessageBox.Show("link clicked: Target=" + e.Target + " URI=" + (e.Uri?.ToString() ?? "(null)"));
        }

        private void ToolbarButton_Click(object sender, RoutedEventArgs e)
        {
            ICommand command = null;

            // TODO AS Commands funktionieren nicht über direktes Binding im XAML ??
            if (sender == tbZoomIn)
                command = pdfViewer.IncreaseZoomCommand;
            else if (sender == tbZoomOut)
                command = pdfViewer.DecreaseZoomCommand;
            else if (sender == tbFirstPage)
                command = pdfViewer.FirstPageCommand;
            else if (sender == tbPrevPage)
                command = pdfViewer.PreviousPageCommand;
            else if (sender == tbNextPage)
                command = pdfViewer.NextPageCommand;
            else if (sender == tbLastPage)
                command = pdfViewer.LastPageCommand;
            else if (sender == tbSave)
                command = pdfViewer.SaveCommand; // TODO AS Umsetzung in QS1: eigene save() Methode
            else if (sender == tbPrint)
                command = pdfViewer.PrintCommand; // TODO AS Umsetzung in QS1: eigene print() Methode

            if (command != null)
            {
                if (command.CanExecute(null))
                    command.Execute(null);
            }
            else if (sender == tbRotateC)
            {
                RotateClockwise();
            }
            else if (sender == tbRotateCC)
            {
                RotateCounterClockwise();
            }
            else if (sender == tbPageWidth)
            {
                pdfViewer.ViewMode = ViewMode.FitWidth; // An Breite anpassen
            }
            else if (sender == tbWholePage)
            {
                pdfViewer.ViewMode = ViewMode.OnePage; // Ganze Seite
            }
            else if (sender == tbHideShow)
            {
                pdfViewer.Visibility = pdfViewer.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        int _angle = 0;
        private void RotateClockwise()
        {
            if (_angle >= 360)
                _angle = 0;
            _angle += 90;
            Rotate();
        }

        private void RotateCounterClockwise()
        {
            if (_angle <= -360)
                _angle = 0;
            _angle -= 90;
            Rotate();
        }

        void Rotate()
        {
            var list = pdfViewer.Template.FindName("PageList", pdfViewer) as C1ListViewer;
            for (int i = 0; i < list.Items.Count; i++)
            {
                var item = list.ItemContainerGenerator.ContainerFromIndex(i);
                if (item != null)
                {
                    C1ListViewerItem listItem = (C1ListViewerItem)(item);
                    var child = VisualTreeHelper.GetChild(listItem, 0);
                    if (child is System.Windows.Controls.Border)
                    {
                        var bdr = (System.Windows.Controls.Border)child;
                        RotateTransform rotate = new RotateTransform(_angle, bdr.ActualWidth / 2, bdr.ActualHeight / 2);
                        bdr.RenderTransform = rotate;
                    }
                }
            }
        }

        private void tbPage_KeyDown(object sender, KeyEventArgs e)
        {
            Console.WriteLine("> " + pdfViewer.PageCount);

            if (e.Key == Key.Enter)
            {
                int result;
                if (int.TryParse(tbPage.Text, out result))
                {
                    pdfViewer.GoToPage(result);
                }
                else
                {
                    MessageBox.Show("Error: invalid page");
                }
            }
        }

        private void pdfViewer_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if(e.PropertyName=="PageNumber")
                tbPage.Text = pdfViewer.PageNumber.ToString();
        }

        //private void tbZoomFactor_KeyDown(object sender, KeyEventArgs e)
        //{
        //    if (e.Key == Key.Enter)
        //    {
        //        double result;
        //        if (double.TryParse(tbZoomFactor.Text.Replace(".", ","), out result))
        //        {
        //            pdfViewer.Zoom = result;
        //        }
        //        else
        //        {
        //            MessageBox.Show("Error: invalid zoom factor");
        //        }
        //    }
        //}
    }

    public class SearchItem
    {
        public int ID { get; set; }
        public string Page { get; set; }
        public string Bounds { get; set; }
        public string Position { get; set; }
        public string NearText { get; set; }
    }
}
