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

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Printing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

//DataGridPrintService
//PrintDataGrid

//TBasePrintServiceComponentEh

//    property ColorSchema: TPrintColotSchemaEh read FColorSchema write FColorSchema default pcsAdaptedColorEh;
//       TPrintColotSchemaEh = (pcsFullColorEh, pcsAdaptedColorEh, pcsBlackAndWhiteEh);

// end;


namespace EhLib
{
  class GridPageColumnData
  {
    public int FromColIndex;
    public int ToColIndex;
  }

  class GridPageRowData
  {
    public int FromRow;
    public int ToRow;
  }

  public class PrintServiceEventArgs
  {
    private readonly BasePrintServiceComponent printService;
    private readonly PrintPageEventArgs printPageEventArgs;

    public PrintServiceEventArgs(BasePrintServiceComponent printService, PrintPageEventArgs printPageEventArgs)
    {
      this.printService = printService;
      this.printPageEventArgs = printPageEventArgs;
    }

    public PrintPageEventArgs PrintPageEventArgs { get { return printPageEventArgs; } }
    public Graphics Graphics { get { return PrintPageEventArgs.Graphics; } }
    public bool BlackAndWhite { get; internal set; }
    public float XScale { get; internal set; }
    public float YScale { get; internal set; }

    public void PrintLine(Color colorPen, Point startPos, Point finishPos)
    {
      printService.PrintLine(colorPen, startPos, finishPos, Graphics);
    }

    public void PrintText(string text, Font font, Rectangle bounds,
      Color foreColor, HorizontalAlignment horzAlign, VerticalAlignment vertAlign, TextFormatFlagsEh flags
      )
    {
      Font printFont = font;
      Font scaledFont = null;
      if (Graphics.PageScale != 1)
      {
        scaledFont = new Font(font.FontFamily, font.Size * Graphics.PageScale, font.Style);
        printFont = scaledFont;
      }

      EhLibUtilsInternal.DrawText(Graphics, text, printFont, bounds, foreColor, horzAlign, vertAlign, flags, UsedGraphicEngine.WinFormsGDIPlus);

      if (scaledFont != null)
        scaledFont.Dispose();
    }
  }

  public class DataGridPrintService : BasePrintServiceComponent
  {
    private readonly List<GridPageColumnData> gridPageColumnDataList = new List<GridPageColumnData>();
    private readonly List<GridPageRowData> gridPageRowDataList = new List<GridPageRowData>();
    private DataGridEh grid;
    private int pageDataWidth;
    private int pageDataHeight;
    private int pageTitleHeight;
    private int pageColDataListIndex;
    private int pageRowDataListIndex;

    private readonly List<int> gridColWidths = new List<int>();
    private readonly List<int> gridRowHeights = new List<int>();

    public void PrintPreview(DataGridEh grid)
    {
      this.grid = grid;
      try
      {
        base.PrintPreview();
      }
      finally
      {
        this.grid = null;
      }
    }

    public void Print(DataGridEh grid)
    {
      this.grid = grid;
      try
      {
        base.Print();
      }
      finally
      {
        this.grid = null;
      }
    }

    protected override void OnBeginPrint(PrintEventArgs e)
    {
      gridColWidths.Clear();
      foreach (DataGridColumn c in grid.VisibleColumns)
        gridColWidths.Add(c.Width + grid.LineOptions.VertLineSpace);

      gridRowHeights.Clear();
      gridRowHeights.Add(grid.Title.Height);

      foreach (DataGridRow r in grid.VisibleRows)
        gridRowHeights.Add(r.Height + grid.LineOptions.HorzLineSpace);

      base.OnBeginPrint(e);

      CalcPagesData();
      InitMacroValues();

      pageColDataListIndex = 0;
      pageRowDataListIndex = 0;
    }

