// Created with MonoDevelop
//
//    Copyright (C) 2020 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.Linq;
using Hiperscan.Common.SearchIpAddress;

namespace Hiperscan.SGS
{
    public class RemoteDevices
    {
        public event EventHandler BeginSearch = null;
        public event EventHandler EndSearch = null;

        public event EventHandler BeginRemoteSearch = null;
        public event EventHandler PingSearchProgressed = null;

        public RemoteDevices(DeviceList device_list = null,
                            bool only_remote_devices = false,
                             bool only_first_device = false)
        {
            this.DeviceList = device_list;
            this.SearchOnlyRemoteDevices = only_remote_devices;
            this.OnlyFirstDevice = only_first_device;

            if (this.DeviceList == null)
                this.DeviceList = new DeviceList(DeviceList.DEFAULT_USB_CONFIGS.ToList());

        }

        public void SearchDevices(SearchIPAdr.Parameter parameter)
        { 
            Console.WriteLine("Search devices ...");
            this.SearchParameter = parameter;
            if (this.OnlyFirstDevice == true)
                this.SearchParameter.Restrictions = SearchIPAdr.SearchRestrictions.OnlyFirstDevice;

            if (SearchParameter.Restrictions != SearchIPAdr.SearchRestrictions.OnlyFirstDevice)
            {
                if (this.SearchParameter.UserdefinedAddress != null && this.SearchParameter.LastChanceAddress != null)
                    throw new Exception("Invalid definition of search ip parameter.");
            }

            this.RemoveRemoteDevices();
            this.Addresses.Clear();
            this.DeviceList.Blacklist.Clear();

            this.BeginSearch?.Invoke(this, EventArgs.Empty);

            this.SearchLocalUSBDevices();
            this.SearchByUserdefinedAddress();
            this.SearchRemoteDevices();
            this.SearchByLastChanceAddress();

            this.AddRemoteDevices();

            this.EndSearch?.Invoke(this, EventArgs.Empty);
        }

        private void SearchLocalUSBDevices()
        {
            if (this.SearchOnlyRemoteDevices == true)
                return;

            Console.WriteLine("Try to connect to usb devices ...");

            lock (this.DeviceList)
            {
                this.DeviceList.Update();  
            }
        }

        private void SearchRemoteDevices()
        {
            if (this.OnlyFirstDevice == true && (this.HasDevices == true || this.HasAddresses))
                return;

            this.BeginRemoteSearch?.Invoke(this, EventArgs.Empty);
            Console.WriteLine("Try to connect to remote devices (mdns) ...");

            SearchIPAdr searcher = new SearchIPAdr();
            searcher.PingProgressed += this.OnPingSearchProgressed;

            foreach (SearchIPAdr.FullAddress address in searcher.Run(this.SearchParameter, this.IsOsWindows))
                this.Addresses.Add(address);
        }

        private void SearchByUserdefinedAddress()
        {
            if (this.HasDevices)
                return;

            if (this.SearchParameter.UserdefinedAddress == null)
                return;

            Console.WriteLine("Try to connect to remote devices (predefined address) ...");
            this.Addresses.Add(this.SearchParameter.UserdefinedAddress);
        }

        private void SearchByLastChanceAddress()
        {
            if (this.HasDevices == true || this.HasAddresses)
                return;

            if (this.SearchParameter.LastChanceAddress == null)
                return;

            Console.WriteLine("Try to connect to remote devices (last chance address) ...");
            this.Addresses.Add(this.SearchParameter.LastChanceAddress);
        }

        private void AddRemoteDevices()
        {
            lock (this.DeviceList)
            {
                foreach (SearchIPAdr.FullAddress full_address in this.Addresses)
                    this.DeviceList.AddRemoteDevice(full_address.Address, full_address.Port);
                this.DeviceList.Update();
            }
        }

        public void RemoveRemoteDevices()
        { 
            foreach (IDevice dev in this.DevicesRemote)
                this.DeviceList.RemoveRemoteDevice(dev);
        }

        private void OnPingSearchProgressed(object sender, EventArgs e)
        {
            this.PingSearchProgressed?.Invoke(sender, e);
        }

        private bool IsOsWindows
        {
            get
            {
                return Environment.OSVersion.Platform != PlatformID.MacOSX &&
                                  Environment.OSVersion.Platform != PlatformID.Unix;
            }
        }

        private List<IDevice> DevicesRemote
        {
            get
            {
                return new List<IDevice>(this.DeviceList.Values.Select(x => x).Where(x => x.Info.IsRemote));
            }
        }

        private bool HasDevices
        {
            get
            {
                if (this.SearchOnlyRemoteDevices)
                    return this.DevicesRemote.Any();

                return this.DeviceList.Values.Any();
            }
        }

        private bool HasAddresses
        {
            get
            {
                return this.Addresses.Any();
            }
        }

        public IDevice FirstDevice
        {
            get
            {
                if (this.SearchOnlyRemoteDevices)
                    return this.DevicesRemote.FirstOrDefault();
                else
                    return this.DeviceList.Values.FirstOrDefault();
            }
        }

        public List<IDevice> AllDevices
        {
            get
            {
                List<IDevice> list = new List<IDevice>();
                if (this.OnlyFirstDevice == true && this.HasDevices)
                {
                    list.Add(this.FirstDevice);
                    return list;
                }

                if (this.SearchOnlyRemoteDevices)
                    list.AddRange(this.DevicesRemote);
                else
                    list.AddRange(this.DeviceList.Values);

                return list;
            }
        }

        public SearchIPAdr.FullAddress LastUsedRemoteAdress
        {
            get
            {
                return null;
            }
        }

        private HashSet<SearchIPAdr.FullAddress> Addresses { get; set; } = new HashSet<SearchIPAdr.FullAddress>();
        private SearchIPAdr.Parameter SearchParameter { get; set; } = new SearchIPAdr.Parameter();

        public bool SearchOnlyRemoteDevices { get; set; } = false;
        public bool OnlyFirstDevice { get; set; } = false;

        public DeviceList DeviceList { get; private set; }
    }
}
