using System;
using System.Collections.Generic;
using Ozeki.VoIP;

namespace _10_MultiSIPAccount
{
    class Softphone
    {
        ISoftPhone _softphone;

        SIPAccount _account;
        List<SIPAccount> _registeredSipAccounts;

        IPhoneLine _phoneLine;
        List<IPhoneLine> _availablePhoneLines;

        object _sync;

        public Softphone()
        {
            _softphone = SoftPhoneFactory.CreateSoftPhone(5000, 10000);
            _registeredSipAccounts = new List<SIPAccount>();
            _availablePhoneLines = new List<IPhoneLine>();

            _sync = new object();
        }

        /// <summary>
        /// Registers a SIP account to a pbx.
        /// </summary>
        /// <param name="registrationRequired"></param>
        /// <param name="displayName"></param>
        /// <param name="userName"></param>
        /// <param name="authenticationId"></param>
        /// <param name="registerPassword"></param>
        /// <param name="domainHost"></param>
        /// <param name="domainPort"></param>
        public void Register(bool registrationRequired, string displayName, string userName, string authenticationId, string registerPassword, string domainHost, int domainPort)
        {
            try
            {
                _account = new SIPAccount(registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost, domainPort);
                Console.WriteLine("\nCreating SIP account {0}", _account);

                _phoneLine = _softphone.CreatePhoneLine(_account);
                Console.WriteLine("Phoneline created.");

                _phoneLine.RegistrationStateChanged +=_phoneLine_RegistrationStateChanged;

                _softphone.RegisterPhoneLine(_phoneLine);

            }
            catch (Exception ex)
            {
                Console.WriteLine("Error during SIP registration: {0}", ex.Message);
                ReadRegisterInfos();
            }
        }

