using System;
using System.ComponentModel;
using System.Drawing;
namespace DevComponents.DotNetBar.Charts
{
    [TypeConverter(typeof(BlankExpandableObjectConverter))]
    public class ChartMatrix : IProcessSerialElement
    {
        #region Private variables
        private States _States;
        private int _Width;
        private int _Height;
        private ChartContainer[,] _LocalArray;
        private Rectangle[,] _BoundsArray;
        private Point _ScrollOffset;
        private MatrixRowColProperties[] _ColumnProperties;
        private MatrixRowColProperties[] _RowProperties;
        private Size _DefaultCellSize = new Size(100, 100);
        private AutoSizeMode _AutoSizeMode = AutoSizeMode.NotSet;
        private DividerLines _DividerLines = DividerLines.NotSet;
        private ChartContainer _Parent;
        #endregion
        #region Constructors
        public ChartMatrix()
            : this(0, 0)
        {
        }
        public ChartMatrix(Size size)
            : this(size.Width, size.Height)
        {
        }
        public ChartMatrix(int width, int height)
        {
            CreateMatrix(width, height);
        }
        #endregion
        #region Public properties
        #region AutoSizeMode
        ///
        /// Gets or sets the default mode used to size each matrix row/col (by FillWeight, etc).
        ///
        [DefaultValue(AutoSizeMode.NotSet), Category("Layout")]
        [Description("Indicates the default mode used to size each matrix row/col (by FillWeight, etc).")]
        public AutoSizeMode AutoSizeMode
        {
            get { return (_AutoSizeMode); }
            set
            {
                if (value != _AutoSizeMode)
                {
                    _AutoSizeMode = value;
                    OnPropertyChanged("AutoSizeMode");
                }
            }
        }
        #endregion
        #region ColumnProperties
        /// 
        /// Get a reference to the matrix column properties.
        /// 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MatrixRowColProperties[] ColumnProperties
        {
            get { return (_ColumnProperties);  }
        }
        #endregion
        #region DefaultCellSize
        ///
        /// Gets or sets the default minimum size for each matrix cell.
        ///
        [ Category("Layout")]
        [Description("Indicates the default minimum size for each matrix cell.")]
        public Size DefaultCellSize
        {
            get { return (_DefaultCellSize); }
            set
            {
                if (value != _DefaultCellSize)
                {
                    _DefaultCellSize = value;
                    OnPropertyChanged("DefaultCellSize");
                }
            }
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        internal virtual bool ShouldSerializeDefaultCellSize()
        {
            return (_DefaultCellSize != new Size(100, 100));
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        internal virtual void ResetDefaultCellSize()
        {
            DefaultCellSize = new Size(100, 100);
        }
        #endregion
        #region DividerLines
        /// 
        /// Gets or sets which Divider lines (horizontal and/or vertical)
        /// are displayed between each Matrix cell.
        /// 
        [DefaultValue(DividerLines.NotSet), Category("Appearance")]
        [Description("Indicates which Divider lines (horizontal and/or vertical) are displayed between each Matrix cell.")]
        public DividerLines DividerLines
        {
            get { return (_DividerLines); }
            set
            {
                if (_DividerLines != value)
                {
                    _DividerLines = value;
                    OnPropertyChanged("DividerLines");
                }
            }
        }
        #endregion
        #region Indexer [,]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ChartContainer this[int x, int y]
        {
            get
            {
                ValidateXandY(x, y);
                return (_LocalArray[x, y]);
            }
            internal set
            {
                ValidateXandY(x, y);
                if (value is IComparable == true)
                {
                    if (((IComparable)value).CompareTo(_LocalArray[x, y]) == 0)
                        return;
                }
                _LocalArray[x, y] = value;
                if (value != null)
                    value.Parent = Parent;
                OnPropertyChanged("Value");
            }
        }
        #endregion
        #region Indexer []
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ChartContainer this[int xy]
        {
            get
            {
                ValidateXY(xy);
                int y = xy / _Width;
                int x = xy % _Width;
                return (this[x, y]);
            }
            internal set
            {
                ValidateXY(xy);
                int y = xy / _Width;
                int x = xy % _Width;
                this[x, y] = value;
                OnPropertyChanged("Value");
            }
        }
        #endregion
        #region IsEmpty
        /// 
        /// Gets whether the matrix is empty.
        /// 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsEmpty
        {
            get
            {
                for (int i = 0; i < _Width; i++)
                {
                    for (int j = 0; j < _Height; j++)
                    {
                        if (_LocalArray[i, j] != null)
                            return (false);
                    }
                }
                return (true);
            }
        }
        #endregion
        #region Height
        /// 
        /// Gets or sets the matrix height.
        /// 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int Height
        {
            get { return (_Height); }
            set
            {
                if (value != _Height)
                {
                    _Height = value;
                    CreateMatrix(_Width, _Height);
                    OnPropertyChanged("Height");
                }
            }
        }
        #endregion
        #region RowProperties
        /// 
        /// Get a reference to the matrix row properties.
        /// 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MatrixRowColProperties[] RowProperties
        {
            get { return (_RowProperties); }
        }
        #endregion
        #region Size
        /// 
        /// Gets or sets the matrix size.
        /// 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Size Size
        {
            get
            {
                int x = _Width;
                int y = _Height;
                return (new Size(x, y));
            }
            set
            {
                if (value.Width != _Width || value.Height != _Height)
                {
                    int x = value.Width;
                    int y = value.Height;
                    CreateMatrix(x, y);
                    OnPropertyChanged("Size");
                }
            }
        }
        #endregion
        #region Width
        /// 
        /// Gets or sets the matrix width.
        /// 
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int Width
        {
            get { return (_Width); }
            set
            {
                if (value != _Width)
                {
                    _Width = value;
                    CreateMatrix(_Width, _Height);
                    OnPropertyChanged("Width");
                }
            }
        }
        #endregion
        #endregion
        #region Internal properties
        #region BoundsArray
        internal Rectangle[,] BoundsArray
        {
            get { return (_BoundsArray); }
        }
        #endregion
        #region Parent
        internal ChartContainer Parent
        {
            get { return (_Parent); }
            set { _Parent = value; }
        }
        #endregion
        #region ScrollOffset
        internal Point ScrollOffset
        {
            get { return (_ScrollOffset); }
            set { _ScrollOffset = value; }
        }
        #endregion
        #endregion
        #region ValidateXandY
        protected void ValidateXandY(int x, int y)
        {
            if ((uint)x >= _Width)
                throw new IndexOutOfRangeException("Invalid 'x' value.");
            if ((uint)y >= _Height)
                throw new IndexOutOfRangeException("Invalid 'y' value.");
        }
        #endregion
        #region ValidateXY
        protected void ValidateXY(int xy)
        {
            int xyMax = _Width * _Height;
            if (xy >= xyMax)
                throw new IndexOutOfRangeException("Invalid 'xy' value.");
        }
        #endregion
        #region CreateMatrix
        private void CreateMatrix(int width, int height)
        {
            if (width < 0)
                throw new Exception("Width cannot be negative");
            if (height < 0)
                throw new Exception("Heigth cannot be negative");
            _Width = width;
            _Height = height;
            _LocalArray = new ChartContainer[width, height];
            _BoundsArray = new Rectangle[width, height];
            CreateProperties(ref _RowProperties, height);
            CreateProperties(ref _ColumnProperties, width);
        }
        #endregion
        #region CreateProperties
        private void CreateProperties(ref MatrixRowColProperties[] properties, int length)
        {
            ReleaseProperties(ref properties);
            properties = new MatrixRowColProperties[length];
            for (int i = 0; i < length; i++)
            {
                properties[i] = new MatrixRowColProperties();
                properties[i].PropertyChanged += Matrix_PropertyChanged;
            }
        }
        #endregion
        #region ReleaseProperties
        private void ReleaseProperties(ref MatrixRowColProperties[] properties)
        {
            if (properties != null)
            {
                for (int i = 0; i < properties.Length; i++)
                {
                    properties[i].PropertyChanged -= Matrix_PropertyChanged;
                    properties[i] = null;
                }
                properties = null;
            }
        }
        #endregion
        #region Clear
        public void Clear()
        {
            CreateMatrix(_Width, _Height);
        }
        #endregion
        #region GetElementAt
        public ChartContainer GetElementAt(Point pt)
        {
            for (int i = 0; i < _Width; i++)
            {
                for (int j = 0; j < _Height; j++)
                {
                    if (_LocalArray[i, j] != null)
                    {
                        Rectangle bounds = _BoundsArray[i, j];
                        bounds.X -= _ScrollOffset.X;
                        bounds.Y -= _ScrollOffset.Y;
                        if (bounds.Contains(pt))
                            return (_LocalArray[i, j]);
                    }
                }
            }
            return (null);
        }
        #endregion
        #region GetMatrixCoordAt
        public bool GetMatrixCoordAt(Point pt, ref int column, ref int row)
        {
            for (int i = 0; i < _Width; i++)
            {
                for (int j = 0; j < _Height; j++)
                {
                    Rectangle bounds = _BoundsArray[i, j];
                    bounds.X -= _ScrollOffset.X;
                    bounds.Y -= _ScrollOffset.Y;
                    if (bounds.Contains(pt))
                    {
                        column = i;
                        row = j;
                        return (true);
                    }
                }
            }
            return (false);
        }
        #endregion
        #region GetMatrixCoordOf
        public bool GetMatrixCoordOf(ChartContainer item, ref int row, ref int column)
        {
            if (item != null)
            {
                for (int i = 0; i < _Width; i++)
                {
                    for (int j = 0; j < _Height; j++)
                    {
                        if (item.Equals(_LocalArray[i, j]))
                        {
                            column = i;
                            row = j;
                            return (true);
                        }
                    }
                }
            }
            return (false);
        }
        #endregion
        #region Matrix_PropertyChanged
        void Matrix_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(e);
        }
        #endregion
        #region INotifyPropertyChanged Members
        /// 
        /// Occurs when property value has changed.
        /// 
        public event PropertyChangedEventHandler PropertyChanged;
        /// 
        /// Raises the PropertyChanged event.
        /// 
        /// Event arguments
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler eh = PropertyChanged;
            if (eh != null)
                eh(this, e);
        }
        /// 
        /// Default PropertyChanged processing
        /// 
        /// 
        protected void OnPropertyChanged(string s)
        {
            if (PropertyChanged != null)
                OnPropertyChanged(new PropertyChangedEventArgs(s));
        }
        #endregion
        #region Copy/CopyTo
        public ChartMatrix Copy()
        {
            ChartMatrix copy = new ChartMatrix();
            CopyTo(copy);
            return (copy);
        }
        public void CopyTo(ChartMatrix c)
        {
            c.AutoSizeMode = AutoSizeMode;
            c.DividerLines = DividerLines;
            c.Size = Size;
            if (ColumnProperties != null)
            {
                for (int i = 0; i < ColumnProperties.Length; i++)
                    ColumnProperties[i].CopyTo(c.ColumnProperties[i]);
            }
            if (RowProperties != null)
            {
                for (int i = 0; i < RowProperties.Length; i++)
                    RowProperties[i].CopyTo(c.RowProperties[i]);
            }
        }
        #endregion
        #region GetSerialData
        internal SerialElementCollection GetSerialData()
        {
            return (GetSerialData(true));
        }
        internal SerialElementCollection GetSerialData(bool root)
        {
            SerialElementCollection sec = new SerialElementCollection();
            if (root == true)
                sec.AddStartElement("ChartMatrix");
            sec.AddValue("AutoSizeMode", AutoSizeMode, AutoSizeMode.NotSet);
            sec.AddValue("DividerLines", DividerLines, DividerLines.NotSet);
            ChartPanel panel = Parent as ChartPanel;
                
            if (panel != null)
            {
                if (panel.AutoSizeChartMatrix == false)
                    sec.AddValue("Size", Size, Size.Empty);
            }
            if (AutoSizeMode == AutoSizeMode.None)
            {
                if (ColumnProperties != null && ColumnProperties.Length > 0)
                {
                    sec.AddStartElement("ColumnProperties count=\"" + ColumnProperties.Length + "\"");
                    foreach (MatrixRowColProperties rcp in ColumnProperties)
                        sec.AddElement(rcp.GetSerialData());
                    sec.AddEndElement("ColumnProperties");
                }
                if (RowProperties != null && RowProperties.Length > 0)
                {
                    sec.AddStartElement("RowProperties count=\"" + RowProperties.Length + "\"");
                    foreach (MatrixRowColProperties rcp in RowProperties)
                        sec.AddElement(rcp.GetSerialData());
                    sec.AddEndElement("RowProperties");
                }
            }
            if (root == true)
                sec.AddEndElement("ChartMatrix");
            return (sec);
        }
        #endregion
        #region PutSerialData
        #region ProcessValue
        void IProcessSerialElement.ProcessValue(SerialElement se)
        {
            ChartPanel panel = Parent as ChartPanel;
                
            switch (se.Name)
            {
                case "AutoSizeMode":
                    AutoSizeMode = (AutoSizeMode)se.GetValueEnum(typeof(AutoSizeMode));
                    break;
                case "DividerLines":
                    DividerLines = (DividerLines)se.GetValueEnum(typeof(DividerLines));
                    break;
                case "Size":
                    if (panel != null)
                    {
                        if (panel.AutoSizeChartMatrix == false)
                            Size = se.GetValueSize();
                    }
                    break;
                default:
                    throw new Exception("Unknown Serial Value (" + se.Name + ")");
            }
        }
        #endregion
        #region ProcessCollection
        private MatrixRowColProperties[] _RcProperties;
        void IProcessSerialElement.ProcessCollection(SerialElement se)
        {
            SerialElementCollection sec = se.Sec;
            switch (se.Name)
            {
                case "ColumnProperties":
                    _RcProperties = ColumnProperties;
                    sec.PutSerialData(this);
                    break;
                case "RowProperties":
                    _RcProperties = RowProperties;
                    sec.PutSerialData(this);
                    break;
                case "MatrixRowColProperties":
                    sec.PutSerialData(_RcProperties[se.ValueIndex]);
                    break;
                default:
                    throw new Exception("Unknown Serial Collection (" + se.Name + ")");
            }
        }
        #endregion
        #endregion
        #region States
        [Flags]
        private enum States : uint
        {
        }
        #region TestState
        private bool TestState(States state)
        {
            return ((_States & state) == state);
        }
        #endregion
        #region SetState
        private void SetState(States state, bool value)
        {
            if (value == true)
                _States |= state;
            else
                _States &= ~state;
        }
        #endregion
        #endregion
        #region IDisposable
        public virtual void Dispose()
        {
            ReleaseProperties(ref _RowProperties);
            ReleaseProperties(ref _ColumnProperties);
        }
        #endregion
    }
    [TypeConverter(typeof(BlankExpandableObjectConverter))]
    public class MatrixRowColProperties : IProcessSerialElement, INotifyPropertyChanged
    {
        #region Private variables
        private int _FillWeight = 100;
        private int _MinimumLength = 0;
        private int _Length;
        private AutoSizeMode _AutoSizeMode = AutoSizeMode.NotSet;
        private bool _AlignContentBounds = true;
        #endregion
        #region Public properties
        #region AlignContentBounds
        ///
        /// Gets or sets whether the matrix row/col has its content bounds aligned.
        ///
        [DefaultValue(true), Category("Data")]
        [Description("Indicates the matrix row/col has its content bounds aligned.")]
        public bool AlignContentBounds
        {
            get { return (_AlignContentBounds); }
            set
            {
                if (value != _AlignContentBounds)
                {
                    _AlignContentBounds = value;
                    OnPropertyChanged("AlignContentBounds");
                }
            }
        }
        #endregion
        #region AutoSizeMode
        ///
        /// Gets or sets the mode used to size the matrix row/col.
        ///
        [DefaultValue(AutoSizeMode.NotSet), Category("Data")]
        [Description("Indicates the mode used to sice the matrix row/col.")]
        public AutoSizeMode AutoSizeMode
        {
            get { return (_AutoSizeMode); }
            set
            {
                if (value != _AutoSizeMode)
                {
                    _AutoSizeMode = value;
                    OnPropertyChanged("AutoSizeMode");
                }
            }
        }
        #endregion
        #region FillWeight
        /// 
        /// Gets or sets a value which, when AutoSizeMode is Fill,
        /// represents the width of the row/col relative to the widths
        /// of other fill-mode row/col items (default value is 100).
        /// 
        [DefaultValue(100), Category("Sizing")]
        [Description("Indicates a value which, when AutoSizeMode is Fill, represents the width of the row/col relative to the widths of other fill-mode row/col items (default value is 100).")]
        public int FillWeight
        {
            get { return (_FillWeight); }
            set
            {
                if (value != _FillWeight)
                {
                    _FillWeight = value;
                    OnPropertyChanged("FillWeight");
                }
            }
        }
        #endregion
        #region Length
        /// 
        /// Gets or sets a value which represents the length (in pixels)
        /// that the row/col is to occupy.
        /// 
        [DefaultValue(0), Category("Sizing")]
        [Description("Indicates a value which represents the length (in pixels) that the row/col is to occupy.")]
        public int Length
        {
            get { return (_Length); }
            set
            {
                if (value != _Length)
                {
                    _Length = value;
                    OnPropertyChanged("Length");
                }
            }
        }
        #endregion
        #region MinimumLength
        /// 
        /// Gets or sets a value which represents the minimum length (in pixels)
        /// that the row/col can occupy.
        /// 
        [DefaultValue(0), Category("Sizing")]
        [Description("Indicates a value which represents the minimum length (in pixels) that the row/col can occupy.")]
        public int MinimumLength
        {
            get { return (_MinimumLength); }
            set
            {
                if (value != _MinimumLength)
                {
                    _MinimumLength = value;
                    OnPropertyChanged("MinimumLength");
                }
            }
        }
        #endregion
        #endregion
        #region CopyTo
        public void CopyTo(MatrixRowColProperties c)
        {
            c.AlignContentBounds = AlignContentBounds;
            c.AutoSizeMode = AutoSizeMode;
            c.FillWeight = FillWeight;
            c.Length = Length;
            c.MinimumLength = MinimumLength;
        }
        #endregion
        #region GetSerialData
        internal SerialElementCollection GetSerialData()
        {
            return (GetSerialData(true));
        }
        internal SerialElementCollection GetSerialData(bool root)
        {
            SerialElementCollection sec = new SerialElementCollection();
            if (root == true)
                sec.AddStartElement("MatrixRowColProperties");
            sec.AddValue("AlignContentBounds", AlignContentBounds, true);
            sec.AddValue("AutoSizeMode", AutoSizeMode, AutoSizeMode.NotSet);
            sec.AddValue("FillWeight", FillWeight, 100);
            sec.AddValue("Length", Length, 0);
            sec.AddValue("MinimumLength", MinimumLength, 0);
            if (root == true)
                sec.AddEndElement("MatrixRowColProperties");
            return (sec);
        }
        #endregion
        #region PutSerialData
        #region ProcessValue
        void IProcessSerialElement.ProcessValue(SerialElement se)
        {
            switch (se.Name)
            {
                case "AlignContentBounds":
                    AlignContentBounds = bool.Parse(se.StringValue);
                    break;
                case "AutoSizeMode":
                    AutoSizeMode = (AutoSizeMode)se.GetValueEnum(typeof(AutoSizeMode));
                    break;
                case "FillWeight":
                    FillWeight = int.Parse(se.StringValue);
                    break;
                case "Length":
                    Length = int.Parse(se.StringValue);
                    break;
                case "MinimumLength":
                    MinimumLength = int.Parse(se.StringValue);
                    break;
                default:
                    throw new Exception("Unknown Serial Value (" + se.Name + ")");
            }
        }
        #endregion
        #region ProcessCollection
        void IProcessSerialElement.ProcessCollection(SerialElement se)
        {
            throw new Exception("Unknown Serial Collection (" + se.Name + ")");
        }
        #endregion
        #endregion
        #region INotifyPropertyChanged Members
        /// 
        /// Occurs when property value has changed.
        /// 
        public event PropertyChangedEventHandler PropertyChanged;
        /// 
        /// Raises the PropertyChanged event.
        /// 
        /// Event arguments
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler eh = PropertyChanged;
            if (eh != null)
                eh(this, e);
        }
        /// 
        /// Default PropertyChanged processing
        /// 
        /// 
        protected void OnPropertyChanged(string s)
        {
            if (PropertyChanged != null)
                OnPropertyChanged(new PropertyChangedEventArgs(s));
        }
        #endregion
    }
}