// 
// Copyright (c) 2013-2016 Carsten Sonne Larsen <cs@innolan.dk>
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using NPlot;
using Ntp.Analyzer.Data;
using Ntp.Analyzer.Data.Sql;
using Ntp.Analyzer.Interface;
using Ntp.Analyzer.Objects;

namespace Ntp.Analyzer.Graph
{
    public sealed class TrafficGraph : GraphBase
    {
        public TrafficGraph(ITrafficGraphConfiguration configuratation, Host host)
            : base(configuratation)
        {
            config = configuratation;
            this.host = host;
        }

        private readonly ITrafficGraphConfiguration config;
        private readonly List<double> dropped = new List<double>();
        private readonly List<double> droppedAvg = new List<double>();
        private readonly Host host;
        private readonly List<double> ignored = new List<double>();
        private readonly List<double> ignoredAvg = new List<double>();
        private readonly List<double> notSent = new List<double>();
        private readonly List<double> notSentAvg = new List<double>();
        private readonly List<double> received = new List<double>();
        private readonly List<double> receivedAvg = new List<double>();
        private readonly List<double> receivedAvgTot = new List<double>();
        private readonly List<double> sent = new List<double>();
        private readonly List<double> sentAvg = new List<double>();
        private readonly List<DateTime> time = new List<DateTime>();

        protected override string YLabel
        {
            get
            {
                string rate;

                switch (config.PacketRate)
                {
                    case 60:
                        rate = "second";
                        break;
                    case 1:
                        rate = "minute";
                        break;
                    default:
                        rate = config.PacketRate > 60
                            ? "second/" + config.PacketRate/60
                            : "minute/" + config.PacketRate;
                        break;
                }

                return $"Packets per {rate}";
            }
        }

