/* Copyright (c) Citrix Systems, Inc. 
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met: 
 * 
 * *   Redistributions of source code must retain the above 
 *     copyright notice, this list of conditions and the 
 *     following disclaimer. 
 * *   Redistributions in binary form must reproduce the above 
 *     copyright notice, this list of conditions and the 
 *     following disclaimer in the documentation and/or other 
 *     materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE.
 */

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace XenAdmin.Controls.Ballooning
{
    public partial class MemorySpinner : UserControl
    {
        public event EventHandler SpinnerValueChanged;
        private double valueMB;
        private string previousUnitsValue;
        private bool initializing = true;

        public MemorySpinner()
        {
            InitializeComponent();
            previousUnitsValue = Messages.VAL_GIGB;
        }

        public void Initialize(double amount, double static_max)
        {
            amount = Util.CorrectRoundingErrors(amount);

            Units = static_max <= Util.BINARY_GIGA ? Messages.VAL_MEGB : Messages.VAL_GIGB;
            ChangeSpinnerSettings();
            previousUnitsValue = Units;
            Initialize(amount, RoundingBehaviour.None);
        }

        public void Initialize(double amount, RoundingBehaviour rounding)
        {
            ValueMB = Util.ToMB(amount, rounding);
            setSpinnerValueDisplay(amount);
            initializing = false;
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
        public string Units
        {
            get
            {
                return SpinnerUnits.Text;
            }
            set
            {
                SpinnerUnits.Text = value;
            }
        }

        [Browsable(false)]
        public double Value
        {
            get
            {
                return ValueMB * Util.BINARY_MEGA;
            }
        }

        double ValueMB
        {
            get
            {
                return valueMB;
            }

            set
            {
                valueMB = value;
            }
        }

        private void setSpinnerValueDisplay(double value)
        {
            decimal newValue;
            if (Units == "GB")
            {
                newValue = (decimal)Util.ToGB(value, 1, RoundingBehaviour.Nearest);
            }
            else
            {
                newValue = (long)Util.ToMB(value, RoundingBehaviour.Nearest);
            }
            if (newValue < Spinner.Minimum)
                newValue = Spinner.Minimum;
            if (newValue > Spinner.Maximum)
                newValue = Spinner.Maximum;
            Spinner.Value = newValue;
        }

        public static void CalcMBRanges(double minBytes, double maxBytes, out double minMB, out double maxMB)
        {
            // Round ranges inwards to avoid bugs like CA-34487 and CA-34996
            minMB = Util.ToMB(minBytes, RoundingBehaviour.Up);
            maxMB = Util.ToMB(maxBytes, RoundingBehaviour.Down);
            if (minMB > maxMB)  // just in case...
            {
                minMB = Util.ToMB(minBytes, RoundingBehaviour.None);
                maxMB = Util.ToMB(maxBytes, RoundingBehaviour.None);
            }
        }

        public static void CalcGBRanges(double minBytes, double maxBytes, out double minGB, out double maxGB)
        {
            // Round ranges inwards to avoid bugs like CA-34487 and CA-34996
            minGB = Util.ToGB(minBytes, 1, RoundingBehaviour.Up);
            maxGB = Util.ToGB(maxBytes, 1, RoundingBehaviour.Down);
            if (minGB > maxGB)  // just in case...
            {
                minGB = Util.ToGB(minBytes, 1, RoundingBehaviour.None);
                maxGB = Util.ToGB(maxBytes, 1, RoundingBehaviour.None);
            }
        }

        public void SetRange(double min, double max)
        {
            if (min > max)
                return;  // Can happen when we are adjusting several simultaneously: can cause a stack overflow

            double spinnerMin, spinnerMax;
            if (Units == "MB")
            {                
                CalcMBRanges(min, max, out spinnerMin, out spinnerMax);
            }
            else
            {
                CalcGBRanges(min, max, out spinnerMin, out spinnerMax);               
            }
            Spinner.Minimum = (decimal)spinnerMin;
            Spinner.Maximum = (decimal)spinnerMax;
        }

        [Browsable(false)]
        public double Increment
        {
            get
            {
               return (double)Spinner.Increment;
            }
            set
            {
                if (Units == "MB")
                {
                    Spinner.Increment = (decimal)value / Util.BINARY_MEGA;                    
                }
                else
                {
                    // When the units are GB, we simply want the numbers to increase by 1 if the spinner value is greater than 10 GB
                    // and by 0.1 if smaller than 10 GB, this being the reason we ignore the given value.
                    if (valueMB * Util.BINARY_MEGA < 10 * Util.BINARY_GIGA)
                    {
                        Spinner.Increment = 0.1M;
                    }
                    else
                    {
                        Spinner.Increment = 1; 
                    }
                }
            }
        }

        private void Spinner_ValueChanged(object sender, EventArgs e)
        {
            // We do not want to modify the ValueMB if the user does not modify anything in the Spinner.Value. 
            // When the Memory Settings dialog is intiliazing and the units change because the new value is > 1 GB,
            // we do not want any changes to be applied to ValueMB.
            if (initializing)
              return;

            if (Units == "GB")
            {
                ValueMB = (double)Spinner.Value * Util.BINARY_KILO;
            }
            else
            {
                ValueMB = (double)Spinner.Value;
            }

            if (SpinnerValueChanged != null)
                SpinnerValueChanged(this, e);
        }

        private void Spinner_Leave(object sender, EventArgs e)
        {
            var num = sender as NumericUpDown;
            if (num != null)
                num.Text = num.Value.ToString();
        }

        private void ChangeSpinnerSettings()
        {
            if (Units == previousUnitsValue)
                return;

            if (Units == "GB")
            {
                SetRange((double)Spinner.Minimum * Util.BINARY_MEGA, (double)Spinner.Maximum * Util.BINARY_MEGA);
                Spinner.DecimalPlaces = 1;
            }
            else
            {
                SetRange((double)Spinner.Minimum * Util.BINARY_GIGA, (double)Spinner.Maximum * Util.BINARY_GIGA);
                Spinner.DecimalPlaces = 0;
            }
        }      
    }
}
