using Org.BouncyCastle.Security;
using Org.BouncyCastle.Tls;
using Org.BouncyCastle.Tls.Crypto;
using Org.BouncyCastle.Tls.Crypto.Impl.BC;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using BcTlsClient = Org.BouncyCastle.Tls.TlsClient;

namespace Cryville.Common.Network {
	public class TlsClient {
		readonly TlsClientProtocol _protocol;
		readonly BcTlsClient _tlsClient;
		public Stream Stream { get; private set; }
		public TlsClient(Stream baseStream, string hostname) {
			_protocol = new TlsClientProtocol(baseStream);
			_tlsClient = new InternalTlsClient(hostname, new BcTlsCrypto(new SecureRandom()));
		}

		public void Connect() {
			_protocol.Connect(_tlsClient);
			Stream = _protocol.Stream;
		}

		public void Close() {
			_protocol.Close();
		}

		private class InternalTlsClient : DefaultTlsClient {
			string _host;

			public InternalTlsClient(string host, TlsCrypto crypto) : base(crypto) {
				_host = host;
			}

			protected override ProtocolVersion[] GetSupportedVersions() {
				return ProtocolVersion.TLSv13.DownTo(ProtocolVersion.TLSv12);
			}

			protected override IList GetProtocolNames() {
				IList list = new ArrayList {
					ProtocolName.Http_1_1
				};
				return list;
			}

			private static readonly int[] supportedCipherSuites = {
				CipherSuite.TLS_AES_128_GCM_SHA256,
				CipherSuite.TLS_AES_256_GCM_SHA384,
				CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
				CipherSuite.TLS_AES_128_CCM_SHA256,
				CipherSuite.TLS_AES_128_CCM_8_SHA256,
			};
			protected override int[] GetSupportedCipherSuites() {
				return base.GetSupportedCipherSuites().Union(supportedCipherSuites).ToArray();
			}

			protected override IList GetSupportedSignatureAlgorithms() {
				var result = base.GetSupportedSignatureAlgorithms();
				result.Add(SignatureAndHashAlgorithm.ecdsa_brainpoolP256r1tls13_sha256);
				result.Add(SignatureAndHashAlgorithm.ecdsa_brainpoolP384r1tls13_sha384);
				result.Add(SignatureAndHashAlgorithm.ecdsa_brainpoolP512r1tls13_sha512);
				return result;
			}

			protected override IList GetSniServerNames() {
				return new ArrayList { new ServerName(0, Encoding.ASCII.GetBytes(_host)) };
			}

			public override TlsAuthentication GetAuthentication() {
				return new NullTlsAuthentication();
			}

			public override void NotifyAlertReceived(short alertLevel, short alertDescription) {
				Logger.Log("main", 0, "Network/TLS", "TLS Alert {0} {1}", alertLevel, alertDescription);
			}

			public override void NotifyServerVersion(ProtocolVersion serverVersion) {
				base.NotifyServerVersion(serverVersion);
				Logger.Log("main", 0, "Network/TLS", "NotifyServerVersion {0}", serverVersion);
			}

			public override void NotifySelectedCipherSuite(int selectedCipherSuite) {
				base.NotifySelectedCipherSuite(selectedCipherSuite);
				Logger.Log("main", 0, "Network/TLS", "NotifySelectedCipherSuite {0}", selectedCipherSuite);
			}
		}

		private class NullTlsAuthentication : TlsAuthentication {
			public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) {
				return null;
			}

			public void NotifyServerCertificate(TlsServerCertificate serverCertificate) {
				// Do nothing
			}
		}
	}
}