        protected override void AddPlots()
        {
            if (config.Received.HasValue)
            {
                var receivedPlot = SetupPlot("Packets received" + FText(config.Received.Value), Color.Green, time,
                    received);
                Surface.Add(receivedPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }

            if (config.ReceivedAvg.HasValue)
            {
                var receivedAvgPlot = SetupPlot(
                    "Packets received accumulated" + FText(config.ReceivedAvg.Value),
                    Color.Orange,
                    time,
                    receivedAvg);

                Surface.Add(receivedAvgPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);

                var receivedAvgTotPlot = SetupPlot(
                    "Packets received since reset" + FText(config.ReceivedAvg.Value),
                    Color.Purple,
                    time,
                    receivedAvgTot);

                Surface.Add(receivedAvgTotPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }

            if (config.Dropped.HasValue)
            {
                var droppedPlot = SetupPlot("Packets dropped" + FText(config.Dropped.Value), Color.Red, time, dropped);
                Surface.Add(droppedPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }

            if (config.Ignored.HasValue)
            {
                var ignoredPlot = SetupPlot("Packets ignored" + FText(config.Ignored.Value), Color.Blue, time, ignored);
                Surface.Add(ignoredPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }

            if (config.Sent.HasValue)
            {
                var sentPlot = SetupPlot("Packets sent" + FText(config.Sent.Value), Color.Black, time, sent);
                Surface.Add(sentPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }

            if (config.NotSent.HasValue)
            {
                var notSentPlot = SetupPlot("Packets not sent" + FText(config.NotSent.Value), Color.DarkRed, time,
                    notSent);
                Surface.Add(notSentPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }
        }

        protected override void LoadData()
        {
            FilteredSqlDatabaseMapper<HostIoReading> dataMapper = DataFace.Instance.HostIoReadings;
            dataMapper.FilterHost = host;
            dataMapper.FilterTime = DateTime.UtcNow.Subtract(GraphTimeSpan);

            var startTime = DateTime.MinValue;

            var firstTime = DateTime.MinValue;
            long firstRecieved = 0;
            long firstIgnored = 0;
            long firstDropped = 0;
            long firstSent = 0;
            long firstNotSent = 0;

            var lastTime = DateTime.MinValue;
            long lastReceived = 0;
            long lastIgnored = 0;
            long lastDropped = 0;
            long lastSent = 0;
            long lastNotSent = 0;

            bool firstRun = true;
            int disFactor = config.PacketRate;
            int interval = config.PlotInterval*config.Timespan;

            foreach (var reading in dataMapper)
            {
                bool readNext = false;
                var readingTime = config.GraphTime == DateTimeKind.Local
                    ? reading.RoundedLocalTime
                    : reading.RoundedUtcTime;

                if (!firstRun)
                {
                    double minutes = readingTime.Subtract(lastTime).TotalMinutes;
                    double totalMinutes = readingTime.Subtract(firstTime).TotalMinutes;
                    double minutesSinceReset = readingTime.Subtract(startTime).TotalMinutes;

                    if (minutes >= interval)
                    {
                        time.Add(readingTime);

                        received.Add((reading.ReceivedPackets - lastReceived)*config.Received/minutes/disFactor ?? 0.0);
                        ignored.Add((reading.IgnoredPackets - lastIgnored)*config.Ignored/minutes/disFactor ?? 0.0);
                        dropped.Add((reading.DroppedPackets - lastDropped)*config.Dropped/minutes/disFactor ?? 0.0);
                        sent.Add((reading.PacketsSent - lastSent)*config.Sent/minutes/disFactor ?? 0.0);
                        notSent.Add((reading.PacketsNotSent - lastNotSent)*config.NotSent/minutes/disFactor ?? 0.0);
                        receivedAvg.Add((reading.ReceivedPackets - firstRecieved)*config.ReceivedAvg/totalMinutes/
                                        disFactor ?? 0.0);

                        // TODO
                        if (config.ReceivedAvg.HasValue)
                            receivedAvgTot.Add(reading.ReceivedPackets*config.ReceivedAvg.Value/minutesSinceReset/
                                               disFactor);
                        else
                            receivedAvgTot.Add(0.0);

                        if (config.IgnoredAvg.HasValue)
                            ignoredAvg.Add((reading.IgnoredPackets - firstIgnored)*config.IgnoredAvg.Value/totalMinutes/
                                           disFactor);
                        else
                            ignoredAvg.Add(0.0);

                        if (config.DroppedAvg.HasValue)
                            droppedAvg.Add((reading.DroppedPackets - firstDropped)*config.DroppedAvg.Value/totalMinutes/
                                           disFactor);
                        else
                            droppedAvg.Add(0.0);

                        if (config.SentAvg.HasValue)
                            sentAvg.Add((reading.PacketsSent - firstSent)*config.SentAvg.Value/totalMinutes/disFactor);
                        else
                            sentAvg.Add(0.0);

                        if (config.NotSentAvg.HasValue)
                            notSentAvg.Add((reading.PacketsNotSent - firstNotSent)*config.NotSentAvg.Value/totalMinutes/
                                           disFactor);
                        else
                            notSentAvg.Add(0.0);

                        readNext = true;
                    }
                }
                else
                {
                    int hours = reading.TimeSinceReset/(60*60);
                    int mins = (reading.TimeSinceReset - hours*60*60)/60;
                    int seconds = reading.TimeSinceReset - hours*60*60 - mins*60;
                    var span = new TimeSpan(hours, mins, seconds);
                    startTime = readingTime.Subtract(span);

                    firstTime = readingTime;
                    firstRecieved = reading.ReceivedPackets;
                    firstIgnored = reading.IgnoredPackets;
                    firstDropped = reading.DroppedPackets;
                    firstSent = reading.PacketsSent;
                    firstNotSent = reading.PacketsNotSent;
                }

                if (firstRun || readNext)
                {
                    lastTime = readingTime;
                    lastReceived = reading.ReceivedPackets;
                    lastIgnored = reading.IgnoredPackets;
                    lastDropped = reading.DroppedPackets;
                    lastSent = reading.PacketsSent;
                    lastNotSent = reading.PacketsNotSent;
                }

                firstRun = false;
            }
        }

        protected override void PreRender()
        {
            base.PreRender();

            Surface.YAxis1.NumberFormat = "{0:####0}";
        }

        private static string FText(double value)
        {
            return Math.Abs(value - 1.0) > 0.0
                ? " x " + value.ToString("0", CultureInfo.InvariantCulture)
                : string.Empty;
        }
    }
}