//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Windows.Forms;
using System.Drawing.Printing;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms.VisualStyles;
using System.Globalization;

namespace EhLib
{

  public enum PageColontitleLineType
  { Non, SingleLine, DoubleLine };

  public enum ScalingMode
    { AdjustToScale, FitToPages };

  public enum PageOrientation
    { Portrait, Landscape };

  public enum MeasurementSystem
  { Metric, Imperial };

  //    property Orientation: TPrinterOrientation read FOrientation write FOrientation default poPortrait;
  //       TPrinterOrientation = (poPortrait, poLandscape);

  // RegionInfo.CurrentRegion.IsMetric
  [TypeConverter(typeof(ExpandableObjectConverter))]
  public class PageMargins
  {
    private MeasurementSystem measurementSystem = MeasurementSystem.Imperial;

    public PageMargins()
    {
      Bottom = 1F;
      Top = 1F;

      Left = 1F;
      Right = 1F;

      Header = 0.3F;
      Footer = 0.3F;
    }

    [DefaultValue(1F)]
    public float Bottom { get; set; }

    [DefaultValue(1F)]
    public float Left { get; set; }

    [DefaultValue(1F)]
    public float Right { get; set; }

    [DefaultValue(1F)]
    public float Top { get; set; }

    [DefaultValue(0.3F)]
    public float Header { get; set; }

    [DefaultValue(0.3F)]
    public float Footer { get; set; }

    [DefaultValue(MeasurementSystem.Imperial)]
    MeasurementSystem MeasurementSystem
    {
      get
      {
        return measurementSystem;
      }
      set
      {
        if (value != measurementSystem)
        {
          measurementSystem = value;
          ConvertValueToNewMeasurementSystem();
        }
      }
    }

    private void ConvertValueToNewMeasurementSystem()
    {
      if (MeasurementSystem == MeasurementSystem.Imperial)
      {
        Bottom = (float)Math.Round(Bottom / 2.54, 4);
        Left = (float)Math.Round(Left / 2.54, 4);
        Right = (float)Math.Round(Right / 2.54, 4);
        Top = (float)Math.Round(Top / 2.54, 4);
        Header = (float)Math.Round(Header / 2.54, 4);
        Footer = (float)Math.Round(Footer / 2.54, 4);
      }
      else
      {
        Bottom = (float)Math.Round(Bottom * 2.54, 4);
        Left = (float)Math.Round(Left * 2.54, 4);
        Right = (float)Math.Round(Right * 2.54, 4);
        Top = (float)Math.Round(Top * 2.54, 4);
        Header = (float)Math.Round(Header * 2.54, 4);
        Footer = (float)Math.Round(Footer * 2.54, 4);
      }
    }

    public float ToGraphicsUnits(float value, Graphics graphics)
    {
      if (MeasurementSystem == MeasurementSystem.Imperial)
        return value * 100;
      else
        return (float)(value / 2.54 * 100);
    }

    public float ToGraphicsUnits(float value, int PixelsPerInch)
    {
      if (MeasurementSystem == MeasurementSystem.Imperial)
        return value * PixelsPerInch;
      else
        return (float)(value / 2.54 * PixelsPerInch);
    }
  }

  [TypeConverter(typeof(ExpandableObjectConverter))]
  public class PrintScaling
  {
    private int scale = 100;
    private int fitToPagesTall = 1;
    private int fitToPagesWide = 1;

    [DefaultValue(100)]
    public int Scale
    {
      get
      {
        return scale;
      }

      set
      {
        if (value <= 0)
          throw new InvalidOperationException("'Scale' can't be less or equal zero");
        scale = value;
      }
    }

    [DefaultValue(1)]
    public int FitToPagesTall 
    { 
      get { return fitToPagesTall; }
      set { fitToPagesTall = value;}
    }

    [DefaultValue(1)]
    public int FitToPagesWide
    {
      get { return fitToPagesWide; }
      set { fitToPagesWide = value; }
    }

    [DefaultValue(ScalingMode.AdjustToScale)]
    public ScalingMode ScalingMode { get; set; }
  }

  [TypeConverter(typeof(ExpandableObjectConverter))]
  public class PageColontitle
  {
    [DefaultValue(PageColontitleLineType.Non)]
    public PageColontitleLineType LineType { get; set; }

