namespace PingLogger
{
    #region Using
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.IO;
    using System.Net;
    using System.Net.NetworkInformation;
    #endregion

    public class Program
    {
        public static object sync = new object();

        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine("Надо указать как минимум 2 параметра:");
                Console.WriteLine("1. Путь к папке где хранить логи");
                Console.WriteLine("2. IP-адрес 1");
                Console.WriteLine("  ...  ");
                Console.WriteLine("n. IP-адрес n");
                Console.WriteLine("Например: PingLogger.exe d:\\PingLogs\\ localhost ya.ru 8.8.8.8 smfd.ru");
                Console.ReadKey();

                return;
            }

            string logPath = args[0];

            if (!Directory.Exists(logPath))
            {
                try { Directory.CreateDirectory(logPath); }
                catch
                {
                    Console.WriteLine("Путь к папке указан неверно! " + logPath);
                    Console.ReadKey();

                    return;
                }
            }

            Dictionary<IPAddress, string> ipAddrs = new Dictionary<IPAddress, string>();
            List<string> errors = new List<string>();

            for (int i = 1; i < args.Length; i++)
            {
                IPAddress ipAddr;
                bool isHostName = false;

                if (!IPAddress.TryParse(args[i], out ipAddr))
                {
                    IPHostEntry ipHost = null;

                    try
                    {
                        ipHost = Dns.Resolve(args[i]);
                        ipAddr = ipHost.AddressList.First(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
                        isHostName = true;
                    }
                    catch (System.Net.Sockets.SocketException ex)
                    {
                        errors.Add(args[i] + " не является ни IP-адресом, ни правильным именем хоста, или не удалось получить IP-адрес по имени хоста");
                    }
                }

                if (ipAddr != null)
                {
                    if (!ipAddrs.ContainsKey(ipAddr))
                    {
                        if (isHostName)
                            ipAddrs.Add(ipAddr, args[i]);
                        else
                            ipAddrs.Add(ipAddr, string.Empty);
                    }
                    else
                    {
                        if (isHostName && string.IsNullOrEmpty(ipAddrs[ipAddr])) // если ип уже есть но без имени хоста, добавим имя
                            ipAddrs[ipAddr] = args[i];
                        else if (isHostName && !ipAddrs[ipAddr].Contains(args[i])) // если ип уже есть но имя хоста/ов не совпадает, добавим имя
                            ipAddrs[ipAddr] = string.Format("{0}, {1}", ipAddrs[ipAddr], args[i]);
                    }
                }
            }

            if (errors.Count > 0)
            {
                Console.WriteLine();
                Console.WriteLine("Во входных параметрах есть ошибки:");

                foreach (string error in errors)
                    Console.WriteLine(error);

                if (ipAddrs.Count == 0)
                {
                    Console.ReadKey();

                    return;
                }

                Console.WriteLine();
                Console.WriteLine("Продолжить выполнение программы? (Y = да)");
                
                if (Console.ReadKey().Key != ConsoleKey.Y)
                    return;
            }

            Console.Title = "PingLogger          <Shift>+<S> => остановить пинг";

            CancellationTokenSource cancelTokenSource = new CancellationTokenSource();

            foreach (KeyValuePair<IPAddress, string> ipAddr in ipAddrs)
            {
                string ip = ipAddr.Key.ToString();
                string hostName = ipAddr.Value;

                Task.Factory.StartNew(() =>
                {
                    new Pinger(ip, hostName, logPath).Ping(cancelTokenSource.Token);
                }, cancelTokenSource.Token);
            }

            while (true)
            {
                ConsoleKeyInfo ci = Console.ReadKey();

                if (ci.Key == ConsoleKey.S && ci.Modifiers == ConsoleModifiers.Shift)
                    cancelTokenSource.Cancel();
            }
        }
    }

    class Pinger
    {
        string ipAddr;
        string hostName;
        string logPath;
        int pingAttempts;
        int pingCount;
        long pingTime;

        public Pinger(string ipAddr, string hostName, string logPath)
        {
            this.ipAddr = ipAddr;
            this.hostName = hostName;
            this.logPath = logPath;
            this.pingAttempts = 0;
            this.pingCount = 0;
            this.pingTime = 0;
        }

        public void Ping(CancellationToken cancelToken)
        {
            Ping ping = new Ping();
            PingReply pingR = null;
            bool cancelled = false;

            while (true)
            {
                string fileName = string.Empty;

                if(string.IsNullOrEmpty(hostName))
                    fileName = Path.Combine(this.logPath, string.Format("{0} {1:dd.MM.yyyy}.txt", this.ipAddr, DateTime.Now));
                else
                    fileName = Path.Combine(this.logPath, string.Format("{0} ({1}) {2:dd.MM.yyyy}.txt", this.hostName, this.ipAddr, DateTime.Now));

                StringBuilder logMessage = new StringBuilder();
                try
                {
                    cancelToken.ThrowIfCancellationRequested();

                    this.pingAttempts++;

                    pingR = null;

                    logMessage.Append(string.Format("{0:hh:mm:ss} > Ответ от {1}: ", DateTime.Now, this.ipAddr));

                    pingR = ping.Send(this.ipAddr);

                    if (pingR.Status == IPStatus.Success)
                    {
                        logMessage.Append(string.Format("{0} мс", (pingR.RoundtripTime == 0 ? "<1" : pingR.RoundtripTime.ToString())));
                        this.pingCount++;
                        this.pingTime += pingR.RoundtripTime;
                    }
                    else
                        logMessage.Append("нет ответа");
                }
                catch (OperationCanceledException) { cancelled = true; }
                catch (PingException) { logMessage.Append("нет ответа"); }

                if (cancelled)
                {
                    lock (Program.sync)
                    {
                        string host = this.ipAddr;

                        if(!string.IsNullOrEmpty(this.hostName))
                            host = string.Format("{0} ({1})", this.hostName, host);

                        string resultMessage1 = string.Format("Результаты эхо-запроса для {0}:", host);
                        string resultMessage2 = string.Format("Отправлено: {0}, получено: {1}, % ответа: {2:F2}%, среднее время отклика: {3} мс",
                            this.pingAttempts,
                            this.pingCount,
                            this.pingCount != 0 ? ((double)this.pingCount * 100) / (double)this.pingAttempts : 0,
                            this.pingCount != 0 ? Math.Round(((double)this.pingTime / (double)this.pingCount)) : 0);

                        logMessage.AppendLine();
                        logMessage.AppendLine(string.Format("{0:hh:mm:ss} {1}", DateTime.Now, resultMessage1));
                        logMessage.AppendLine(resultMessage2);

                        using (StreamWriter w = File.AppendText(fileName))
                            w.WriteLine(logMessage.ToString());

                        Console.WriteLine();
                        Console.WriteLine(resultMessage1);
                        Console.WriteLine(resultMessage2);
                    }
                    break;
                }

                lock (Program.sync)
                {
                    if (!File.Exists(fileName))
                        File.Create(fileName).Close();

                    if (pingR != null && pingR.Status == IPStatus.Success) // Все норм
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.Write("#");
                    }
                    else if (pingR != null && (pingR.Status == IPStatus.TimedOut || pingR.Status == IPStatus.DestinationHostUnreachable)) // Нет ответа или хост недоступен
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("#");
                    }
                    else // Нет ответа по неясным причинам или ошибка
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write("!");
                    }

                    using (StreamWriter w = File.AppendText(fileName))
                        w.WriteLine(logMessage.ToString());

                    Console.ResetColor();
                }

                Thread.Sleep(1000);
            }
        }
    }
}