    protected virtual void CalcPagesData()
    {
      int i;
      int incWidth;
      int incFromCol;
      int incHeight;
      int incFromRow;
      GridPageColumnData pcd;
      GridPageRowData prd;
      int pageDataRowsHeight;

      gridPageColumnDataList.Clear();
      gridPageRowDataList.Clear();

      pageDataWidth = PageContentBounds.Width;
      incWidth = 0;
      incFromCol = 0;

      for (i = 0; i < grid.VisibleColumns.Count; i++)
      {
        incWidth = incWidth + grid.VisibleColumns[i].Width + grid.LineOptions.VertLineSpace;
        if (incWidth > pageDataWidth && i > incFromCol)
        {
          pcd = new GridPageColumnData
          {
            FromColIndex = incFromCol,
            ToColIndex = i - 1
          };
          gridPageColumnDataList.Add(pcd);

          incFromCol = i;
          incWidth = grid.VisibleColumns[i].Width + grid.LineOptions.VertLineSpace;
        }
      }

      pageTitleHeight = grid.Title.Height;

      pcd = new GridPageColumnData
      {
        FromColIndex = incFromCol,
        ToColIndex = grid.VisibleColumns.Count - 1
      };
      gridPageColumnDataList.Add(pcd);

      pageDataHeight = PageContentBounds.Height;
      pageDataRowsHeight = pageDataHeight - pageTitleHeight;
      incHeight = 0;
      incFromRow = 0;
      for (i = 0; i < grid.VisibleRows.Count + grid.Footer.Rows.Count; i++)
      {
        if (incHeight > pageDataRowsHeight)
        {
          prd = new GridPageRowData
          {
            FromRow = incFromRow,
            ToRow = i - 1
          };
          incFromRow = i;
          incHeight = 0;
          gridPageRowDataList.Add(prd);
        }
        if (i < grid.VisibleRows.Count)
          incHeight = incHeight + grid.VisibleRows[i].Height;
        else
          incHeight = incHeight + grid.Footer.Rows[i - grid.VisibleRows.Count].Height;
      }

      prd = new GridPageRowData
      {
        FromRow = incFromRow,
        ToRow = grid.VisibleRows.Count + grid.Footer.Rows.Count - 1
      };
      gridPageRowDataList.Add(prd);

      PageCount = gridPageColumnDataList.Count * gridPageRowDataList.Count;
    }

    protected override float CalcScaleForFitToPages()
    {

      float result;
      long fullPagesTall, fullGridTall;
      long fullPagesWide, fullGridWide;
      int onePageTall, oneScaledPageTall;
      int onePageWide, oneScaledPageWide;
      int i;
      int scaleForTall, scaleForWide;
      int curExtand, curPages;

      Rectangle pgContentBounds = GetPageContentBoundsForScale(1);

      //-ScaleForTall
      fullPagesTall = 0;
      onePageTall = pgContentBounds.Height;
      for (i = 0; i < Scaling.FitToPagesTall; i++)
        fullPagesTall = fullPagesTall + onePageTall;

      fullGridTall = 0;
      for (i = 0; i < gridRowHeights.Count; i++)
        fullGridTall = fullGridTall + gridRowHeights[i];

      //FullGridTall = FullGridTall + FBeforeGridFullHeight + FAfterGridFullHeight;

      scaleForTall = (int)Math.Truncate((float)fullPagesTall / fullGridTall * 100);
      if (scaleForTall > 100)
        scaleForTall = 100;

      oneScaledPageTall = (int)Math.Truncate((float)onePageTall * 100 / scaleForTall);

      while (true)
      {
        curExtand = 0;
        curPages = 1;

        for (i = 0; i < gridRowHeights.Count; i++)
        {
          curExtand = curExtand + gridRowHeights[i];
          if (curExtand > oneScaledPageTall)
          {
            curPages = curPages + 1;
            curExtand = gridRowHeights[i];
          }
        }

        if (curPages > Scaling.FitToPagesTall)
        {
          scaleForTall = scaleForTall - 1;
          if (scaleForTall < 0)
          {
            scaleForTall = 1;
            break;
          }
          oneScaledPageTall = (int)Math.Truncate((float)onePageTall * 100 / scaleForTall);
        }
        else
          break;
      }

      //-ScaleForWide
      fullPagesWide = 0;
      onePageWide = pgContentBounds.Width;
      for (i = 0; i < Scaling.FitToPagesWide; i++)
        fullPagesWide = fullPagesWide + onePageWide;

      fullGridWide = 0;
      for (i = 0; i < gridColWidths.Count; i++)
        fullGridWide = fullGridWide + gridColWidths[i];

      scaleForWide = (int)Math.Truncate((float)fullPagesWide / fullGridWide * 100);
      if (scaleForWide > 100)
        scaleForWide = 100;

      oneScaledPageWide = (int)Math.Truncate((float)onePageWide * 100 / scaleForWide);

      while (true)
      {
        curExtand = 0;
        curPages = 1;

        for (i = 0; i < gridColWidths.Count; i++)
        {
          curExtand = curExtand + gridColWidths[i];
          if (curExtand > oneScaledPageWide)
          {
            curPages = curPages + 1;
            curExtand = gridColWidths[i];
          }
        }
        if (curPages > Scaling.FitToPagesWide)
        {
          scaleForWide = scaleForWide - 1;
          if (scaleForWide < 0)
          {
            scaleForWide = 1;
            break;
          }
          oneScaledPageWide = (int)Math.Truncate((float)onePageWide * 100 / scaleForWide);
        } else
          break;
      }

      if (scaleForWide < scaleForTall)
        result = (float)scaleForWide / 100;
      else
        result = (float)scaleForTall / 100;

      return result;
    }