        void _phoneLine_RegistrationStateChanged(object sender, RegistrationStateChangedArgs e)
        {
            if (e.State == RegState.RegistrationSucceeded)
            {
                _registeredSipAccounts.Add(_account);
                _availablePhoneLines.Add(_phoneLine);
            }

            var handler = PhoneLineStateChanged;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>
        /// Asks for SIP account information, and tries to register.
        /// </summary>
        public void ReadRegisterInfos()
        {
            var registrationRequired = true;
            Console.WriteLine("\nPlease set up Your SIP account:\n");

            // Asks, if a registration is required to the PBX. The default value is true.
            Console.Write("Please set if the registration is required (true/false) (default: true): ");
            var regRequired = Read("Registration required", false);
            if (regRequired.ToLower() == "false" || regRequired.ToLower() == "no" ||
                regRequired.ToLower() == "n")
            {
                registrationRequired = false;
            }
            else
            {
                Console.WriteLine("Registration set to required.");
            }

            // The SIP account needs and authentication ID, and some names as well.
            Console.Write("Please set Your authentication ID: ");
            var authenticationId = Read("Authentication ID", true);

            // If the user only presses the Enter button, the username will be the same as the authentication ID
            Console.Write("Please set Your username (default: " + authenticationId + "): ");
            var userName = Read("Username", false);
            if (string.IsNullOrEmpty(userName))
                userName = authenticationId;

            // If the user only presses the Enter button, the display name will be the same as the authentication ID
            Console.Write("Please set Your name to be displayed (default: " + authenticationId + "): ");
            var displayName = Read("Display name", false);
            if (string.IsNullOrEmpty(displayName))
                displayName = authenticationId;

            // The registration password needs to be entered.
            Console.Write("Please set Your registration password: ");
            var registerPassword = Read("Password", true);

            // Domain name as a string, for example an IP adress.
            Console.Write("Please set the domain name: ");
            var domainHost = Read("Domain name", true);

            // Port number with the as 5060 default value.
            Console.Write("Please set the port number (default: 5060): ");
            int domainPort;
            string port = Read("Port", false);
            if (string.IsNullOrEmpty(port))
            {
                domainPort = 5060;
            }
            else
            {
                domainPort = Int32.Parse(port);
            }

            Register(registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost, domainPort);
        }

        /// <summary>
        /// Gets the amount of successfully registered SIP accounts.
        /// </summary>
        /// <returns></returns>
        public int GetSipAccountCount()
        {
            lock (_sync)
                return _registeredSipAccounts.Count;
        }

        /// <summary>
        /// Lists the successfully registered SIP accounts.
        /// </summary>
        public void ListSipAccounts()
        {
            lock (_sync)
            {
                if (_registeredSipAccounts.Count > 0)
                {
                    int i = 0;
                    foreach (var account in _registeredSipAccounts)
                    {
                        Console.WriteLine("{0}. account: ", ++i);
                        Console.WriteLine("Display name: {0}", account.DisplayName);
                        Console.WriteLine("User name: {0}", account.UserName);
                        Console.WriteLine("Domain host: {0}", account.DomainServerHost);
                        Console.WriteLine("Port number: {0}\n", account.DomainServerPort);
                    }
                }
            }
        }

        /// <summary>
        /// Gets the amount of available phone lines.
        /// </summary>
        /// <returns></returns>
        public int GetAvailablePhoneLineCount()
        {
            lock (_sync)
                return _availablePhoneLines.Count;
        }

        /// <summary>
        /// Gets the first available phone line from the list.
        /// </summary>
        /// <returns></returns>
        public IPhoneLine GetAvailablePhoneLine()
        {
            lock (_sync)
                return _availablePhoneLines[0];
        }

        /// <summary>
        /// Gets the phone line, selected by its username and domainHost
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="domainHost"></param>
        /// <returns></returns>
        public IPhoneLine GetAvailablePhoneLine(string userName, string domainHost)
        {
            lock (_sync)
            {
                foreach (var phoneLine in _availablePhoneLines)
                {
                    if (phoneLine.SIPAccount.UserName.Equals(userName) &&
                        phoneLine.SIPAccount.DomainServerHost.Equals(domainHost))
                    {
                        return phoneLine;
                    }
                }
                Console.WriteLine("No available phone line with those attributes! First available phone line is being selected.");
                return _availablePhoneLines[0];
            }
        }

        /// <summary>
        /// Lists the available phone lines.
        /// </summary>
        public void ListAvailablePhoneLines()
        {
            lock (_sync)
            {
                int i = 0;
                foreach (var availablePhoneLine in _availablePhoneLines)
                {
                    Console.WriteLine("{0}. account: ", ++i);
                    Console.WriteLine("Phone number with hostname: {0}", availablePhoneLine.SIPAccount);
                }
            }
        }

        /// <summary>
        /// Adds a phone line to the available phone line's list.
        /// </summary>
        /// <param name="phoneLine"></param>
        public void AddAvailablePhoneLine(IPhoneLine phoneLine)
        {
            lock (_sync)
                _availablePhoneLines.Add(phoneLine);
        }

        /// <summary>
        /// Removes a phone line from the available phone line's list.
        /// </summary>
        /// <param name="phoneLine"></param>
        public void RemoveAvailablePhoneLine(IPhoneLine phoneLine)
        {
            lock (_sync)
                _availablePhoneLines.Remove(phoneLine);
        }

        /// <summary>
        /// Creates a call object to the called member via the selected phone line.
        /// </summary>
        /// <param name="phoneLine"></param>
        /// <param name="member"></param>
        /// <returns></returns>
        public IPhoneCall CreateCall(IPhoneLine phoneLine, string member)
        {
            return _softphone.CreateCallObject(phoneLine, member);
        }

        public event EventHandler<RegistrationStateChangedArgs> PhoneLineStateChanged;

        string Read(string inputName, bool readWhileEmpty)
        {

            while (true)
            {
                string input = Console.ReadLine();

                if (!readWhileEmpty)
                    return input;

                if (!string.IsNullOrEmpty(input))
                    return input;

                Console.WriteLine(inputName + " cannot be empty.");
                Console.Write(inputName + ": ");
            }
        }

    }
}
