// Created with MonoDevelop
//
//    SearchIPAddr: find spectrometer in local network
//    Copyright (C) 2020 A. Höpfner, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.InteropServices;
using Tmds.MDns;

namespace Hiperscan.Common.SearchIpAddress
{
    public partial class SearchIPAdr
    {
        public event EventHandler PingProgressed = null;

        private class IPAndMac
        {
            public string IP { get; set; }
            public string MAC { get; set; }
        }
        
        List<IPAndMac> ipAndMacs = null;
        List<string> devices = null;
        
        //List<string> connectedSsids = null;

        




        private int Search(string mask)
        {
            string gate_ip = NetworkGateway();
            string devname = "";
            //Extracting and pinging all other ip's.
            string[] array = gate_ip.Split('.');

            for (int i = 2; i <= 255; i++)
            {
                string ping_var = array[0] + "." + array[1] + "." + array[2] + "." + i;
                //time in milliseconds           
                devname = Ping(ping_var, 1, 100);

                if (devname.StartsWith(mask, StringComparison.InvariantCultureIgnoreCase))
                    devices.Add(devname);
            }
            return devices.Count;
        }

        private void SearchByPing()
        {
            //string gate_ip = ""; //NetworkGateway();
            string devname = "";

            var gate_ips = GetAllGateways();

            if( gate_ips.Count < 1)
                return;

            foreach( var gate_ip in gate_ips ) {
                //Extracting and pinging all other ip's.
                string[] array = gate_ip.Split('.');

                double progress = 0;
                for (int i = this.SearchParameter.PingSubNetStart; i <= this.SearchParameter.PingSubNetEnd; i++)
                {
                    string ping_var = array[0] + "." + array[1] + "." + array[2] + "." + i;
                    //time in milliseconds           
                    devname = Ping(ping_var, 1, this.SearchParameter.TimeoutMSec);
                    if ((devname != "") && (devname == ping_var)) 
                        devname = GetMacAddress(ping_var);
                    Console.WriteLine("Search: {0} - {1} ", ping_var, devname);

                    progress += 1.0 / (this.SearchParameter.PingSubNetEnd - this.SearchParameter.PingSubNetStart);
                    this.PingProgressed?.Invoke(progress, EventArgs.Empty);

                    if (this.SearchParameter.PingServiceTypes.Any(x => devname.StartsWith(x, StringComparison.InvariantCultureIgnoreCase)))
                    {
                        uint port = this.SelectPort(ping_var, 49152, 49160);
                        FullAddress full_address = new FullAddress(ping_var, port);
                        if (full_address.IsValid)
                            this.FullAddresses.Add(full_address);
                    }
                }
            }
        }

        public string Ping(string host, int attempts, int timeout)
        {
            Console.WriteLine("Search by Ping...");
            PingReply rpl;
            var ping = new Ping();
            string hostname;
       
            try
            {
                //System.Net.NetworkInformation.Ping ping = new System.Net.NetworkInformation.Ping();
                //ping.PingCompleted += new PingCompletedEventHandler(PingCompleted);
                //ping.SendAsync(host, timeout, host);
                for (int ii = 1; ii <= attempts; ii++)
                {
                    rpl = ping.Send(host, timeout * ii); 
                    if (rpl.Status == IPStatus.Success)
                    {
                        hostname = GetHostName(rpl.Address.ToString());
                        devices.Add(hostname);
                        //Console.WriteLine(rpl.Address.ToString() + " - " + hostname);
                        return hostname;
                    }
                }
            }
            catch
            {
                // Do nothing and let it try again until the attempts are exausted.
                // Exceptions are thrown for normal ping failurs like address lookup
                // failed.  For this reason we are supressing errors.
            }
            return "";
        }


        //You shouldn't forget to handle every ping response using an EventHandler. 
        //Hide Shrink   Copy Code

        private void PingCompleted(object sender, PingCompletedEventArgs e)
        {
            string ip = (string)e.UserState;
            if (e.Reply != null && e.Reply.Status == IPStatus.Success)
            {
                string hostname = GetHostName(ip);
                string macaddres = GetMacAddress(ip);
                string[] arr = new string[3];

                //store all three parameters to be shown on ListView
                arr[0] = ip;
                arr[1] = hostname;
                arr[2] = macaddres;

                devices.Add(hostname);
                Console.WriteLine(hostname);
            }
            else
            {
                Console.WriteLine(e.Reply.Status.ToString());
            }
        }


        private List<string> GetAllGateways()
        {
            IPAddress addres = null;
            var alist = new List<String>();

            var cards = NetworkInterface.GetAllNetworkInterfaces();
            if (cards.Length > 0 )
            {
                foreach (var card in cards)
                {
                    var props = card.GetIPProperties();
                    if (props == null)
                        continue;

                    var gateways = props.GatewayAddresses;
                    if ( gateways.Count < 1)
                        continue;

                    var gateway =
                        gateways[0]; //.FirstOrDefault(g => g.Address.AddressFamily.ToString() == "InterNetwork");
                    if (gateway == null)
                        continue;

                    addres = gateway.Address;
                    alist.Add(addres.ToString());
                    //break;
                };
            }

            return alist;
        }

