using System.Collections.Generic;
using Renci.SshNet.Common;

namespace Renci.SshNet.Security
{
    /// <summary>
    /// Implements key support for host algorithm.
    /// </summary>
    public class KeyHostAlgorithm : HostAlgorithm
    {
        /// <summary>
        /// Gets the key.
        /// </summary>
        public Key Key { get; private set; }

        /// <summary>
        /// Gets the public key data.
        /// </summary>
        public override byte[] Data
        {
            get
            {
                return new SshKeyData(Name, Key.Public).GetBytes();
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
        /// </summary>
        /// <param name="name">Host key name.</param>
        /// <param name="key">Host key.</param>
        public KeyHostAlgorithm(string name, Key key)
            : base(name)
        {
            Key = key;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="HostAlgorithm"/> class.
        /// </summary>
        /// <param name="name">Host key name.</param>
        /// <param name="key">Host key.</param>
        /// <param name="data">Host key encoded data.</param>
        public KeyHostAlgorithm(string name, Key key, byte[] data)
            : base(name)
        {
            Key = key;

            var sshKey = new SshKeyData();
            sshKey.Load(data);
            Key.Public = sshKey.Keys;
        }

        /// <summary>
        /// Signs the specified data.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns>
        /// Signed data.
        /// </returns>
        public override byte[] Sign(byte[] data)
        {
            return new SignatureKeyData(Name, Key.Sign(data)).GetBytes();
        }

        /// <summary>
        /// Verifies the signature.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="signature">The signature.</param>
        /// <returns>
        ///   <c>True</c> is signature was successfully verifies; otherwise <c>false</c>.
        /// </returns>
        public override bool VerifySignature(byte[] data, byte[] signature)
        {
            var signatureData = new SignatureKeyData();
            signatureData.Load(signature);

            return Key.VerifySignature(data, signatureData.Signature);
        }

        private class SshKeyData : SshData
        {
            private byte[] _name;
            private IList<byte[]> _keys;

            public BigInteger[] Keys
            {
                get
                {
                    var keys = new BigInteger[_keys.Count];
                    for (var i = 0; i < _keys.Count; i++)
                    {
                        var key = _keys[i];
                        keys[i] = key.ToBigInteger();
                    }
                    return keys;
                }
                private set
                {
                    _keys = new List<byte[]>(value.Length);
                    foreach (var key in value)
                    {
                        _keys.Add(key.ToByteArray().Reverse());
                    }
                }
            }

            private string Name
            {
                get { return Utf8.GetString(_name, 0, _name.Length); }
                set { _name = Utf8.GetBytes(value); }
            }

            protected override int BufferCapacity
            {
                get
                {
                    var capacity = base.BufferCapacity;
                    capacity += 4; // Name length
                    capacity += _name.Length; // Name
                    foreach (var key in _keys)
                    {
                        capacity += 4; // Key length
                        capacity += key.Length; // Key
                    }
                    return capacity;
                }
            }

            public SshKeyData()
            {
            }

            public SshKeyData(string name, params BigInteger[] keys)
            {
                Name = name;
                Keys = keys;
            }

            protected override void LoadData()
            {
                _name = ReadBinary();
                _keys = new List<byte[]>();

                while (!IsEndOfData)
                {
                    _keys.Add(ReadBinary());
                }
            }

            protected override void SaveData()
            {
                WriteBinaryString(_name);

                foreach (var key in _keys)
                {
                    WriteBinaryString(key);
                }
            }
        }

        private class SignatureKeyData : SshData
        {
            /// <summary>
            /// Gets or sets the name of the algorithm as UTF-8 encoded byte array.
            /// </summary>
            /// <value>
            /// The name of the algorithm.
            /// </value>
            private byte[] AlgorithmName { get; set; }

            /// <summary>
            /// Gets or sets the signature.
            /// </summary>
            /// <value>
            /// The signature.
            /// </value>
            public byte[] Signature { get; private set; }

            /// <summary>
            /// Gets the size of the message in bytes.
            /// </summary>
            /// <value>
            /// The size of the messages in bytes.
            /// </value>
            protected override int BufferCapacity
            {
                get
                {
                    var capacity = base.BufferCapacity;
                    capacity += 4; // AlgorithmName length
                    capacity += AlgorithmName.Length; // AlgorithmName
                    capacity += 4; // Signature length
                    capacity += Signature.Length; // Signature
                    return capacity;
                }
            }

            public SignatureKeyData()
            {
            }

            public SignatureKeyData(string name, byte[] signature)
            {
                AlgorithmName = Utf8.GetBytes(name);
                Signature = signature;
            }

            /// <summary>
            /// Called when type specific data need to be loaded.
            /// </summary>
            protected override void LoadData()
            {
                AlgorithmName = ReadBinary();
                Signature = ReadBinary();
            }

            /// <summary>
            /// Called when type specific data need to be saved.
            /// </summary>
            protected override void SaveData()
            {
                WriteBinaryString(AlgorithmName);
                WriteBinaryString(Signature);
            }
        }
    }
}
