using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace IPCamLib.Base
{
    /// <summary>
    /// Abstract base class for IP cameras reading an HTTP stream.
    /// </summary>
    public abstract class BaseHTTPStreamCamera : ICamera
    {
        private readonly Uri streamUri;


        /// <param name="streamUri">The URI to the camera stream.
        /// Can include basic credentials in the standard 'username:password@' format.</param>
        protected BaseHTTPStreamCamera(Uri streamUri)
        {
            this.streamUri = streamUri;
        }


        /// <inheritdoc />
        public async Task Fetch(ICameraObserver observer, CancellationToken cancellationToken)
        {
            var request = WebRequest.CreateHttp(streamUri);

            if (!string.IsNullOrEmpty(streamUri.UserInfo))
            {
                var parts = streamUri.UserInfo.Split(':');
                request.Credentials = new NetworkCredential(parts[0], parts.Length > 1 ? parts[1] : "");
            }

            request.ReadWriteTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
            try
            {
                HttpWebResponse response;

                using (cancellationToken.Register(() => request.Abort(), false))
                {
                    response = (HttpWebResponse)await request.GetResponseAsync();
                    cancellationToken.ThrowIfCancellationRequested();
                }


                if (response.StatusCode != HttpStatusCode.OK)
                    throw new WebException(response.StatusDescription);

                
                using var responseStream = response.GetResponseStream();
                await ReadFrames(observer, responseStream, cancellationToken);
            }
            catch (TaskCanceledException)
            {
            }
            finally
            {
                request.Abort();
            }
        }


        /// <summary>
        /// Implement in concrete descendants to continuously read frames from the HTTP stream.
        /// </summary>
        /// <param name="observer">The observer implementation passed to Fetch which should receive the decoded frames.</param>
        /// <param name="stream">The HTTP response stream.</param>
        /// <param name="cancellationToken">Stop reading frames when this token is cancelled.</param>
        protected abstract Task ReadFrames(ICameraObserver observer, Stream stream, CancellationToken cancellationToken);
    }
}
