//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//See the NOTICE file distributed with this work for additional
//information regarding copyright ownership.
//You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.

//Portions Copyright(c) Microsoft Corporation

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.CompilerServices;

namespace CalculatorUITestFramework
{
    public class WindowsDriverLocalService : IDisposable
    {
        private FileInfo FileName;
        private string Arguments;
        private IPAddress IP;
        private int Port;
        private TimeSpan InitializationTimeout;
        private Process Service;

        public event DataReceivedEventHandler OutputDataReceived;

        internal WindowsDriverLocalService(
            FileInfo fileName,
            string arguments,
            IPAddress ip,
            int port,
            TimeSpan initializationTimeout)
        {
            this.FileName = fileName;
            this.Arguments = arguments;
            this.IP = ip;
            this.Port = port;
            this.InitializationTimeout = initializationTimeout;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Start()
        {
            if (this.IsRunning)
            {
                return;
            }

            this.Service = new Process();
            this.Service.StartInfo.FileName = FileName.FullName;
            this.Service.StartInfo.Arguments = Arguments;
            this.Service.StartInfo.UseShellExecute = false;
            this.Service.StartInfo.CreateNoWindow = true;

            this.Service.StartInfo.RedirectStandardOutput = true;
            this.Service.OutputDataReceived += (sender, e) => OutputDataReceived?.Invoke(this, e);

            bool isLaunched = false;
            string msgTxt =
                $"The local WinAppDriver server has not been started: {this.FileName.FullName} Arguments: {this.Arguments}. " +
                "\n";

            try
            {
                Service.Start();

                Service.BeginOutputReadLine();
            }
            catch (Exception e)
            {
                DestroyProcess();
                throw new Exception(msgTxt, e);
            }

            isLaunched = Ping();
            if (!isLaunched)
            {
                DestroyProcess();
                throw new Exception(
                    msgTxt +
                    $"Time {InitializationTimeout.TotalMilliseconds} ms for the service starting has been expired!");
            }
        }

        public bool IsRunning
        {
            get
            {
                if (this.Service == null)
                {
                    return false;
                }

                try
                {
                    var pid = this.Service.Id;
                }
                catch (Exception)
                {
                    return false;
                }

                return Ping();
            }
        }

        public void Dispose()
        {
            DestroyProcess();
            GC.SuppressFinalize(this);
        }

        public Uri ServiceUrl
        {
            // Note: append /wd/hub to the URL if you're directing the test at Appium
            get { return new Uri($"http://{this.IP.ToString()}:{Convert.ToString(this.Port)}"); }
        }

        private void DestroyProcess()
        {
            if (this.Service == null)
            {
                return;
            }

            try
            {
                this.Service.Kill();
            }
            catch (Exception)
            {
            }
            finally
            {
                this.Service.Close();
            }
        }

        private bool Ping()
        {
            bool pinged = false;

            Uri status;

            Uri service = this.ServiceUrl;
            if (service.IsLoopback)
            {
                status = new Uri("http://localhost:" + Convert.ToString(this.Port) + "/status");
            }
            else
            {
                status = new Uri(service.ToString() + "/status");
            }

            DateTime endTime = DateTime.Now.Add(this.InitializationTimeout);
            while (!pinged & DateTime.Now < endTime)
            {
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(status);
                HttpWebResponse response = null;
                try
                {
                    using (response = (HttpWebResponse)request.GetResponse())
                    {
                        pinged = true;
                    }
                }
                catch (Exception)
                {
                    pinged = false;
                }
                finally
                {
                    if (response != null)
                    {
                        response.Close();
                    }
                }
            }
            return pinged;
        }
    }
}
