using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Text;
using DevComponents.Instrumentation.Primitives;
namespace DevComponents.Instrumentation
{
    public class GaugeIndicator : GaugeItem
    {
        #region Private variables
        private bool _AutoSize;
        private bool _UnderScale;
        private int _RefreshRate;
        private double _MinValue;
        private double _MaxValue;
        private double _Value;
        private GradientFillColor _BackColor;
        private SizeF _Size;
        private PointF _Location;
        private Rectangle _Bounds;
        private Point _Center;
        private Font _Font;
        private string _Text;
        private string _EmptyString;
        private string _OverRangeString;
        private string _UnderRangeString;
        private bool _Dampening;
        private double _DampeningSweepTime;
        private double _DValue;
        private double _DeltaValue;
        private double _DStartValue;
        private double _DEndValue;
        private long _DStartTicks;
        private long _DRefreshTicks;
        private GaugeControl _GaugeControl;
        #endregion
        public GaugeIndicator()
        {
            _AutoSize = true;
            BackColor.BorderColor = Color.Black;
            _Size = new SizeF(.07f, .07f);
            _Location = new PointF(.5f, .7f);
            _UnderScale = true;
            _RefreshRate = 10;
            _EmptyString = "-";
            _OverRangeString = "Error";
            _UnderRangeString = "Error";
            _MinValue = 0;
            _MaxValue = 100;
            _Value = double.NaN;
        }
        #region Public properties
        #region AutoSize
        /// 
        /// Gets or sets whether the indicator contents are auto sized
        /// 
        [Browsable(true)]
        [Category("Behavior"), DefaultValue(true)]
        [Editor("DevComponents.Instrumentation.Design.AngleRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral,  PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))]
        [Description("Indicates whether the indicator contents are auto sized.")]
        public bool AutoSize
        {
            get { return (_AutoSize); }
            set
            {
                if (_AutoSize != value)
                {
                    _AutoSize = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region BackColor
        /// 
        /// Gets or sets the BackColor
        /// 
        [Browsable(true), Category("Appearance")]
        [Description("Indicates the BackColor.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public GradientFillColor BackColor
        {
            get
            {
                if (_BackColor == null)
                {
                    _BackColor = new GradientFillColor();
                    _BackColor.ColorTableChanged += BackColorColorTableChanged;
                }
                return (_BackColor);
            }
            set
            {
                if (_BackColor != null)
                    _BackColor.ColorTableChanged -= BackColorColorTableChanged;
                _BackColor = value;
                if (_BackColor != null)
                    _BackColor.ColorTableChanged += BackColorColorTableChanged;
                OnGaugeItemChanged();
            }
        }
        #endregion
        #region Bounds
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Rectangle Bounds
        {
            get { return (_Bounds); }
            internal set { _Bounds = value; }
        }
        #endregion
        #region DampeningSweepTime
        /// 
        /// Gets or sets the time it takes for the indicator to
        /// change from its minimum to maximum value, measured in seconds
        /// 
        [Browsable(true)]
        [Category("Behavior"), DefaultValue(0d)]
        [Description("Indicates the time it takes for the indicator to change from its minimum to maximum value, measured in seconds.")]
        public double DampeningSweepTime
        {
            get { return (_DampeningSweepTime); }
            set
            {
                if (_DampeningSweepTime != value)
                {
                    _DampeningSweepTime = value;
                    OnGaugeItemChanged();
                }
            }
        }
        #endregion
        #region EmptyString
        /// 
        /// Gets or sets the text string to display when the Indicator Value is empty (double.NaN)
        /// 
        [Browsable(true), Category("Behavior"), DefaultValue("-")]
        [Description("Indicates the text string to display when the Indicator Value is empty (double.NaN).")]
        public string EmptyString
        {
            get { return (_EmptyString); }
            set
            {
                if (_EmptyString != value)
                {
                    _EmptyString = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region Font
        /// 
        /// Gets or sets the text Font
        /// 
        [Browsable(true), Category("Appearance")]
        [Description("Indicates the text Font.")]
        public Font Font
        {
            get
            {
                if (_Font == null)
                    _Font = new Font("Microsoft SanSerif", 12);
                return (_Font);
            }
            set
            {
                if (_Font != null)
                    _Font.Dispose();
                _Font = value;
                AbsFont = null;
                OnGaugeItemChanged(true);
            }
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        internal virtual bool ShouldSerializeFont()
        {
            if (_Font == null)
                return (false);
            using (Font font = new Font("Microsoft SanSerif", 12))
                return (_Font.Equals(font) == false);
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        internal virtual void ResetFont()
        {
            Font = new Font("Microsoft SanSerif", 12);
        }
        #endregion
        #region GaugeControl
        /// 
        /// Gets or sets the owning GaugeControl
        /// 
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public GaugeControl GaugeControl
        {
            get { return (_GaugeControl); }
            internal set { _GaugeControl = value; }
        }
        #endregion
        #region Location
        /// 
        /// Gets or sets the location of the image area, specified as a percentage
        /// 
        [Browsable(true), Category("Layout")]
        [Editor("DevComponents.Instrumentation.Design.LocationEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral,  PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))]
        [Description("Indicates the location of the image area, specified as a percentage.")]
        [TypeConverter(typeof(PointFConverter))]
        public PointF Location
        {
            get { return (_Location); }
            set
            {
                if (_Location != value)
                {
                    _Location = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public bool ShouldSerializeLocation()
        {
            return (_Location.X != .5f || _Location.Y != .7f);
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public void ResetLocation()
        {
            _Location = new PointF(.5f, .7f);
        }
        #endregion
        #region MaxValue
        /// 
        /// Gets or sets the Maximum value for the Indicator
        /// 
        [Browsable(true), Category("Behavior"), DefaultValue(100d)]
        [Description("Indicates the Maximum value for the Indicator.")]
        public double MaxValue
        {
            get { return (_MaxValue); }
            set
            {
                if (_MaxValue != value)
                {
                    _MaxValue = value;
                    DValue = _Value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region MinValue
        /// 
        /// Gets or sets the Minimum value for the Indicator
        /// 
        [Browsable(true), Category("Behavior"), DefaultValue(0d)]
        [Description("Indicates the Minimum value for the Indicator.")]
        public double MinValue
        {
            get { return (_MinValue); }
            set
            {
                if (_MinValue != value)
                {
                    _MinValue = value;
                    DValue = _Value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region OverRangeString
        /// 
        /// Gets or sets the text string to display when the Indicator Value is over the set MaxValue range
        /// 
        [Browsable(true), Category("Behavior"), DefaultValue("Error")]
        [Description("Indicates the text string to display when the Indicator Value is over the set MaxValue range.")]
        public string OverRangeString
        {
            get { return (_OverRangeString); }
            set
            {
                if (_OverRangeString != value)
                {
                    _OverRangeString = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region RefreshRate
        /// 
        /// Gets or sets how often the indicator is refreshed per second
        /// 
        [Browsable(true)]
        [Category("Behavior"), DefaultValue(10)]
        [Editor("DevComponents.Instrumentation.Design.AngleRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral,  PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))]
        [Description("Indicates how often the indicator is refreshed per second.")]
        public int RefreshRate
        {
            get { return (_RefreshRate); }
            set
            {
                if (_RefreshRate != value)
                {
                    if (value <= 0 || value > 1000)
                        throw new ArgumentException("Value must be between 1 and 100");
                    _RefreshRate = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region Size
        /// 
        /// Gets or sets the size of the indicator, specified as a percentage
        /// 
        [Browsable(true), Category("Layout")]
        [Editor("DevComponents.Instrumentation.Design.SizeEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral,  PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))]
        [Description("Determines the size of the indicator, specified as a percentage.")]
        public SizeF Size
        {
            get { return (_Size); }
            set
            {
                if (_Size != value)
                {
                    _Size = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public bool ShouldSerializeSize()
        {
            return (_Size.Width != .07f || _Size.Height != .07f);
        }
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public void ResetSize()
        {
            _Size = new SizeF(.07f, .07f);
        }
        #endregion
        #region Text
        /// 
        /// Gets or sets the text to be displayed
        /// 
        [Browsable(true), Category("Behavior"), DefaultValue(null)]
        [Description("Indicates the text to be displayed.")]
        public string Text
        {
            get { return (_Text); }
            set
            {
                if (_Text != value)
                {
                    _Text = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region UnderRangeString
        /// 
        /// Gets or sets the text string to display when the Indicator Value is under the set MinValue range
        /// 
        [Browsable(true), Category("Behavior"), DefaultValue("Error")]
        [Description("Indicates the text string to display when the Indicator Value is under the set MinValue range.")]
        public string UnderRangeString
        {
            get { return (_UnderRangeString); }
            set
            {
                if (_UnderRangeString != value)
                {
                    _UnderRangeString = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region UnderScale
        /// 
        /// Gets or sets whether the indicator is displayed under the scale
        /// 
        [Browsable(true)]
        [Category("Layout"), DefaultValue(true)]
        [Editor("DevComponents.Instrumentation.Design.AngleRangeValueEditor, DevComponents.Instrumentation.Design, Version=14.1.0.37, Culture=neutral,  PublicKeyToken=76cb4c6eb576bca5", typeof(UITypeEditor))]
        [Description("Indicates whether the indicator is displayed under the scale.")]
        public bool UnderScale
        {
            get { return (_UnderScale); }
            set
            {
                if (_UnderScale != value)
                {
                    _UnderScale = value;
                    OnGaugeItemChanged(true);
                }
            }
        }
        #endregion
        #region Value
        /// 
        /// Gets or sets the indicator value
        /// 
        [Browsable(true)]
        [Category("Behavior"), DefaultValue(double.NaN)]
        [Description("Indicates the indicator value.")]
        public double Value
        {
            get { return (_Value); }
            set
            {
                if (_Value != value)
                {
                    _Value = value;
                    OnValueChanged(true);
                }
            }
        }
        #endregion
        #region ValueEx
        /// 
        /// Gets or sets the value of the indicator - but with no dampening
        /// 
        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public double ValueEx
        {
            get { return (_Value); }
            set
            {
                if (_Value != value)
                {
                    _Value = value;
                    OnValueChanged(false);
                }
            }
        }
        #endregion
        #endregion
        #region Internal properties
        #region AbsFont
        // ReSharper disable ValueParameterNotUsed
        internal virtual Font AbsFont
        {
            get { return (Font); }
            set { }
        }
        // ReSharper restore ValueParameterNotUsed
        #endregion
        #region Center
        internal Point Center
        {
            get { return (_Center); }
        }
        #endregion
        #region DValue
        internal double DValue
        {
            get { return (_DValue); }
            set
            {
                _DValue = value;
                if (GaugeControl != null)
                    GaugeControl.DValueChange = true;
                OnGaugeItemChanged(true);
            }
        }
        #endregion
        #endregion
        #region Event processing
        #region BackColor processing
        void BackColorColorTableChanged(object sender, EventArgs e)
        {
            OnGaugeItemChanged(true);
        }
        #endregion
        #endregion
        #region OnValueChanged
        internal virtual void OnValueChanged(bool dampen)
        {
            if (GaugeControl != null && Visible == true)
            {
                if (GaugeControl.InDesignMode == false)
                {
                    if (_DValue < _Value)
                    {
                        double start = Math.Max(_MinValue, _DValue);
                        if (start < _MaxValue)
                        {
                            if (StartDampening(dampen, start, Math.Min(_MaxValue, _Value)))
                                return;
                        }
                    }
                    else if (_DValue > _Value)
                    {
                        double start = Math.Min(_MaxValue, _DValue);
                        if (start > _MinValue)
                        {
                            if (StartDampening(dampen, start, Math.Max(_MinValue, _Value)))
                                return;
                        }
                    }
                }
            }
            DValue = _Value;
        }
        #endregion
        #region Dampening support
        #region StartDampening
        private bool StartDampening(bool dampen, double start, double end)
        {
            double dampenTime = _DampeningSweepTime;
            if (GaugeControl.IsHandleCreated &&
                dampen == true && dampenTime > 0)
            {
                _DStartValue = start;
                _DEndValue = end;
                _DeltaValue = (_MaxValue - _MinValue) / (dampenTime * 1000);
                _DStartTicks = DateTime.Now.Ticks;
                if (_Dampening == false)
                {
                    _Dampening = true;
                    _DRefreshTicks = _DStartTicks;
                    _GaugeControl.DampeningUpdate += DampeningUpdate;
                    _GaugeControl.StartDampening();
                }
                return (true);
            }
            return (false);
        }
        #endregion
        #region DampeningUpdate
        void DampeningUpdate(object sender, EventArgs e)
        {
            SetIndicatorValue();
            if (_DValue == _DEndValue)
            {
                if (_Dampening == true)
                {
                    _GaugeControl.DampeningUpdate -= DampeningUpdate;
                    _GaugeControl.StopDampening();
                    _Dampening = false;
                }
            }
        }
        #endregion
        #region SetIndicatorValue
        private void SetIndicatorValue()
        {
            long now = DateTime.Now.Ticks;
            double ms = new TimeSpan(now - _DStartTicks).TotalMilliseconds;
            double delta = _DeltaValue * ms;
            if (delta > 0)
            {
                double n = (_DValue <= _Value)
                               ? Math.Min(_DStartValue + delta, _DEndValue)
                               : Math.Max(_DStartValue - delta, _DEndValue);
                if (n == _DEndValue ||
                    new TimeSpan(now - _DRefreshTicks).TotalMilliseconds > 1000 / _RefreshRate)
                {
                    _DRefreshTicks = now;
                    DValue = n;
                }
            }
        }
        #endregion
        #endregion
        #region RecalcLayout
        public override void RecalcLayout()
        {
            base.RecalcLayout();
            bool autoCenter = _GaugeControl.Frame.AutoCenter;
            _Center = _GaugeControl.GetAbsPoint(_Location, autoCenter);
            Size size = _GaugeControl.GetAbsSize(_Size, autoCenter);
            _Bounds = new Rectangle(
                _Center.X - size.Width / 2, _Center.Y - size.Height / 2,
                size.Width, size.Height);
        }
        #endregion
        #region Contains
        internal virtual bool Contains(Point pt)
        {
            return (Bounds.Contains(pt));
        }
        #endregion
        #region GetOverrideString
        protected string GetOverrideString()
        {
            string s = null;
            if (String.IsNullOrEmpty(Text) == false)
                s = Text;
            else if (Value.Equals(double.NaN) == true)
                s = _EmptyString ?? "";
            else if (Value < MinValue)
                s = _UnderRangeString;
            else if (Value > MaxValue)
                s = _OverRangeString ?? "";
            return (s);
        }
        #endregion
        #region ProcessTemplateText
        protected override void ProcessTemplateText(
            GaugeControl gauge, StringBuilder sb, string key, string data)
        {
            if (key.Equals("Value") == true)
            {
                sb.Append(string.IsNullOrEmpty(data)
                              ? _Value.ToString()
                              : String.Format("{0:" + data + "}", _Value));
            }
            else
            {
                base.ProcessTemplateText(gauge, sb, key, data);
            }
        }
        #endregion
        #region CopyToItem
        public override void CopyToItem(GaugeItem copy)
        {
            GaugeIndicator c = copy as GaugeIndicator;
            if (c != null)
            {
                base.CopyToItem(c);
                c.AutoSize = _AutoSize;
                if (_BackColor != null)
                    c.BackColor = (GradientFillColor)_BackColor.Clone();
                c.DampeningSweepTime = _DampeningSweepTime;
                c.EmptyString = _EmptyString;
                if (_Font != null)
                    c.Font = (Font)_Font.Clone();
                c.Location = _Location;
                c.MaxValue = _MaxValue;
                c.MinValue = _MinValue;
                c.OverRangeString = _OverRangeString;
                c.RefreshRate = _RefreshRate;
                c.Size = _Size;
                c.Text = _Text;
                c.UnderRangeString = _UnderRangeString;
                c.UnderScale = _UnderScale;
                c.Value = _Value;
            }
        }
        #endregion
    }
}