/*
Copyright Danal Technology Inc.
For tutorial purposes only, not for production use
*/

using System;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace Danal.Layout
{
    internal class ManageSizeChanges
    {
        Form form;
        UserControl userControl;
        public ManageSizeChanges(Form form, UserControl userControl)
        {
            this.form = form;
            this.userControl = userControl;
        }
        internal void SizeChanged(Object sender, EventArgs args)
        {
            userControl.Size = form.Size;
        }
    }
    [AttributeUsage(AttributeTargets.Class)]
    public class UseAutoLayoutAttribute : Attribute
    {
        public static Form HostInForm(UserControl userControl)
        {
            return HostInForm(userControl, null);
        }
        public static Form HostInForm(UserControl userControl, Form form)
        {
            if (null == form)
            {
                form = new Form();
            }
            form.Controls.Add(userControl);
            ManageSizeChanges manageSizeChanges =
                new ManageSizeChanges(form, userControl);
            form.SizeChanged += new EventHandler(manageSizeChanges.SizeChanged);
            form.ClientSize = userControl.Size;
            manageSizeChanges.SizeChanged(null, EventArgs.Empty);
            return form;
        }
    }
    [AttributeUsage(AttributeTargets.Class)]
    public class LayoutSpacing : Attribute
    {
        Int32 horizontalSpacing;
        Int32 verticalSpacing;
        public LayoutSpacing(Int32 horizontalSpacing, Int32 verticalSpacing)
        {
            this.horizontalSpacing = horizontalSpacing;
            this.verticalSpacing = verticalSpacing;
        }
        public LayoutSpacing(Int32 spacing)
            : this(spacing, spacing)
        {
        }
        public LayoutSpacing()
            : this(3)
        {
        }
        public Int32 HorizontalSpacing
        {
            get { return horizontalSpacing; }
        }
        public Int32 VerticalSpacing
        {
            get { return verticalSpacing; }
        }

    }
    [AttributeUsage(AttributeTargets.Field)]
    public class LayoutItem : Attribute
    {
        public const string LayoutFont = "Danal.LayoutItem.Font";
        Byte row;
        Byte column;
        Int32 emWidth;
        Int32 lines;
        Byte order;
        String text;

        Int32 encodedPosition
        {
            get { return row + (column << 8) + (order << 16); }
        }
        static void DecodePosition(Int32 position,
                                            ref Int32 row,
                                            ref Int32 column,
                                            ref Int32 order)
        {
            row = position & 0XFF;
            column = (position >> 8) & 0xFF;
            order = (position >> 16) & 0xFF;
        }
        public String Text
        {
            get { return text; }
            set { text = value; }
        }
        public Int32 EmWidth
        {
            get { return emWidth; }
            set { emWidth = value; }
        }
        public Int32 Lines
        {
            get { return lines; }
            set { lines = value; }
        }
        public LayoutItem(Byte row)
            : this(row, 1)
        {
        }
        public LayoutItem(Byte row, Byte column)
            : this(row, column, 1)
        {
        }
        public LayoutItem(Byte row, Byte column, Byte order)
            : this()
        {
            this.row = row;
            this.column = column;
            this.order = order;
        }
        LayoutItem()
        {
        }
        // sets positions of each child of control, and resizes appropiratly
        // returns same control, but with everything layed out according to attribtues
        static public UserControl DoLayout(UserControl userControl)
        {
            if ((userControl.GetType().GetCustomAttributes
                (typeof(UseAutoLayoutAttribute), true)).Length == 0)
            {
                return userControl;
            }
            // remove any existing child control, the will be replaced
            // by attributed fields
            userControl.Controls.Clear();
            Dictionary<Int32, FieldInfo> controls
                    = new Dictionary<Int32, FieldInfo>();
            Int32 maxColumn = -1;
            Int32 maxRow = -1;
            Int32 maxOrder = 0;
            // find out how many fields have attributes
            using (Graphics graphics = userControl.CreateGraphics())
            //Graphics graphics = null;
            {
                foreach (FieldInfo fieldInfo in
                          userControl.GetType().GetFields
                          (BindingFlags.Instance | BindingFlags.NonPublic))
                {
                    if (fieldInfo.FieldType.IsSubclassOf(typeof(Control)))
                    {
                        LayoutItem[] layoutItems
                                = (LayoutItem[])
                                  fieldInfo.GetCustomAttributes(typeof(LayoutItem), true);

                        if (layoutItems.Length > 0)
                        {

                            Int32 row = layoutItems[0].row;
                            Int32 column = layoutItems[0].column;
                            Int32 order = layoutItems[0].order;
                            maxRow = row > maxRow ? row : maxRow;
                            maxColumn = column > maxColumn ? column : maxColumn;
                            maxOrder = order > maxOrder ? order : maxOrder;
                            controls.Add(layoutItems[0].encodedPosition, fieldInfo);
                            String widthString = String.Empty;
                            Control currentControl = ((Control)(fieldInfo.GetValue(userControl)));
                            if (currentControl == null)
                            {
                                // create one
                                ConstructorInfo ci = fieldInfo.FieldType.GetConstructor(new Type[0]);
                                currentControl = (Control)ci.Invoke(null);
                                fieldInfo.SetValue(userControl, currentControl);
                            }
                            currentControl.Name = fieldInfo.Name;
                            Font useFont = (Font)System.Runtime.Remoting.Messaging.CallContext.GetData(
                                LayoutItem.LayoutFont);
                            if (useFont != null)
                            {
                                currentControl.Font = useFont;
                            }

                            if (layoutItems[0].Text != null)
                            {
                                widthString = layoutItems[0].Text;
                                // use auto size if avaiable
                                currentControl.Width = 0;
                                currentControl.AutoSize = true;
                                currentControl.Text = widthString;
                            }
                            userControl.Controls.Add(currentControl);
                            // EmWidth takes precdedence over actual text width
                            if (layoutItems[0].EmWidth > 0)
                            {
                                widthString = new String('M', layoutItems[0].EmWidth);

                            }
                            if (widthString != String.Empty)
                            {
                                int width = graphics.MeasureString(widthString, currentControl.Font).ToSize().Width;
                                currentControl.ClientSize = new Size(width, currentControl.ClientSize.Height);

                            }
                            if (layoutItems[0].Lines > 0)
                            {
                                currentControl.AutoSize = false;
                                currentControl.Height
                                        = userControl.Font.Height * layoutItems[0].Lines;
                            }
                        }
                    }
                }
            }
            if (controls.Count > 0)
            {
                Int32 horizontalSpacing = 3;
                Int32 verticalSpacing = 3;
                LayoutSpacing[] layoutSpacings
                        = (LayoutSpacing[])
                          userControl.GetType().GetCustomAttributes
                          (typeof(LayoutSpacing), true);
                if (layoutSpacings.Length > 0)
                {
                    horizontalSpacing = layoutSpacings[0].HorizontalSpacing;
                    verticalSpacing = layoutSpacings[0].VerticalSpacing;
                }

                Int32[, ,] cellWidths = new Int32[maxRow, maxColumn, maxOrder];
                Int32[, ,] cellHeights = new Int32[maxRow, maxColumn, maxOrder];
                //userControl.Dock = DockStyle.Fill;
                Int32[] rowMaxHeights = new Int32[maxRow];
                Int32[] rowWidths = new Int32[maxRow];
                Int32[] columnMaxWidths = new Int32[maxColumn];
                // figure out all the cell widths and heights
                foreach (KeyValuePair<Int32, FieldInfo> keyValuePair in controls)
                {
                    Int32 position = (Int32)keyValuePair.Key;
                    Int32 rowIndex = 0;
                    Int32 columnIndex = 0;
                    Int32 orderIndex = 0;
                    DecodePosition(position, ref rowIndex, ref columnIndex, ref orderIndex);
                    rowIndex--;
                    columnIndex--;
                    orderIndex--;
                    FieldInfo fieldInfo = (FieldInfo)keyValuePair.Value;
                    Control control = (Control)fieldInfo.GetValue(userControl);
                    cellWidths[rowIndex, columnIndex, orderIndex] = control.Width;
                    cellHeights[rowIndex, columnIndex, orderIndex] = control.Height;
                }
                // figure out all the max cell widths and heights
                for (Int32 rowIndex = 0; rowIndex < maxRow; rowIndex++)
                {
                    for (Int32 columnIndex = 0; columnIndex < maxColumn; columnIndex++)
                    {
                        Int32 maxWidth = 0;
                        Int32 lastOrder = 0;
                        for (Int32 orderIndex = 0; orderIndex < maxOrder; orderIndex++)
                        {
                            Int32 cellWidth = cellWidths[rowIndex, columnIndex, orderIndex];
                            if (cellWidth > 0)
                            {
                                maxWidth += cellWidth;
                                lastOrder = orderIndex;
                            }
                        }
                        maxWidth += lastOrder * horizontalSpacing;
                        columnMaxWidths[columnIndex]
                                = maxWidth > columnMaxWidths[columnIndex]
                                  ? maxWidth : columnMaxWidths[columnIndex];
                    }
                }
                Int32[, ,] cellLefts = new Int32[maxRow, maxColumn, maxOrder];
                // figure out the position of left side of each column
                for (Int32 rowIndex = 0; rowIndex < maxRow; rowIndex++)
                {
                    Int32 columnStart = horizontalSpacing;
                    for (Int32 columnIndex = 0; columnIndex < maxColumn; columnIndex++)
                    {
                        Int32 nextLeft = columnStart;
                        for (Int32 orderIndex = 0; orderIndex < maxOrder; orderIndex++)
                        {
                            Int32 cellHeight = cellHeights[rowIndex, columnIndex, orderIndex];
                            Int32 cellWidth = cellWidths[rowIndex, columnIndex, orderIndex];
                            rowMaxHeights[rowIndex]
                                    = cellHeight > rowMaxHeights[rowIndex]
                                        ? cellHeight : rowMaxHeights[rowIndex];
                            cellLefts[rowIndex, columnIndex, orderIndex]
                                    = nextLeft;
                            nextLeft += cellWidth + horizontalSpacing;
                        }
                        columnStart += columnMaxWidths[columnIndex] + horizontalSpacing;
                    }
                }
                Int32[] rowTops = new Int32[maxRow];
                Int32 lastTop = 0;
                for (int index = 0; index < maxRow; index++)
                {
                    lastTop += verticalSpacing;
                    rowTops[index] = lastTop;
                    lastTop += rowMaxHeights[index];
                }
                Int32 lastLeft = 0;
                for (int index = 0; index < maxColumn; index++)
                {
                    lastLeft += horizontalSpacing;
                    //columnLefts[index] = lastLeft;
                    lastLeft += columnMaxWidths[index];
                }
                userControl.ClientSize
                        = new Size(lastLeft + horizontalSpacing, lastTop + verticalSpacing);
                // now go through each control and repositon and resize it.
                foreach (KeyValuePair<Int32, FieldInfo> keyValuePair in controls)
                {
                    Int32 position = (Int32)keyValuePair.Key;
                    Int32 rowIndex = 0;
                    Int32 columnIndex = 0;
                    Int32 orderIndex = 0;
                    DecodePosition(position, ref rowIndex, ref columnIndex, ref orderIndex);
                    rowIndex--;
                    columnIndex--;
                    orderIndex--;
                    FieldInfo fieldInfo = (FieldInfo)keyValuePair.Value;
                    Control control = (Control)fieldInfo.GetValue(userControl);
                    control.Location
                            = new Point(cellLefts[rowIndex, columnIndex, orderIndex],
                                            rowTops[rowIndex]);
                    // add this as a child of the user control
                    //userControl.Controls.Add(control);
                }
                DialogExtent.SetBottom(userControl, verticalSpacing);
            }
            // is bottom control specified?
            return userControl;
        }
    }



}