    [Editor("EhLib.Design.SimpleRichEditor" + EhLibUtils.EhLibDesignDesignerVersionInfo, "System.Drawing.Design.UITypeEditor, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    [DefaultValue(null)]
    public string CenterText { get; set; }

    [DefaultValue(null)]
    public string LeftText { get; set; }

    [DefaultValue(null)]
    public string RightText { get; set; }

    [Browsable(false)]
    public string CenterTextAsRtf
    {
      get
      {
        if (String.IsNullOrEmpty(CenterText))
          return null;
        else if (CenterText.StartsWith("{\\rtf"))
          return CenterText;
        else
          return EhLibUtils.PlainTextToRtfText(CenterText, HorizontalAlignment.Center);
      }
    }

    [Browsable(false)]
    public string LeftTextAsRtf
    {
      get
      {
        if (String.IsNullOrEmpty(LeftText))
          return null;
        else if (LeftText.StartsWith("{\\rtf"))
          return LeftText;
        else
          return EhLibUtils.PlainTextToRtfText(LeftText, HorizontalAlignment.Left);
      }
    }

    [Browsable(false)]
    public string RightTextAsRtf
    {
      get
      {
        if (String.IsNullOrEmpty(RightText))
          return null;
        else if (RightText.StartsWith("{\\rtf"))
          return RightText;
        else
          return EhLibUtils.PlainTextToRtfText(RightText, HorizontalAlignment.Right);
      }
    }
  }

  [ToolboxItem(false)]
  [TypeConverter(typeof(BasePrintServiceComponentConverter))]
  public class BasePrintServiceComponent : PrintDocument
  {
    #region private consts
    static readonly string[] ColontileMacros = { "&[Page]", "&[ShortDate]", "&[Date]", "&[LongDate]", "&[Time]", "&[Pages]" };
    #endregion private consts

    #region internals
    private readonly PageMargins pageMargins;
    private readonly PrintScaling scaling;
    private readonly PageColontitle pageFooter;
    private readonly PageColontitle pageHeader;

    private float pageScale;
    private Rectangle pageContentBounds;
    private int pageNo;
    private int pageCount;
    private readonly string[] _macroValues = new string[6];
    private RichTextBoxPrintSerivce rtfPrintSerivce;
    private RichTextBox toolRichTextBox;
    #endregion internals

    public BasePrintServiceComponent()
    {
      pageMargins = new PageMargins();
      scaling = new PrintScaling();
      pageFooter = new PageColontitle();
      pageHeader = new PageColontitle();

      TextAfterContent = "";
      TextBeforeContent = "";
    }

    #region run-time properties
    [Browsable(false)]
    public Rectangle PageContentBounds
    {
      get
      {
        return pageContentBounds;
      }
    }

    [Browsable(false)]
    public int PageNo
    {
      get
      {
        return pageNo;
      }
    }

    [Browsable(false)]
    public int PageCount
    {
      get
      {
        return pageCount;
      }

      protected set
      {
        pageCount = value;
      }
    }

    #endregion run-time properties

    #region design-time properties
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public PageMargins PageMargins
    {
      get
      {
        return pageMargins;
      }
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public PrintScaling Scaling
    {
      get
      {
        return scaling;
      }
    }

    [DefaultValue(PageOrientation.Portrait)]
    public PageOrientation Orientation { get; set; }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public PageColontitle PageFooter
    {
      get
      {
        return pageFooter;
      }
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public PageColontitle PageHeader
    {
      get
      {
        return pageHeader;
      }
    }

    [DefaultValue("")]
    public string TextAfterContent
    { get; set; }

    [DefaultValue("")]
    public string TextBeforeContent
    { get; set; }
    #endregion design-time properties

    #region events
    protected new event PrintEventHandler BeginPrint
    {
      add { base.BeginPrint += value; }
      remove { base.BeginPrint -= value; }
    }

    protected new event PrintEventHandler EndPrint
    {
      add { base.EndPrint += value; }
      remove { base.EndPrint -= value; }
    }

    protected new event PrintPageEventHandler PrintPage
    {
      add { base.PrintPage += value; }
      remove { base.PrintPage -= value; }
    }

    protected new event QueryPageSettingsEventHandler QueryPageSettings
    {
      add { base.QueryPageSettings += value; }
      remove { base.QueryPageSettings -= value; }
    }
    #endregion events

    #region public methods
    public void PrintPreview()
    {
      using (var ppvw = new PrintPreviewDialog())
      {
        ppvw.Document = this;
        ppvw.Size = new Size(800, 600);
        ppvw.PrintPreviewControl.Zoom = 2;
        ppvw.ShowDialog();
      }
    }

    public void SetToNextPage(PrintPageEventArgs e)
    {
      pageNo = pageNo + 1;
      e.HasMorePages = true;
    }
    #endregion public methods

    #region internal methods
    protected override void OnBeginPrint(PrintEventArgs e)
    {
      base.OnBeginPrint(e);
      pageNo = 1;
      rtfPrintSerivce = new RichTextBoxPrintSerivce();
      toolRichTextBox = new RichTextBox();

      if (Orientation == PageOrientation.Landscape)
        DefaultPageSettings.Landscape = true;
      else
        DefaultPageSettings.Landscape = false;

      CalcScale();
      SetPageMetrics();
      InitMacroValues();
    }

    protected override void OnEndPrint(PrintEventArgs e)
    {
      base.OnEndPrint(e);
      rtfPrintSerivce.Dispose();
      toolRichTextBox.Dispose();
    }

    protected virtual void SetPageMetrics()
    {
      DefaultPageSettings.Margins = 
        new Margins((int)PageMargins.ToGraphicsUnits(PageMargins.Left, 100), 
                    (int)PageMargins.ToGraphicsUnits(PageMargins.Right, 100), 
                    (int)PageMargins.ToGraphicsUnits(PageMargins.Top, 100),
                    (int)PageMargins.ToGraphicsUnits(PageMargins.Bottom, 100));
      pageContentBounds = GetPageContentBoundsForScale(pageScale);
    }

    protected virtual Rectangle GetPageContentBoundsForScale(float pageScale)
    {
      Rectangle result = new Rectangle();
      PageSettings dps = DefaultPageSettings;
      Rectangle pageBounds = GetPageBounds();

      Rectangle marginBounds = new Rectangle(dps.Margins.Left,
                                             dps.Margins.Top,
                                             pageBounds.Width - (dps.Margins.Left + dps.Margins.Right),
                                             pageBounds.Height - (dps.Margins.Top + dps.Margins.Bottom));

      marginBounds.X = marginBounds.X - (int)dps.HardMarginX;
      marginBounds.Y = marginBounds.Y - (int)dps.HardMarginY;

      result.X = (int)Math.Ceiling(marginBounds.X * 1 / pageScale);
      result.Y = (int)Math.Ceiling(marginBounds.Y * 1 / pageScale);
      result.Width = (int)Math.Ceiling(marginBounds.Width * 1 / pageScale);
      result.Height = (int)Math.Ceiling(marginBounds.Height * 1 / pageScale);

      return result;
    }

    internal Rectangle GetPageBounds()
    {
      Rectangle pageBounds;
      PaperSize size = DefaultPageSettings.PaperSize;
      if (DefaultPageSettings.Landscape)
        pageBounds = new Rectangle(0, 0, size.Height, size.Width);
      else
        pageBounds = new Rectangle(0, 0, size.Width, size.Height);

      return pageBounds;
    }

    protected virtual void CalcScale()
    {
      if (Scaling.ScalingMode == ScalingMode.AdjustToScale)
        pageScale = (float)Scaling.Scale / 100;
      else
        pageScale = CalcScaleForFitToPages();
    }

    protected virtual float CalcScaleForFitToPages()
    {
      return 1;
    }

    protected override void OnPrintPage(PrintPageEventArgs e)
    {
      InitMacroValues();
      PrintPageHeader(e);
      PrintPageFooter(e);

      SetGraphicsScale(e);
      PrintPageContent(e);
    }

    protected virtual void PrintPageHeader(PrintPageEventArgs e)
    {
      Rectangle paintRect = e.MarginBounds;

      paintRect.Y = e.PageBounds.Top + (int)PageMargins.ToGraphicsUnits(PageMargins.Header, e.Graphics);
      paintRect.Height = e.PageBounds.Height;

      paintRect.Height = GetPageColontitleHeight(e, PageHeader, paintRect);

      paintRect.X = paintRect.X - (int)DefaultPageSettings.HardMarginX;
      paintRect.Y = paintRect.Y - (int)DefaultPageSettings.HardMarginY;

      PrintPageColontitle(e, PageHeader, paintRect, VerticalAlignment.Top);

      e.Graphics.DrawLine(Pens.Black,
                          new Point(paintRect.Left, paintRect.Bottom + 1),
                          new Point(paintRect.Right, paintRect.Bottom + 1));
    }

    protected virtual void PrintPageFooter(PrintPageEventArgs e)
    {
      Rectangle paintRect = e.MarginBounds;

      int footerHeight = GetPageColontitleHeight(e, PageFooter, paintRect);

      paintRect.Y = e.PageBounds.Bottom - (int)PageMargins.ToGraphicsUnits(PageMargins.Footer, e.Graphics) - footerHeight;
      paintRect.Height = footerHeight;

      paintRect.X = paintRect.X - (int)DefaultPageSettings.HardMarginX;
      paintRect.Y = paintRect.Y - (int)DefaultPageSettings.HardMarginY;

      e.Graphics.DrawLine(Pens.Black,
                          new Point(paintRect.Left, paintRect.Top),
                          new Point(paintRect.Right, paintRect.Top));

      PrintPageColontitle(e, PageFooter, paintRect, VerticalAlignment.Top);
    }

    protected virtual void PrintPageColontitle(PrintPageEventArgs e, PageColontitle colontitle, Rectangle rect, VerticalAlignment vertAlign)
    {
      string text;

      text = ExtractMacrosInRtf(colontitle.LeftTextAsRtf);
      rtfPrintSerivce.Print(text, 0, -1, e, rect);

      text = ExtractMacrosInRtf(colontitle.CenterTextAsRtf);
      rtfPrintSerivce.Print(text, 0, -1, e, rect);

      text = ExtractMacrosInRtf(colontitle.RightTextAsRtf);
      rtfPrintSerivce.Print(text, 0, -1, e, rect);
    }

    protected virtual int GetPageColontitleHeight(PrintPageEventArgs e, PageColontitle colontitle, Rectangle rect)
    {
      string text;

      int defHeight = (int)e.Graphics.MeasureString("", new Font("Microsoft Sans Serif", 8.25F)).Height;

      text = ExtractMacrosInRtf(colontitle.LeftTextAsRtf);
      int height1;
      if (String.IsNullOrEmpty(text))
        height1 = 0;
      else
        rtfPrintSerivce.Measure(text, 0, -1, e, rect, out height1);

      text = ExtractMacrosInRtf(colontitle.CenterTextAsRtf);
      int height2;
      if (String.IsNullOrEmpty(text))
        height2 = 0;
      else
        rtfPrintSerivce.Measure(text, 0, -1, e, rect, out height2);

      text = ExtractMacrosInRtf(colontitle.RightTextAsRtf);
      int height3;
      if (String.IsNullOrEmpty(text))
        height3 = 0;
      else
        rtfPrintSerivce.Measure(text, 0, -1, e, rect, out height3);

      return Math.Max(defHeight, Math.Max(Math.Max(height1, height2), height3));
    }

    protected virtual void InitMacroValues()
    {
      _macroValues[0] = PageNo.ToString();
      _macroValues[1] = DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern); //[ShortDate]
      _macroValues[2] = DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern); //[Date]
      _macroValues[3] = DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern); //[LongDate] 
      _macroValues[4] = DateTime.Now.ToString(CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern); //[Time] 
      _macroValues[5] = PageCount.ToString(); //[Pages]
    }

    protected virtual string ExtractMacros(string s)
    {
      int  i;
      string result = s;

      if (string.IsNullOrEmpty(result)) return "";

      for (i = 0;  i < ColontileMacros.Length; i++)
        result = SubstituteTextVar(result, ColontileMacros[i], _macroValues[i]);

      return result;
    }

    protected virtual string ExtractMacrosInRtf(string richText)
    {
      if (String.IsNullOrEmpty(richText)) return null;

      toolRichTextBox.Rtf = richText;
      //toolRichTextBox.Text = richText;

      for (int i = 0; i < ColontileMacros.Length; i++)
      {
        int pos;
        do
        {
          pos = toolRichTextBox.Find(ColontileMacros[i]);
          if (pos >= 0)
          {
            toolRichTextBox.Select(pos, ColontileMacros[i].Length);
            if (!String.IsNullOrEmpty(toolRichTextBox.SelectedText))
              toolRichTextBox.SelectedText = _macroValues[i];
          }
        } while (pos >= 0);
      }

      return toolRichTextBox.Rtf;
    }

    protected virtual string SubstituteTextVar(string s, string v1, string v2)
    {
      return s.Replace(v1, v2);
    }

    protected virtual void SetGraphicsScale(PrintPageEventArgs e)
    {
      e.Graphics.PageScale = pageScale; 
    }

    protected virtual void PrintPageContent(PrintPageEventArgs e)
    {

    }

    public void PrintLine(Color colorPen, Point startPos, Point finishPos, Graphics graphics)
    {
      Rectangle lineRect;
      if (startPos.Y == finishPos.Y)
      {
        lineRect = new Rectangle(startPos.X, startPos.Y, finishPos.X - startPos.X + 1, 1);
        using (SolidBrush brush = new SolidBrush(colorPen))
        {
          graphics.FillRectangle(brush, lineRect);
        }
      }
      else if (startPos.X == finishPos.X)
      {
        lineRect = new Rectangle(startPos.X, startPos.Y, 1, finishPos.Y - startPos.Y + 1);
        using (SolidBrush brush = new SolidBrush(colorPen))
        {
          graphics.FillRectangle(brush, lineRect);
        }
      }
      else
      {
        using (Pen pen = new Pen(colorPen))
        {
          graphics.DrawLine(pen, startPos, finishPos);
        }
      }
    }
    #endregion internal methods

  }

  public class BasePrintServiceComponentConverter : ComponentConverter
  {

    public BasePrintServiceComponentConverter(Type type) : base(type)
    {
    }

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
    {
      return false;
    }
  }

}