    protected override void PrintPageContent(PrintPageEventArgs e)
    {
      PrintServiceEventArgs pe = new PrintServiceEventArgs(this, e);

      PrintPageCells(pe);
                                           
      SetNextPageData(e);
    }

    protected bool SetNextPageData(PrintPageEventArgs e)
    {
      bool result;

      if (pageColDataListIndex < gridPageColumnDataList.Count - 1)
      {
        pageColDataListIndex = pageColDataListIndex + 1;
        result = true;
      }
      else if (pageRowDataListIndex < gridPageRowDataList.Count - 1)
      {
        pageRowDataListIndex = pageRowDataListIndex + 1;
        pageColDataListIndex = 0;
        result = true;
      }
      else
      {
        pageColDataListIndex = gridPageColumnDataList.Count;
        pageRowDataListIndex = gridPageRowDataList.Count;
        result = false;
      }

      if (result)
        SetToNextPage(e);

      return result;
    }

    private void PrintPageCells(PrintServiceEventArgs e)
    {
      int fromRow, toRow;
      Rectangle rowRect;

      fromRow = gridPageRowDataList[pageRowDataListIndex].FromRow;
      toRow = gridPageRowDataList[pageRowDataListIndex].ToRow;

      rowRect = PageContentBounds;

      PrintTopLine(rowRect, e);
      PrintTitle(rowRect, e);

      rowRect.Y = rowRect.Y + pageTitleHeight;
      rowRect.Height = rowRect.Height - pageTitleHeight;

      for (int r = fromRow; r <= toRow; r++)
      {
        if (r < grid.VisibleRows.Count)
        {
          DataGridRow dataRow = grid.VisibleRows[r];
          rowRect.Height = dataRow.Height;
          PrintDataRow(r, dataRow, rowRect, e);
          rowRect.Offset(0, rowRect.Height);
        }
        else
        {
          DataGridFooterRow footerRow = grid.Footer.Rows[r - grid.VisibleRows.Count];
          rowRect.Height = footerRow.Height;
          PrintFooterDataRow(r - grid.VisibleRows.Count, footerRow, rowRect, e);
          rowRect.Offset(0, rowRect.Height);
        }
      }
    }

    private void PrintTitle(Rectangle rowRect, PrintServiceEventArgs e)
    {
      int fromCol, toCol;

      fromCol = gridPageColumnDataList[pageColDataListIndex].FromColIndex;
      toCol = gridPageColumnDataList[pageColDataListIndex].ToColIndex;

      if (grid.Title.MultiTitle.Active)
        PrintMultiTitleRow(rowRect, e, fromCol, toCol);
      else
        PrintSimpleTitleRow(rowRect, e, fromCol, toCol);

      rowRect.Y = rowRect.Y + grid.Title.Height;
    }

