using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace PrintListView
{
    public static class ListViewExtensions
    {
        // Make the ListView's column headers.
        // The ParamArray entries should alternate between
        // strings and HorizontalAlignment values.
        public static void MakeColumnHeaders(this ListView lvw, params object[] headerInfo)
        {
            // Remove any existing headers.
            lvw.Columns.Clear();

            // Make the column headers.
            for (int i = headerInfo.GetLowerBound(0);
                i <= headerInfo.GetUpperBound(0);
                i += 3)
            {
                lvw.Columns.Add(
                    (string)headerInfo[i],
                    (int)headerInfo[i + 1],
                    (HorizontalAlignment)headerInfo[i + 2]);
            }
        }

        // Add a row to the ListView.
        public static ListViewItem AddRow(this ListView lvw, int imageIndex,
            params string[] values)
        {
            // Make the item.
            ListViewItem newItem = new ListViewItem(values, imageIndex);
            lvw.Items.Add(newItem);
            return newItem;
        }

        // Add a row to a group in the ListView.
        public static ListViewItem AddRow(this ListView lvw, ListViewGroup group,
            int imageIndex, params string[] values)
        {
            ListViewItem newItem = lvw.AddRow(imageIndex, values);
            newItem.Group = group;
            return newItem;
        }

        // Print the ListView's data at the indicated location
        // assuming everything will fit within the column widths.
        public static void PrintData(this ListView lvw, Point location, Graphics gr,
            Brush headerBrush, Brush dataBrush, Pen gridPen)
        {
            const int xmargin = 5;
            const int ymargin = 3;
            float x = location.X;
            float y = location.Y;

            // See how tall rows should be.
            SizeF rowSize = gr.MeasureString(lvw.Columns[0].Text, lvw.Font);
            int rowHeight = (int)rowSize.Height + 2 * ymargin;

            // Get the column widths in printer units.
            float[] colWids = lvw.ColumnWidths(gr, 4 * xmargin);

            // Print.
            int numColumns = lvw.Columns.Count;
            using (StringFormat sf = new StringFormat())
            {
                // Draw the column headers.
                sf.Alignment = StringAlignment.Center;
                sf.LineAlignment = StringAlignment.Center;
                for (int i = 0; i < numColumns; i++)
                {
                    RectangleF rect = new RectangleF(x, y, colWids[i], rowHeight);
                    gr.DrawString(lvw.Columns[i].Text, lvw.Font, headerBrush, rect, sf);
                    gr.DrawRectangle(gridPen, rect);
                    x += colWids[i];
                }
                y += rowHeight;

                // Draw the data.
                foreach (ListViewItem item in lvw.Items)
                {
                    x = location.X;
                    for (int i = 0; i < numColumns; i++)
                    {
                        RectangleF rect = new RectangleF(
                            x + xmargin, y, colWids[i] - 2 * xmargin, rowHeight);

                        switch (lvw.Columns[i].TextAlign)
                        {
                            case HorizontalAlignment.Left:
                                sf.Alignment = StringAlignment.Near;
                                break;
                            case HorizontalAlignment.Center:
                                sf.Alignment = StringAlignment.Center;
                                break;
                            case HorizontalAlignment.Right:
                                sf.Alignment = StringAlignment.Far;
                                break;
                        }

                        gr.DrawString(item.SubItems[i].Text, lvw.Font,
                            headerBrush, rect, sf);
                        rect = new RectangleF(x, y, colWids[i], rowHeight);
                        gr.DrawRectangle(gridPen, rect);
                        x += colWids[i];
                    }
                    y += rowHeight;
                }
            }
        }

        // Return necessary column widths.
        public static float[] ColumnWidths(this ListView lvw, Graphics gr, int margin)
        {
            int numColumns = lvw.Columns.Count;
            float[] widths = new float[numColumns];
            for (int i = 0; i < numColumns; i++)
            {
                // Allow room for the header.
                SizeF size = gr.MeasureString(lvw.Columns[i].Text, lvw.Font);
                widths[i] = size.Width;

                // Allow room for the data.
                foreach (ListViewItem item in lvw.Items)
                {
                    size = gr.MeasureString(item.SubItems[i].Text, lvw.Font);
                    if (widths[i] < size.Width) widths[i] = size.Width;
                }
                widths[i] += margin;
            }
            return widths;
        }

        // Draw a RectangleF.
        public static void DrawRectangle(this Graphics gr, Pen pen, RectangleF rectf)
        {
            gr.DrawRectangle(pen, rectf.Left, rectf.Top, rectf.Width, rectf.Height);
        }
    }
}
