using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.Windows.Forms;
using NotepadEnhanced.AppState;
using NotepadEnhanced.GUI.Controls;
using System.Drawing;
using System.IO;
using NotepadEnhanced.Helpers;
using NotepadEnhanced.Localization;

namespace NotepadEnhanced
{
    class Printing : IDisposable
    {
        private readonly PrintDocument _printDocument = new PrintDocument();
        private readonly PrintDialog _dlgPrint = new PrintDialog();
        private readonly PlainTextBox _activePtb;
        private List<string> _lines = new List<string>();
        private int _linesPrinted;

        public Printing(PlainTextBox activePtb)
        {
            _activePtb = activePtb;
            _dlgPrint.Document = _printDocument;
            _dlgPrint.AllowSelection = !string.IsNullOrEmpty(_activePtb.SelectedText);
            _printDocument.PrintPage += _printDoc_PrintPage;
            _printDocument.BeginPrint += _printDoc_BeginPrint;
            _printDocument.EndPrint += _printDoc_EndPrint;
        }

        public void PrintRichTextDocument(string documentName)
        {
            _printDocument.DocumentName = documentName;

            try
            {
                if (_dlgPrint.ShowDialog() == DialogResult.OK)
                {
                    _printDocument.Print();
                }
            }
            catch (InvalidPrinterException ex)
            {
                MsgBox.Show(ex.Message);
            }
        }

        private static float GetFontHeight(Graphics graphics)
        {
            SizeF fontSize = graphics.MeasureString("ABC", UserSettings.Instance.Font);
            return fontSize.Height;
        }

        private void _printDoc_PrintPage(object sender, PrintPageEventArgs e)
        {
            float x = e.MarginBounds.Left;
            float y = e.MarginBounds.Top;
            Brush brush = new SolidBrush(UserSettings.Instance.FontColor);

            if (UserSettings.Instance.AddTitleToPrint)
            {
                // Align left
                if (UserSettings.Instance.PrintTitleAlign == 0)
                {
                    e.Graphics.DrawString(_lines[0], UserSettings.Instance.Font, brush, x, y);
                    y += GetFontHeight(e.Graphics);
                }
                // Align center
                else if (UserSettings.Instance.PrintTitleAlign == 1)
                {
                    float width = GetStringWidth(_lines[0]);
                    float xPos = x + (GetMaxLineWidth() / 2f) - (width / 2);

                    e.Graphics.DrawString(_lines[0], UserSettings.Instance.Font, brush, xPos, y);
                    y += GetFontHeight(e.Graphics);
                }
                // Align right
                else if (UserSettings.Instance.PrintTitleAlign == 2)
                {
                    float width = GetStringWidth(_lines[0]);
                    float xPos = x + GetMaxLineWidth() - width;


                    e.Graphics.DrawString(_lines[0], UserSettings.Instance.Font, brush, xPos, y);
                    y += GetFontHeight(e.Graphics);
                }

                _linesPrinted++;
            }

            while (_linesPrinted < _lines.Count)
            {
                string line = _lines[_linesPrinted++];

                e.Graphics.DrawString(line, UserSettings.Instance.Font, brush, x, y);
                y += GetFontHeight(e.Graphics);

                if (y >= e.MarginBounds.Bottom - GetFontHeight(e.Graphics))
                {
                    e.HasMorePages = true;
                    return;
                }
            }

            _linesPrinted = 0;
            e.HasMorePages = false;
            brush.Dispose();
        }

        private void _printDoc_BeginPrint(object sender, PrintEventArgs e)
        {
            if (_dlgPrint.PrinterSettings.PrintRange == PrintRange.Selection)
            {
                _lines = new List<string>(_activePtb.SelectedText.Split('\n'));
            }
            else
            {
                _lines = GetPrintableLines();
            }

            // This is performed after lines are built, so easy insertions is done
            // First a new line is added then on top of that a title
            if (UserSettings.Instance.AddTitleToPrint && _activePtb.Document.SafeFileName.Length > 0)
            {
                _lines.Insert(0, string.Empty);
                string title = Path.GetFileNameWithoutExtension(_activePtb.Document.SafeFileName);
                _lines.Insert(0, title);
            }
        }

        /// <summary>
        /// Gets the maximum line with in pixels, accounting the margins of the
        /// page as well as the page with
        /// </summary>
        private float GetMaxLineWidth()
        {
            if (_printDocument.DefaultPageSettings.Landscape)
            {
                return _printDocument.DefaultPageSettings.PaperSize.Height -
                    _printDocument.DefaultPageSettings.Margins.Left -
                     _printDocument.DefaultPageSettings.Margins.Right;
            }

                return _printDocument.DefaultPageSettings.PaperSize.Width -
                    _printDocument.DefaultPageSettings.Margins.Left -
                    _printDocument.DefaultPageSettings.Margins.Right;
        }

        /// <summary>
        /// Processes the lines so they fit within the page margins.
        /// </summary>
        private List<string> GetPrintableLines()
        {
            //Get rid of carriage returns so no ovelaps occur
            string text = _activePtb.Text.Replace("\r", string.Empty);
            float maxLineWidth = GetMaxLineWidth();
            var lines = new List<string>();
            int indexStart = 0, indexEnd = 1;

            if (_activePtb.Lines.Length.Equals(1))
            {
                lines.Add(text);
                return lines;
            }

            while (++indexEnd < text.Length)
            {
                if (text[indexEnd] == '\n')
                {
                    lines.Add(text.Substring(indexStart, indexEnd - indexStart).Trim());
                    indexEnd = indexStart = indexEnd;
                    continue;
                }

                string line = text.Substring(indexStart, indexEnd - indexStart);
                if (!(GetStringWidth(line) >= maxLineWidth)) continue;
                int splitIndex = indexEnd - 1;

                while (indexEnd-- > 0)
                {
                    if (text[indexEnd].Equals(' '))
                    {
                        int length = indexEnd - indexStart;
                        lines.Add(text.Substring(indexStart, length).Trim());
                        indexStart = indexEnd;
                        break;
                    }

                    if (indexEnd.Equals(indexStart + 1))
                    {
                        int length = splitIndex - indexStart;
                        lines.Add(text.Substring(indexStart, length).Trim());
                    }
                }
            }

            return lines;
        }

        private float GetStringWidth(string text)
        {
            using (Graphics graphics = _activePtb.CreateGraphics())
            {
                return graphics.MeasureString(text, UserSettings.Instance.Font).Width;
            }
        }

        private void _printDoc_EndPrint(object sender, PrintEventArgs e)
        {
            _lines = null;
        }

        public void ShowPrintPreview()
        {
            if (_printDocument.PrinterSettings.IsValid)
            {
                using (var dlgPrintPreview = new PrintPreviewDialog())
                {
                    dlgPrintPreview.ClientSize = new Size(800, 600);
                    dlgPrintPreview.Document = _printDocument;
                    dlgPrintPreview.ShowDialog();
                }
            }
            else
            {
                MsgBox.Show(Strings.MsgNoPrinter, MessageBoxIcon.Information);
            }
        }

        public void ShowPageSetupDialog()
        {
            if (!_printDocument.PrinterSettings.IsValid)
            {
                MsgBox.Show(Strings.MsgNoPrinter, MessageBoxIcon.Information);
            }
            else
            {
                using (var dlgPageSetup = new PageSetupDialog())
                {
                    dlgPageSetup.Document = _printDocument;
                    dlgPageSetup.ShowDialog();
                }
            }
        }

        public void Dispose()
        {
            _dlgPrint.Dispose();
            _printDocument.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}