    private void PrintSimpleTitleRow(Rectangle rowRect, PrintServiceEventArgs e, int fromCol, int toCol)
    {
      Rectangle cellAreaRect;

      cellAreaRect = rowRect;
      cellAreaRect.Width = grid.VisibleColumns[fromCol].Width + grid.LineOptions.VertLineSpace;
      cellAreaRect.Height = grid.Title.Height;

      PrintLine(Color.Black, new Point(cellAreaRect.Left - 1, cellAreaRect.Top), new Point(cellAreaRect.Left - 1, cellAreaRect.Bottom - 1), e.Graphics);

      for (int c = fromCol; c <= toCol; c++)
      {
        cellAreaRect.Width = grid.VisibleColumns[c].Width + grid.LineOptions.VertLineSpace;
        PrintTitleCellArea(c, grid.VisibleColumns[c], cellAreaRect, e);
        cellAreaRect.Offset(cellAreaRect.Width, 0);
      }
    }

    protected internal virtual void PrintTitleTree(DataGridTitleNode node,
      PrintServiceEventArgs e, Rectangle rowRect)
    {
      node.Print(e, rowRect);
      for (int i = 0; i < node.VisibleItems.Count; i++)
      {
        PrintTitleTree(node.VisibleItems[i], e, rowRect);
      }
    }

    private void PrintMultiTitleRow(Rectangle rowRect, PrintServiceEventArgs e, int fromCol, int toCol)
    {
      Rectangle cellAreaRect;
      Rectangle printRowRect;
      int backShift = 0;
      int colsWidth = 0;

      cellAreaRect = rowRect;
      cellAreaRect.Width = grid.VisibleColumns[fromCol].Width + grid.LineOptions.VertLineSpace;
      cellAreaRect.Height = grid.Title.Height;

      PrintLine(Color.Black, new Point(cellAreaRect.Left - 1, cellAreaRect.Top), new Point(cellAreaRect.Left - 1, cellAreaRect.Bottom - 1), e.Graphics);

      printRowRect = rowRect;
      for (int c = 0; c < fromCol; c++)
      {
        backShift = backShift + grid.VisibleColumns[c].Width + grid.LineOptions.VertLineSpace;
      }
      for (int c = fromCol; c <= toCol; c++)
      {
        colsWidth = colsWidth + grid.VisibleColumns[c].Width + grid.LineOptions.VertLineSpace;
      }

      Rectangle clipRect = rowRect;
      clipRect.Width = colsWidth;

      Region clientClip = e.Graphics.Clip;
      e.Graphics.SetClip(clipRect, CombineMode.Intersect);

      printRowRect.X = printRowRect.X - backShift;
      printRowRect.Width = printRowRect.Width + backShift;

      PrintTitleTree(grid.Title.MultiTitle.Root, e, printRowRect);

      e.Graphics.Clip = clientClip;
    }

    private void PrintDataRow(int rowIndex, DataGridRow row, Rectangle rowRect, PrintServiceEventArgs e)
    {
      int fromCol, toCol;
      Rectangle cellAreaRect;

      fromCol = gridPageColumnDataList[pageColDataListIndex].FromColIndex;
      toCol = gridPageColumnDataList[pageColDataListIndex].ToColIndex;

      cellAreaRect = rowRect;
      cellAreaRect.Width = grid.VisibleColumns[fromCol].Width + grid.LineOptions.VertLineSpace;

      PrintLine(Color.Black, new Point(cellAreaRect.Left-1, cellAreaRect.Top), new Point(cellAreaRect.Left-1, cellAreaRect.Bottom-1), e.Graphics);

      for (int c = fromCol; c <= toCol; c++)
      {
        cellAreaRect.Width = grid.VisibleColumns[c].Width + grid.LineOptions.VertLineSpace;
        PrintDataCellArea(c, grid.VisibleColumns[c], rowIndex, row, cellAreaRect, e);
        cellAreaRect.Offset(cellAreaRect.Width, 0);
      }
    }

