//
// TrafficGraph.cs
//
// Author:
//       Carsten Sonne Larsen <cs@innolan.dk>
//
// Copyright (c) 2013-2016 Carsten Sonne Larsen
//
// 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>();

        // TODO: Adjust logic
        protected override string YLabel
        {
            get
            {
                string rate;

                if (config.PacketRate == 60)
                {
                    rate = "second";
                }
                else if (config.PacketRate == 1)
                {
                    rate = "minute";
                }
                else if (config.PacketRate > 60)
                {
                    rate = "second/" + (config.PacketRate/60);
                }
                else
                {
                    rate = "minute/" + config.PacketRate;
                }

                return "Packets per " + rate;
            }
        }

        protected override void LoadData()
        {
            FilteredSqlDatabaseMapper<HostIoReading> dataMapper = DataFace.Instance.HostIoReadings;
            dataMapper.FilterHost = host;
            // TODO: Adjust with offset if hostpage shows time in UTC
            dataMapper.FilterTime = DateTime.Now.Subtract(GraphTimeSpan);

            DateTime startTime = DateTime.MinValue;

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

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

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

            foreach (HostIoReading reading in dataMapper)
            {
                bool readNext = false;

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

                    if (minutes >= interval)
                    {
                        time.Add(reading.Time);
                        received.Add((reading.ReceivedPackets - lastReceived)*config.Received/minutes/disFactor);
                        ignored.Add((reading.IgnoredPackets - lastIgnored)*config.Ignored/minutes/disFactor);
                        dropped.Add((reading.DroppedPackets - lastDropped)*config.Dropped/minutes/disFactor);
                        sent.Add((reading.PacketsSent - lastSent)*config.Sent/minutes/disFactor);
                        notSent.Add((reading.PacketsNotSent - lastNotSent)*config.NotSent/minutes/disFactor);

                        receivedAvg.Add((reading.ReceivedPackets - firstRecieved)*config.ReceivedAvg/totalMinutes/
                                        disFactor);
                        receivedAvgTot.Add(reading.ReceivedPackets*config.ReceivedAvg/minutesSinceReset/disFactor);

                        ignoredAvg.Add((reading.IgnoredPackets - firstIgnored)*config.IgnoredAvg/totalMinutes/disFactor);
                        droppedAvg.Add((reading.DroppedPackets - firstDropped)*config.DroppedAvg/totalMinutes/disFactor);
                        sentAvg.Add((reading.PacketsSent - firstSent)*config.SentAvg/totalMinutes/disFactor);
                        notSentAvg.Add((reading.PacketsNotSent - firstNotSent)*config.NotSentAvg/totalMinutes/disFactor);

                        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 = reading.Time.Subtract(span);

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

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

                    readNext = false;
                }

                firstRun = false;
            }
        }

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

            if (config.ReceivedAvg != 0.0)
            {
                LinePlot receivedAvgPlot = SetupPlot("Packets received accumulated" + FText(config.ReceivedAvg),
                    Color.Orange, time, receivedAvg);
                Surface.Add(receivedAvgPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);

                LinePlot receivedAvgTotPlot = SetupPlot("Packets received since reset" + FText(config.ReceivedAvg),
                    Color.Purple, time, receivedAvgTot);
                Surface.Add(receivedAvgTotPlot, PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Left);
            }

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

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

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

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

        private string FText(double value)
        {
            return value != 1.0 ? " x " + value.ToString("0", CultureInfo.InvariantCulture) : String.Empty;
        }

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

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