        private string NetworkGateway()
        {
            string ip = null;
            var alist = GetAllGateways();

            foreach (NetworkInterface f in NetworkInterface.GetAllNetworkInterfaces())
            {
                if (f.OperationalStatus == OperationalStatus.Up)
                {
                    foreach (GatewayIPAddressInformation d in f.GetIPProperties().GatewayAddresses)
                    {
                        ip = d.Address.ToString();
                    }
                }
            }
            return ip;
        }

        private string GetHostName(string ipAddress)
        {
            try
            {
                IPHostEntry entry = Dns.GetHostEntry(ipAddress);
                if (entry != null)
                {
                    return entry.HostName;
                }
            }
            catch (SocketException ex)
            {
                Console.WriteLine(ex.Message.ToString());
            }
            return "";
        }


        //Get MAC address
        private string GetMacAddress(string ipAddress)
        {
           
            string macAddress = string.Empty;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                try
                {
                    System.Diagnostics.Process Process = new System.Diagnostics.Process();

                    Process.StartInfo.FileName = "/usr/sbin/arp";
                    Process.StartInfo.Arguments = "-a " + ipAddress;
                    Process.StartInfo.UseShellExecute = false;
                    Process.StartInfo.RedirectStandardOutput = true;
                    Process.StartInfo.CreateNoWindow = true;
                    Process.Start();
                    string strOutput = Process.StandardOutput.ReadToEnd();
                    string[] substrings = strOutput.Split('-');
                    if (substrings.Length < 8)
                    {
                        substrings = strOutput.Split(' ');
                        for (int ii = 0; ii < substrings.Length; ii++)
                            if (substrings[ii].Contains(":"))
                            {
                                macAddress = substrings[ii];
                                return macAddress;
                            }
                    }


                    if (substrings.Length >= 8)
                    {
                        macAddress = substrings[3].Substring(Math.Max(0, substrings[3].Length - 2))
                                     + ":" + substrings[4] + ":" + substrings[5] + ":" + substrings[6]
                                     + ":" + substrings[7] + ":"
                                     + substrings[8].Substring(0, 2);
                        return macAddress;
                    }

                    else
                    {
                        return "OWN Machine";
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("GetMacAddress(L) exception: " + ex.Message);
                    return String.Empty;
                }
            }
            else
            {
                try
                {
                    string smac = FindMacFromIPAddress(ipAddress);
                    Console.WriteLine("GetMacAddress: " + smac);
                    smac.Replace('-', ':');
                    return smac;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("GetMacAddress(W) exception: " + ex.Message);
                    return String.Empty;
                }
            }
        }

        public bool CheckPort( string hostUri, uint portNumber)
        {
            try
            {
                using (var client = new TcpClient(hostUri, (int) portNumber))
                    return true;
            }
            catch
            {
                Console.WriteLine("Error checking port:" + hostUri + ":" + portNumber.ToString());
                return false;
            }
        }

        public uint SelectPort(string hostUri, uint start, uint stop)
        {
            for( uint ii = start; ii <= stop; ii++)
            {
                if (CheckPort(hostUri, ii))
                    return ii;
            }

            return 0;
        }

        private static StreamReader ExecuteCommandLine(String file, String arguments = "")
        {
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.CreateNoWindow = true;
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            startInfo.FileName = file;
            startInfo.Arguments = arguments;

            Process process = Process.Start(startInfo);
            return process.StandardOutput;
        }
        
        private int InitializeGetIPsAndMac()
        {
            if (ipAndMacs != null)
                return 0;

            var arpStream = ExecuteCommandLine("arp", "-a");
            List<string> result = new List<string>();
            while (!arpStream.EndOfStream)
            {
                var line = arpStream.ReadLine().Trim();
                result.Add(line);
            }

            try
            {
                ipAndMacs = result.Where(x => !string.IsNullOrEmpty(x) && (x.Contains("dynami") || x.Contains("stati")))
                    .Select(x =>
                    {
                        string[] parts = x.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                        return new IPAndMac { IP = parts[0].Trim(), MAC = parts[1].Trim() };
                    }).ToList();
                return ipAndMacs.Count;

            }
            catch (Exception ex)
            {
                Console.WriteLine("InitializeGetIPsAndMac: " +ex.Message);
                return -1;
            }
        }
        
        public string FindIPFromMacAddress(string macAddress)
        {
            if( InitializeGetIPsAndMac() > 0 )
              return ipAndMacs.SingleOrDefault(x => x.MAC == macAddress).IP;
            return String.Empty;

        }

        public string FindMacFromIPAddress(string ip)
        {
            if (InitializeGetIPsAndMac() > 0)
                return ipAndMacs.SingleOrDefault(x => x.IP == ip).MAC;
            return String.Empty;
        }

        private void WaitForPingTimeOut()
        {
            DateTime end = DateTime.Now.AddMilliseconds(this.SearchParameter.TimeoutMSec);
            for (; ; )
            {
                if (this.IsPingSearchFinished == true)
                    break;

                if (DateTime.Now > end)
                    break;

                Thread.Sleep(50);
            }
        }

        private bool IsPingSearchFinished { get; set; } = true;
    }
}