    protected virtual void PrintDataCellArea(int colIndex, DataGridColumn col, int rowIndex, DataGridRow row, Rectangle cellAreaRect, PrintServiceEventArgs e)
    {
      int gridColIndex = grid.DataToRawCol(col.VisibleIndex);
      int gridRowIndex = grid.DataToRawRow(row.VisibleIndex);

      PrintBordersForCellArea(gridColIndex, gridRowIndex, ref cellAreaRect, e);
      PrintDataCell(colIndex, col, rowIndex, row, cellAreaRect, e);
    }

    protected virtual void PrintTitleCellArea(int colIndex, DataGridColumn col, Rectangle cellAreaRect, PrintServiceEventArgs e)
    {
      int gridColIndex = grid.DataToRawCol(col.VisibleIndex);
      int gridRowIndex = 0;

      PrintBordersForCellArea(gridColIndex, gridRowIndex, ref cellAreaRect, e);
      PrintTitleCell(colIndex, col, cellAreaRect, e);
    }

    public void PrintBordersForCellArea(int col, int row, ref Rectangle rect, PrintServiceEventArgs e)
    {
      bool rIsPaint;
      Color rColor;
      DashStyle rStyle;
      bool rIsExtent;
      grid.ProcessGetCellBorders(col, row, GridCellBorderType.Right, out rIsPaint, out rColor, out rStyle, out rIsExtent);

      bool bIsPaint;
      Color bColor;
      DashStyle bStyle;
      bool bIsExtent;
      grid.ProcessGetCellBorders(col, row, GridCellBorderType.Bottom, out bIsPaint, out bColor, out bStyle, out bIsExtent);

      rColor = Color.Black;
      bColor = Color.Black;

      PrintBordersForRect(ref rect, rIsPaint, bIsPaint, rColor, bColor, rIsExtent, bIsExtent, e);
    }

    protected virtual void PrintDataCell(int colIndex, DataGridColumn col, int rowIndex, DataGridRow row, Rectangle cellRect, PrintServiceEventArgs e)
    {
      int gridColIndex = grid.DataToRawCol(col.VisibleIndex);
      int gridRowIndex = grid.DataToRawRow(row.VisibleIndex);
      int areaColIndex;
      int areaRowIndex;

      BaseGridCellManager cell = (BaseGridCellManager)grid.CellByColRow(gridColIndex, gridRowIndex, out areaColIndex, out areaRowIndex);

      cell.GridCellPosToAreaCellPos(gridColIndex, gridRowIndex, out areaColIndex, out areaRowIndex);

      cell.PrintCell(e, cellRect, areaColIndex, areaRowIndex, null);
    }

    protected virtual void PrintTitleCell(int colIndex, DataGridColumn col, Rectangle cellRect, PrintServiceEventArgs e)
    {
      int gridColIndex = grid.DataToRawCol(col.VisibleIndex);
      int gridRowIndex = 0;
      int areaColIndex;
      int areaRowIndex;

      DataGridBaseCellMan cell = (DataGridBaseCellMan)grid.CellByColRow(gridColIndex, gridRowIndex);

      cell.GridCellPosToAreaCellPos(gridColIndex, gridRowIndex, out areaColIndex, out areaRowIndex);

      cell.PrintCell(e, cellRect, areaColIndex, areaRowIndex, e);
    }

    private void PrintFooterDataRow(int rowIndex, DataGridFooterRow footerRow, Rectangle rowRect, PrintServiceEventArgs e)
    {
      int fromCol, toCol;
      Rectangle cellAreaRect;

      fromCol = gridPageColumnDataList[pageColDataListIndex].FromColIndex;
      toCol = gridPageColumnDataList[pageColDataListIndex].ToColIndex;

      cellAreaRect = rowRect;
      cellAreaRect.Width = grid.VisibleColumns[fromCol].Width + grid.LineOptions.VertLineSpace;

      PrintLine(Color.Black, new Point(cellAreaRect.Left - 1, cellAreaRect.Top), new Point(cellAreaRect.Left - 1, cellAreaRect.Bottom - 1), e.Graphics);

      for (int c = fromCol; c <= toCol; c++)
      {
        cellAreaRect.Width = grid.VisibleColumns[c].Width + grid.LineOptions.VertLineSpace;
        PrintFooterCellArea(c, grid.VisibleColumns[c], rowIndex, footerRow, cellAreaRect, e);
        cellAreaRect.Offset(cellAreaRect.Width, 0);
      }
    }

