using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Xml;
namespace DevComponents.DotNetBar
{
    /// 
    /// Represents Dock container with either horizontal or vertical layout.
    /// 
    [DesignTimeVisible(false), ToolboxItem(false), TypeConverter(typeof(DocumentDockContainerConverter))]
    public class DocumentDockContainer : DocumentBaseContainer
    {
        #region Private Variables
        private bool _OversizeEnabled;
        private eOrientation m_Orientation = eOrientation.Horizontal;
        private DocumentBaseContainerCollection m_Documents = new DocumentBaseContainerCollection();
        private int m_SplitterSize = 3;
        private Size m_MinimumSize = Size.Empty;
        private bool m_RecordDocumentSize = false;
        #endregion
        #region Internal Implementation
        /// 
        /// Creates new instance of the object and initializes it with specified values.
        /// 
        /// Array of documents to host in this container.
        /// Container orientation
        public DocumentDockContainer(DocumentBaseContainer[] documents, eOrientation orientation)
        {
            m_Orientation = orientation;
            m_Documents.Owner = this;
            m_Documents.DocumentAdded += new EventHandler(DocumentAdded);
            m_Documents.DocumentRemoved += new EventHandler(DocumentRemoved);
            if (documents != null)
            {
                foreach (DocumentBaseContainer doc in documents)
                    m_Documents.Add(doc);
            }
        }
        /// 
        /// Creates new instance of the object.
        /// 
        public DocumentDockContainer() : this(null, eOrientation.Horizontal) { }
        /// 
        /// Resizes the object inside of the given bounds.
        /// 
        /// Available area.
        public override void Layout(Rectangle bounds)
        {
            if (LayoutInternal(bounds))
            {
                if (!_OversizeEnabled)
                    this.SetDisplayBounds(bounds);
            }
            else
                this.SetDisplayBounds(Rectangle.Empty);
        }
        private struct DocumentMeasureInfo
        {
            public int TotalSize;
            public int VisibleCount;
        }
        private DocumentMeasureInfo MeasureDocuments(Rectangle bounds)
        {
            DocumentMeasureInfo info = new DocumentMeasureInfo();
            if (m_Documents.Count == 0)
                return info;
            int boundsSize = (m_Orientation == eOrientation.Horizontal ? bounds.Width : bounds.Height);
            int defaultSize = 0; // (boundsSize - ((m_Documents.Count - 1) * m_SplitterSize)) / m_Documents.Count;
            // Check whether default size is below minimum size and make appropriate adjustments...
            int[] dockSizes = new int[m_Documents.Count];
            bool adjustDefaultSize = false;
            int defaultSizeCount = m_Documents.Count;
            int splitterSize = Dpi.Width(m_SplitterSize);
            do
            {
                defaultSize = (boundsSize - ((defaultSizeCount - 1) * splitterSize)) / defaultSizeCount;
                adjustDefaultSize = false;
                for (int i = 0; i < m_Documents.Count; i++)
                {
                    if (dockSizes[i] != 0) continue;
                    DocumentBaseContainer doc = m_Documents[i];
                    if (m_Orientation == eOrientation.Horizontal && doc.LayoutBounds.Width == 0 && doc.MinimumSize.Width > defaultSize)
                    {
                        dockSizes[i] = doc.MinimumSize.Width;
                        adjustDefaultSize = true;
                        boundsSize -= dockSizes[i];
                        defaultSizeCount--;
                        break;
                    }
                    else if (m_Orientation == eOrientation.Vertical && doc.LayoutBounds.Height == 0 && doc.MinimumSize.Height > defaultSize)
                    {
                        dockSizes[i] = doc.MinimumSize.Height;
                        adjustDefaultSize = true;
                        boundsSize -= dockSizes[i];
                        defaultSizeCount--;
                        break;
                    }
                }
            } while (adjustDefaultSize && defaultSizeCount > 0);
            int totalSize = 0;
            int visibleCount = 0;
            for (int i = 0; i < m_Documents.Count; i++)
            {
                DocumentBaseContainer doc = m_Documents[i];
                if (m_Orientation == eOrientation.Horizontal && doc.LayoutBounds.Width == 0)
                    doc.SetLayoutBounds(new Rectangle(0, 0, (dockSizes[i] > 0 ? dockSizes[i] : defaultSize), bounds.Height));
                else if (m_Orientation == eOrientation.Vertical && doc.LayoutBounds.Height == 0)
                    doc.SetLayoutBounds(new Rectangle(0, 0, bounds.Width, (dockSizes[i] > 0 ? dockSizes[i] : defaultSize)));
                if (doc.Visible)
                {
                    totalSize += ((m_Orientation == eOrientation.Horizontal ? doc.LayoutBounds.Width : doc.LayoutBounds.Height) + splitterSize);
                    visibleCount++;
                }
            }
            if (visibleCount > 0)
                totalSize -= splitterSize;
            info.TotalSize = totalSize;
            info.VisibleCount = visibleCount;
            return info;
        }
        private bool AdjustLayoutBoundsForMinimumSize(float m, Rectangle layoutBounds)
        {
            bool layoutAdjusted = false;
            foreach (DocumentBaseContainer doc in m_Documents)
            {
                if (!doc.Visible) continue;
                Rectangle docBounds;
                if (m_Orientation == eOrientation.Horizontal)
                {
                    docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, (int)(doc.LayoutBounds.Width * m), layoutBounds.Height);
                    if (docBounds.Width < doc.MinimumSize.Width)
                    {
                        layoutAdjusted = true;
                        break;
                    }
                }
                else
                {
                    docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, layoutBounds.Width, (int)(doc.LayoutBounds.Height * m));
                    if (docBounds.Height < doc.MinimumSize.Height)
                    {
                        layoutAdjusted = true;
                        break;
                    }
                }
            }
            if (layoutAdjusted)
            {
                foreach (DocumentBaseContainer doc in m_Documents)
                    doc.SetLayoutBounds(Rectangle.Empty);
            }
            return layoutAdjusted;
        }
        private bool LayoutInternal(Rectangle bounds)
        {
            if (m_Documents.Count == 0)
                return false;
            int boundsSize = (m_Orientation == eOrientation.Horizontal ? bounds.Width : bounds.Height);
            DocumentMeasureInfo info = MeasureDocuments(bounds);
            if (info.VisibleCount == 0)
                return false;
            int totalSize = info.TotalSize;
            int visibleCount = info.VisibleCount;
            float m = (float)boundsSize / (float)totalSize;
            if (AdjustLayoutBoundsForMinimumSize(m, bounds))
                info = MeasureDocuments(bounds);
            Rectangle layoutBounds = bounds;
            int processed = 0;
            bool setSize = true;
            if (!m_RecordDocumentSize && Math.Abs(totalSize - boundsSize) / m_Documents.Count != (int)(Math.Abs(totalSize - boundsSize) / m_Documents.Count) * m_Documents.Count)
                setSize = false;
            bool resize = (totalSize != boundsSize);
            Rectangle contentBounds = Rectangle.Empty;
            for (int i = 0; i < m_Documents.Count; i++)
            {
                DocumentBaseContainer doc = m_Documents[i];
                if (doc.Visible)
                {
                    processed++;
                    Rectangle docBounds;
                    if (resize)
                    {
                        if (m_Orientation == eOrientation.Horizontal)
                        {
                            if (_OversizeEnabled)
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, (int)Math.Max(doc.MinimumSize.Width, (doc.LayoutBounds.Width * m)), Math.Max(layoutBounds.Height, doc.MinimumSize.Height));
                            else
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, (int)(doc.LayoutBounds.Width * m), layoutBounds.Height);
                        }
                        else
                        {
                            if (_OversizeEnabled)
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, Math.Max(layoutBounds.Width, doc.MinimumSize.Width), (int)Math.Max(doc.MinimumSize.Height, (doc.LayoutBounds.Height * m)));
                            else
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, layoutBounds.Width, (int)(doc.LayoutBounds.Height * m));
                        }
                    }
                    else
                    {
                        if (m_Orientation == eOrientation.Horizontal)
                        {
                            if (_OversizeEnabled)
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, doc.LayoutBounds.Width, Math.Max(layoutBounds.Height, doc.MinimumSize.Height));
                            else
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, doc.LayoutBounds.Width, layoutBounds.Height);
                        }
                        else
                        {
                            if (_OversizeEnabled)
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, Math.Max(layoutBounds.Width, doc.MinimumSize.Width), doc.LayoutBounds.Height);
                            else
                                docBounds = new Rectangle(layoutBounds.X, layoutBounds.Y, layoutBounds.Width, doc.LayoutBounds.Height);
                        }
                    }
                    if (processed == visibleCount && (!_OversizeEnabled || docBounds.Width < layoutBounds.Width && m_Orientation == eOrientation.Horizontal || docBounds.Height < layoutBounds.Height && m_Orientation == eOrientation.Vertical))
                        docBounds = layoutBounds;
                    Rectangle actualDocBounds = docBounds;
                    actualDocBounds.Offset(_ScrollPosition);
                    doc.Layout(actualDocBounds);
                    if (setSize) doc.SetLayoutBounds(actualDocBounds);
                    if (contentBounds.IsEmpty)
                        contentBounds = actualDocBounds;
                    else
                        contentBounds = Rectangle.Union(contentBounds, actualDocBounds);
                    int splitterSize = Dpi.Width(m_SplitterSize);
                    if (m_Orientation == eOrientation.Horizontal)
                    {
                        layoutBounds.Width -= (docBounds.Width + splitterSize);
                        layoutBounds.X += (docBounds.Width + splitterSize);
                    }
                    else
                    {
                        layoutBounds.Height -= (docBounds.Height + splitterSize);
                        layoutBounds.Y += (docBounds.Height + splitterSize);
                    }
                }
            }
            if (_OversizeEnabled)
            {
                this.SetDisplayBounds(contentBounds);
            }
            return true;
        }
        private Point _ScrollPosition = Point.Empty;
        internal Point ScrollPosition
        {
            get { return _ScrollPosition; }
            set { _ScrollPosition = value; }
        }
        /// 
        /// Gets whether document is visible or not.
        /// 
        public override bool Visible
        {
            get
            {
                foreach (DocumentBaseContainer doc in m_Documents)
                {
                    if (doc.Visible)
                        return true;
                }
                return false;
            }
        }
        /// 
        /// Gets the orientation of the container. Default value is Horizontal.
        /// 
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public eOrientation Orientation
        {
            get { return m_Orientation; }
            set
            {
                if (m_Orientation != value)
                {
                    m_Orientation = value;
                    OnOrientationChanged();
                }
            }
        }
        /// 
        /// Returns collection of the documents hosted by this container.
        /// 
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DocumentBaseContainerCollection Documents
        {
            get { return m_Documents; }
        }
        /// 
        /// Occurs when width is being set on child document.
        /// 
        /// Reference document being changed
        /// Width in pixels
        /// True if width was applied by parent otherwise false
        protected internal override bool OnSetWidth(DocumentBaseContainer doc, int width)
        {
            bool ret = false;
            if (m_Orientation != eOrientation.Horizontal || width < doc.MinimumSize.Width && doc.MinimumSize.Width > 0)
                return ret;
            DocumentBaseContainer pairDoc = null;
            DocumentBaseContainer previousDoc = null;
            int refIndex = m_Documents.IndexOf(doc);
            // Lock in the display size if it is set
            for (int i = 0; i < m_Documents.Count; i++)
            {
                DocumentBaseContainer dc = m_Documents[i];
                if (!dc.Visible) continue;
                if (dc.DisplayBounds.Width > 0)
                    dc.SetLayoutBounds(dc.DisplayBounds);
                if (i > refIndex && dc.Visible && pairDoc == null)
                    pairDoc = dc;
                else if (i < refIndex && dc.Visible)
                    previousDoc = dc;
            }
            int diff = doc.LayoutBounds.Width - width;
            if (pairDoc != null && pairDoc.LayoutBounds.Width > 0 && pairDoc.LayoutBounds.Width + diff > 0 &&
                (pairDoc.LayoutBounds.Width + diff >= pairDoc.MinimumSize.Width || pairDoc.MinimumSize.Width == 0))
            {
                pairDoc.SetLayoutBounds(new Rectangle(pairDoc.LayoutBounds.X, pairDoc.LayoutBounds.Y, pairDoc.LayoutBounds.Width + diff, pairDoc.LayoutBounds.Height));
                ret = true;
            }
            else if (pairDoc == null && previousDoc != null && previousDoc.LayoutBounds.Width > 0 && previousDoc.LayoutBounds.Width + diff > 0 &&
                (previousDoc.LayoutBounds.Width + diff >= previousDoc.MinimumSize.Width || previousDoc.MinimumSize.Width == 0))
            {
                doc.SetLayoutBounds(new Rectangle(doc.LayoutBounds.X, doc.LayoutBounds.Y, width, doc.LayoutBounds.Height));
                // Resetting previous document width caused problem with ever growing bar when Width of single bar is set
                // Reason is that resetting width here will cause the new space in container to be proportionally allocated thus setting single bar width which is intent of this function never works
                //previousDoc.SetLayoutBounds(new Rectangle(previousDoc.LayoutBounds.X, previousDoc.LayoutBounds.Y, 0, previousDoc.LayoutBounds.Height));
                ret = true;
            }
            return ret;
        }
        /// 
        /// Occurs when height is being set on child document.
        /// 
        /// Reference document being changed
        /// Height in pixels
        /// True if width was applied by parent otherwise false
        protected internal override bool OnSetHeight(DocumentBaseContainer doc, int height)
        {
            bool ret = false;
            if (m_Orientation != eOrientation.Vertical || height < doc.MinimumSize.Height && doc.MinimumSize.Height > 0)
                return ret;
            DocumentBaseContainer pairDoc = null;
            DocumentBaseContainer previousDoc = null;
            int refIndex = m_Documents.IndexOf(doc);
            // Lock in the display size if it is set
            for (int i = 0; i < m_Documents.Count; i++)
            {
                DocumentBaseContainer dc = m_Documents[i];
                if (!dc.Visible) continue;
                if (dc.DisplayBounds.Height > 0)
                    dc.SetLayoutBounds(dc.DisplayBounds);
                if (i > refIndex && dc.Visible && pairDoc == null)
                    pairDoc = dc;
                else if (i < refIndex && dc.Visible)
                    previousDoc = dc;
            }
            if (pairDoc == null) pairDoc = previousDoc;
            int diff = doc.LayoutBounds.Height - height;
            if (pairDoc != null && pairDoc.LayoutBounds.Height > 0 && pairDoc.LayoutBounds.Height + diff > 0 &&
                (pairDoc.LayoutBounds.Height + diff >= pairDoc.MinimumSize.Height || pairDoc.MinimumSize.Height == 0))
            {
                pairDoc.SetLayoutBounds(new Rectangle(pairDoc.LayoutBounds.X, pairDoc.LayoutBounds.Y, pairDoc.LayoutBounds.Width, pairDoc.LayoutBounds.Height + diff));
                ret = true;
            }
            else if (pairDoc == null && previousDoc != null && previousDoc.LayoutBounds.Height > 0 && previousDoc.LayoutBounds.Height + diff > 0 &&
                (previousDoc.LayoutBounds.Height + diff >= previousDoc.MinimumSize.Height || previousDoc.MinimumSize.Height == 0))
            {
                doc.SetLayoutBounds(new Rectangle(doc.LayoutBounds.X, doc.LayoutBounds.Y, doc.LayoutBounds.Width, height));
                previousDoc.SetLayoutBounds(new Rectangle(previousDoc.LayoutBounds.X, previousDoc.LayoutBounds.Y, previousDoc.LayoutBounds.Width, 0));
                ret = true;
            }
            return ret;
        }
        /// 
        /// Returns the DocumentBarContainer object for a given bar.
        /// 
        /// Bar to search for.
        /// Reference to container or null if bar could not be found
        public DocumentBarContainer GetBarDocumentContainer(Bar bar)
        {
            foreach (DocumentBaseContainer doc in m_Documents)
            {
                if (doc is DocumentBarContainer && ((DocumentBarContainer)doc).Bar == bar)
                    return (DocumentBarContainer)doc;
                else if (doc is DocumentDockContainer)
                {
                    DocumentBarContainer db = ((DocumentDockContainer)doc).GetBarDocumentContainer(bar);
                    if (db != null) return db;
                }
            }
            return null;
        }
        /// 
        /// Returns minimum size of the object.
        /// 
        protected internal override System.Drawing.Size MinimumSize
        {
            get
            {
                return m_MinimumSize;
            }
        }
        private void DocumentAdded(object sender, EventArgs e)
        {
            DocumentBaseContainer doc = sender as DocumentBaseContainer;
            Size s = doc.MinimumSize;
            int splitterSize = Dpi.Width(m_SplitterSize);
            if (m_Orientation == eOrientation.Horizontal)
            {
                m_MinimumSize.Width += (s.Width + splitterSize);
                if (s.Height > m_MinimumSize.Height)
                    m_MinimumSize.Height = s.Height;
            }
            else
            {
                m_MinimumSize.Height += (s.Height + splitterSize);
                if (s.Width > m_MinimumSize.Width)
                    m_MinimumSize.Width = s.Width;
            }
        }
        internal void DocumentRemoved(object sender, EventArgs e)
        {
            RefreshMinimumSize();
        }
        internal void RefreshMinimumSize()
        {
            m_MinimumSize = Size.Empty;
            foreach (DocumentBaseContainer doc in m_Documents)
            {
                Size s = doc.MinimumSize;
                int splitterSize = Dpi.Width(m_SplitterSize);
                if (m_Orientation == eOrientation.Horizontal)
                {
                    m_MinimumSize.Width += (s.Width + splitterSize);
                    if (s.Height > m_MinimumSize.Height)
                        m_MinimumSize.Height = s.Height;
                }
                else
                {
                    m_MinimumSize.Height += (s.Height + splitterSize);
                    if (s.Width > m_MinimumSize.Width)
                        m_MinimumSize.Width = s.Width;
                }
            }
        }
        private void OnOrientationChanged()
        {
            ResetDocumentsLayout();
        }
        private void ResetDocumentsLayout()
        {
            foreach (DocumentBaseContainer doc in m_Documents)
                doc.SetLayoutBounds(Rectangle.Empty);
        }
        /// 
        /// Gets or sets splitter size in pixels between the documents docking inside the container. Default value is 3.
        /// 
        [Browsable(true), DefaultValue(3), Description("Indicates the splitter size between the documents docking inside the container."), Category("Layout")]
        public virtual int SplitterSize
        {
            get { return m_SplitterSize; }
            set
            {
                m_SplitterSize = value;
                foreach (DocumentBaseContainer doc in m_Documents)
                {
                    DocumentDockContainer cont = doc as DocumentDockContainer;
                    if (cont != null)
                        cont.SplitterSize = value;
                }
            }
        }
        /// 
        /// Gets or sets whether the size of the documents is recorded once the layout is calculated.
        /// 
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool RecordDocumentSize
        {
            get { return m_RecordDocumentSize; }
            set { m_RecordDocumentSize = value; }
        }
        /// 
        /// Indicates whether the container is allowed to exceed the parent control client size due to inner child windows minimum size constraints.
        /// 
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool OversizeEnabled
        {
            get { return _OversizeEnabled; }
            set { _OversizeEnabled = value; }
        }
        internal override void UpdateScrollBounds(int xScroll, int yScroll, bool moveControls)
        {
            base.UpdateScrollBounds(xScroll, yScroll, moveControls);
            foreach (DocumentBaseContainer doc in m_Documents)
            {
                doc.UpdateScrollBounds(xScroll, yScroll, moveControls);
            }
        }
        #endregion
    }
}