﻿/* 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.Collections.Generic;
using System.Text;
using XenAPI;
using System.Collections.ObjectModel;
using XenAdmin.Core;


namespace XenAdmin.Commands
{
    /// <summary>
    /// Performs the WLB recommendations for the Start-On, Resume-On and Migrate menu items.
    /// </summary>
    internal class WlbRecommendations
    {
        private readonly static log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private readonly ReadOnlyCollection<VM> _vms;
        private readonly Session _session;
        private bool _initialized;
        private readonly Dictionary<VM, Dictionary<XenRef<Host>, string[]>> _recommendations = new Dictionary<VM, Dictionary<XenRef<Host>, string[]>>();
        private bool _isError;

        /// <summary>
        /// Initializes a new instance of the <see cref="WlbRecommendations"/> class.
        /// </summary>
        /// <param name="vms">The VMs that the recommendations are required for.</param>
        /// <param name="session">The session.</param>
        public WlbRecommendations(IEnumerable<VM> vms, Session session)
        {
            Util.ThrowIfEnumerableParameterNullOrEmpty(vms, "vms");
            Util.ThrowIfParameterNull(session, "session");

            _vms = new ReadOnlyCollection<VM>(new List<VM>(vms));
            _session = session;
        }

        /// <summary>
        /// Calls VM.retrieve_wlb_recommendations for each of the VMs specified in the constructor.
        /// </summary>
        public void Initialize()
        {
            if (_initialized)
            {
                throw new InvalidOperationException("Already initialized");
            }

            _initialized = true;

            if (Helpers.WlbEnabled(_vms[0].Connection))
            {
                try
                {
                    foreach (VM vm in _vms)
                    {
                        _recommendations[vm] = VM.retrieve_wlb_recommendations(_session, vm.opaque_ref);
                    }
                }
                catch (Exception e)
                {
                    log.Error("Error getting WLB recommendations", e);
                    _isError = true;
                }
            }
            else
            {
                _isError = true;
            }
        }

        /// <summary>
        /// Gets a value indicating whether an exception was thrown when calling VM.retrieve_wlb_recommendations.
        /// </summary>
        /// <value><c>true</c> if an exception was thrown; otherwise, <c>false</c>.</value>
        public bool IsError
        {
            get
            {
                return _isError;
            }
        }

        private void Verify()
        {
            if (_isError)
            {
                throw new InvalidOperationException("There was an error getting the WLB recommendations.");
            }
            if (!_initialized)
            {
                throw new InvalidOperationException("Initialize() has not been called.");
            }
        }

        public Host GetOptimalServer(VM vm)
        {
            Verify();

            double highStars = 0;
            Host recHost = null;
            foreach (KeyValuePair<XenRef<Host>, string[]> rec in _recommendations[vm])
            {
                if (rec.Value.Length > 0 && rec.Value[0].Trim().ToLower() == "wlb")
                {
                    // WLB: stars equal to highStars handles when there are only two hosts and vm resides on a host with high stars
                    double stars = Helpers.ParseStringToDouble(rec.Value[1].Trim(), 0);
                    if ((stars >= highStars) && (rec.Key.opaque_ref != vm.resident_on.opaque_ref))
                    {
                        highStars = stars;
                        recHost = vm.Connection.Resolve(rec.Key);
                    }
                }
            }

            return recHost;
        }

        public WlbRecommendation GetStarRating(Host host)
        {
            Verify();

            List<double> starRatings = new List<double>();
            Dictionary<VM, bool> canExecutes = new Dictionary<VM, bool>();
            Dictionary<VM, string> cantExecuteReasons = new Dictionary<VM, string>();

            foreach (VM vm in _vms)
            {
                Host residentHost = vm.Connection.Resolve(vm.resident_on);
                string[] rec = new string[0];

                if (residentHost != null && residentHost.opaque_ref == host.opaque_ref)
                {
                    cantExecuteReasons[vm] = Messages.HOST_MENU_CURRENT_SERVER;
                    canExecutes[vm] = false;
                }
                else if (_recommendations[vm].TryGetValue(new XenRef<Host>(host.opaque_ref), out rec))
                {
                    if (rec.Length > 0 && rec[0].Trim().ToLower() == "wlb")
                    {
                        double stars = 0;
                        ParseStarRating(rec, out stars);
                        canExecutes[vm] = true;
                        starRatings.Add(stars);
                    }
                    else
                    {
                        cantExecuteReasons[vm] = new Failure(rec).ShortMessage;
                        canExecutes[vm] = false;
                    }
                }
                else
                {
                    cantExecuteReasons[vm] = FriendlyErrorNames.HOST_NOT_LIVE_SHORT;
                    canExecutes[vm] = false;
                }
            }

            double averageStarRating = 0.0;

            foreach (double s in starRatings)
            {
                averageStarRating += s;
            }
            averageStarRating /= starRatings.Count;

            return new WlbRecommendation(canExecutes, averageStarRating, cantExecuteReasons);
        }

        private static bool ParseStarRating(string[] rec, out double starRating)
        {
            // recommedation string[] format examples: 
            //    WLB; 0.0; zero_score_reason
            //    WLB; 3.4
            //    xapiError(such as the assert can boot failsthe);detail;detail

            starRating = 0;
            if (rec != null && rec.Length > 1 && rec[0].Trim().ToLower() == "wlb")
            {
                starRating = Helpers.ParseStringToDouble(rec[1], 0);
                return true;
            }
            return false;
        }

        internal class WlbRecommendation
        {
            public readonly Dictionary<VM, bool> CanExecuteByVM;
            public readonly double StarRating;
            public readonly Dictionary<VM, string> CantExecuteReasons;

            public WlbRecommendation(Dictionary<VM, bool> canExecuteByVM, double starRating, Dictionary<VM, string> cantExecuteReasons)
            {
                CanExecuteByVM = canExecuteByVM;
                StarRating = starRating;
                CantExecuteReasons = cantExecuteReasons;
            }
        }
    }
}