    protected virtual void PrintFooterCellArea(int dataColIndex, DataGridColumn col, 
      int footerRowIndex, DataGridFooterRow footerRow, Rectangle cellAreaRect, PrintServiceEventArgs e)
    {
      int gridColIndex = grid.DataToRawCol(col.VisibleIndex);
      int gridRowIndex = footerRowIndex + grid.RowCount;

      PrintBordersForCellArea(gridColIndex, gridRowIndex, ref cellAreaRect, e);
      PrintFooterCell(gridColIndex, dataColIndex, col, gridRowIndex, footerRowIndex, footerRow, cellAreaRect, e);
    }

    protected virtual void PrintFooterCell(int gridColIndex, int dataColIndex, DataGridColumn col, 
      int gridRowIndex, int footerRowIndex, DataGridFooterRow footerRow, Rectangle cellRect, PrintServiceEventArgs e)
    {
      BaseGridCellManager cell = (BaseGridCellManager)grid.CellByColRow(gridColIndex, gridRowIndex);
      cell.PrintCell(e, cellRect, dataColIndex, footerRowIndex, e);
    }

    public void PrintBordersForRect(ref Rectangle rect, bool rIsDraw, bool bIsDraw,
      Color rBorderColor, Color bBorderColor, bool rIsExtent, bool bIsExtent, PrintServiceEventArgs e)
    {
      Rectangle rBorderRect, bBorderRect;
      if (rIsDraw && rIsExtent && bIsDraw && bIsExtent)
      {
        if (rBorderColor != bBorderColor)
          if (BaseGridControl.GetColorLuminance(rBorderColor) >
              BaseGridControl.GetColorLuminance(bBorderColor)
         )
            rIsExtent = false;
          else
            bIsExtent = false;
      }

      if (rIsDraw)
      {
        rBorderRect = rect;
        if (rIsExtent)
          rBorderRect.Height++;
        PrintLine(rBorderColor, new Point(rBorderRect.Right - 1, rBorderRect.Top), new Point(rBorderRect.Right - 1, rBorderRect.Bottom - 2), e.Graphics);
      }

      if (bIsDraw)
      {
        bBorderRect = rect;
        if (bIsExtent)
          bBorderRect.Width++;
        PrintLine(bBorderColor, new Point(bBorderRect.Left, bBorderRect.Bottom - 1), new Point(bBorderRect.Right - 2, bBorderRect.Bottom - 1), e.Graphics);
      }

      if (rIsDraw)
        rect.Width--;
      if (bIsDraw)
        rect.Height--;
    }

    public void PrintTopLine(Rectangle rowRect, PrintServiceEventArgs e)
    {
      int fromCol, toCol;
      Rectangle cellAreaRect;

      fromCol = gridPageColumnDataList[pageColDataListIndex].FromColIndex;
      toCol = gridPageColumnDataList[pageColDataListIndex].ToColIndex;

      cellAreaRect = rowRect;
      cellAreaRect.Y = cellAreaRect.Y - 1;
      cellAreaRect.Width = grid.VisibleColumns[fromCol].Width + grid.LineOptions.VertLineSpace;

      for (int c = fromCol; c <= toCol; c++)
      {
        cellAreaRect.Width = grid.VisibleColumns[c].Width + grid.LineOptions.VertLineSpace;
        PrintLine(Color.Black, cellAreaRect.Location, new Point(cellAreaRect.Right-1, cellAreaRect.Top), e.Graphics);
        cellAreaRect.Offset(cellAreaRect.Width, 0);
      }
    }

  }
}
