// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
// Description: Visual representing a paragraph. 
//

using System;
using System.Windows;
using System.Windows.Media;

namespace MS.Internal.PtsHost
{
    /// <summary>
    /// Visual representing a paragraph.
    /// </summary>
    internal class ParagraphVisual : DrawingVisual
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        internal ParagraphVisual()
        {
            _renderBounds = Rect.Empty;
        }

        /// <summary>
        /// Draw background and border.
        /// </summary>
        /// <param name="backgroundBrush">The brush used for background.</param>
        /// <param name="borderBrush">The brush used for border.</param>
        /// <param name="borderThickness">Border thickness.</param>
        /// <param name="renderBounds">Render bounds of the visual.</param>
        /// <param name="isFirstChunk">Whether this is paragraph's first chunk.</param>
        /// <param name="isLastChunk">Whether this is paragraph's last chunk.</param>
        internal void DrawBackgroundAndBorder(Brush backgroundBrush, Brush borderBrush, Thickness borderThickness, Rect renderBounds, bool isFirstChunk, bool isLastChunk)
        {
            if (_backgroundBrush != backgroundBrush || _renderBounds != renderBounds ||
                _borderBrush != borderBrush || !Thickness.AreClose(_borderThickness, borderThickness))
            {
                // Open DrawingContext and draw background.
                using (DrawingContext dc = RenderOpen())
                {
                    DrawBackgroundAndBorderIntoContext(dc, backgroundBrush, borderBrush, borderThickness, renderBounds, isFirstChunk, isLastChunk);
                }
            }
        }


        /// <summary>
        /// Draw background and border.
        /// </summary>
        /// <param name="dc">Drawing context.</param>
        /// <param name="backgroundBrush">The brush used for background.</param>
        /// <param name="borderBrush">The brush used for border.</param>
        /// <param name="borderThickness">Border thickness.</param>
        /// <param name="renderBounds">Render bounds of the visual.</param>
        /// <param name="isFirstChunk">Whether this is paragraph's first chunk.</param>
        /// <param name="isLastChunk">Whether this is paragraph's last chunk.</param>
        internal void DrawBackgroundAndBorderIntoContext(DrawingContext dc, Brush backgroundBrush, Brush borderBrush, Thickness borderThickness, Rect renderBounds, bool isFirstChunk, bool isLastChunk)
        {
            // We do not want to cause the user's Brushes to become frozen when we
            // freeze the Pen in OnRender, therefore we make our own copy of the
            // Brushes if they are not already frozen.
            _backgroundBrush = (Brush)FreezableOperations.GetAsFrozenIfPossible(backgroundBrush);
            _renderBounds = renderBounds;
            _borderBrush = (Brush)FreezableOperations.GetAsFrozenIfPossible(borderBrush);
            _borderThickness = borderThickness;

            // Exclude top/bottom border if this is not the first/last chunk of the paragraph.
            if (!isFirstChunk)
            {
                _borderThickness.Top = 0.0;
            }
            if (!isLastChunk)
            {
                _borderThickness.Bottom = 0.0;
            }

            // If we have a brush with which to draw the border, do so.
            // NB: We double draw corners right now.  Corner handling is tricky (bevelling, &c...) and
            //     we need a firm spec before doing "the right thing."  (greglett, ffortes)
            if (_borderBrush != null)
            {
                // Initialize the first pen.  Note that each pen is created via new()
                // and frozen if possible.  Doing this avoids the overhead of
                // maintaining changed handlers.
                Pen pen = new Pen();
                pen.Brush = _borderBrush;
                pen.Thickness = _borderThickness.Left;
                if (pen.CanFreeze) { pen.Freeze(); }

                if (_borderThickness.IsUniform)
                {
                    // Uniform border; stroke a rectangle. 
                    dc.DrawRectangle(null, pen, new Rect(
                        new Point(_renderBounds.Left + pen.Thickness * 0.5, _renderBounds.Bottom - pen.Thickness * 0.5),
                        new Point(_renderBounds.Right - pen.Thickness * 0.5, _renderBounds.Top + pen.Thickness * 0.5)));
                }
                else
                {
                    if (DoubleUtil.GreaterThanZero(_borderThickness.Left))
                    {
                        dc.DrawLine(pen,
                            new Point(_renderBounds.Left + pen.Thickness / 2, _renderBounds.Top),
                            new Point(_renderBounds.Left + pen.Thickness / 2, _renderBounds.Bottom));
                    }
                    if (DoubleUtil.GreaterThanZero(_borderThickness.Right))
                    {
                        pen = new Pen();
                        pen.Brush = _borderBrush;
                        pen.Thickness = _borderThickness.Right;
                        if (pen.CanFreeze) { pen.Freeze(); }

                        dc.DrawLine(pen,
                            new Point(_renderBounds.Right - pen.Thickness / 2, _renderBounds.Top),
                            new Point(_renderBounds.Right - pen.Thickness / 2, _renderBounds.Bottom));
                    }
                    if (DoubleUtil.GreaterThanZero(_borderThickness.Top))
                    {
                        pen = new Pen();
                        pen.Brush = _borderBrush;
                        pen.Thickness = _borderThickness.Top;
                        if (pen.CanFreeze) { pen.Freeze(); }

                        dc.DrawLine(pen,
                            new Point(_renderBounds.Left, _renderBounds.Top + pen.Thickness / 2),
                            new Point(_renderBounds.Right, _renderBounds.Top + pen.Thickness / 2));
                    }
                    if (DoubleUtil.GreaterThanZero(_borderThickness.Bottom))
                    {
                        pen = new Pen();
                        pen.Brush = _borderBrush;
                        pen.Thickness = _borderThickness.Bottom;
                        if (pen.CanFreeze) { pen.Freeze(); }

                        dc.DrawLine(pen,
                            new Point(_renderBounds.Left, _renderBounds.Bottom - pen.Thickness / 2),
                            new Point(_renderBounds.Right, _renderBounds.Bottom - pen.Thickness / 2));
                    }
                }
            }


            // Draw background in rectangle inside border.
            if (_backgroundBrush != null)
            {
                dc.DrawRectangle(_backgroundBrush, null,
                    new Rect(
                        new Point(_renderBounds.Left + _borderThickness.Left, _renderBounds.Top + _borderThickness.Top),
                        new Point(_renderBounds.Right - _borderThickness.Right, _renderBounds.Bottom - _borderThickness.Bottom)));
            }
        }

        private Brush _backgroundBrush;         // Background brush
        private Brush _borderBrush;             // Border brush
        private Thickness _borderThickness;     // Border thickness
        private Rect _renderBounds;             // Render bounds of the visual
    }
}
