// /* **********************************************************************************
//  *
//  * Copyright (c) Sky Sanders. All rights reserved.
//  * 
//  * This source code is subject to terms and conditions of the Microsoft Public
//  * License (Ms-PL). A copy of the license can be found in the license.htm file
//  * included in this distribution.
//  *
//  * You must not remove this notice, or any other, from this software.
//  *
//  * **********************************************************************************/
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;

namespace CassiniDev
{

    /// <summary>
    /// Made a go at spinning the server up from this process but after dealing with 
    /// irratic behaviour regarding apartment state, platform concerns, unloaded app domains,
    /// and all the other issues that you can find that people struggle with I just decided
    /// to strictly format the console app's output and just spin up an external process. 
    /// Seems robust so far.
    /// </summary>
    public class CassiniDevServerOP 
    {
        //private bool _disposed;
        
        private string _hostname;
        private StreamWriter _input;
        private IPAddress _ipAddress;
        private Thread _outputThread;
        private string _rootUrl;
        private Process _serverProcess;
        private const int TimeOut = 60000;
        private const int WaitForPort = 5000;


        /// <summary>
        /// </summary>
        public void Dispose()
        {
            {
                if (_serverProcess != null)
                {
                    StopServer();
                }
            }
        }


        /// <summary>
        /// The root URL of the running web application
        /// </summary>
        public string RootUrl
        {
            get { return _rootUrl; }
        }

        /// <summary>
        /// Combine the RootUrl of the running web application with the relative url specified.
        /// </summary>
        /// <param name="relativeUrl"></param>
        /// <returns></returns>
        public string NormalizeUrl(string relativeUrl)
        {
            return CassiniNetworkUtils.NormalizeUrl(RootUrl, relativeUrl);
        }

        /// <summary>
        /// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/"
        /// </summary>
        /// <param name="applicationPath">Physical path to application.</param>
        public void StartServer(string applicationPath)
        {
            StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), "/", "localhost");
        }

        /// <summary>
        /// Will start specified application on loopback
        /// </summary>
        /// <param name="applicationPath">Physical path to application.</param>
        /// <param name="port">Port to listen on.</param>
        /// <param name="virtualPath">Optional. defaults to "/"</param>
        /// <param name="hostName">Optional. Is used to construct RootUrl. Defaults to "localhost"</param>
        public void StartServer(string applicationPath, int port, string virtualPath, string hostName)
        {
            // WebHost.Server will not run on any other IP
            IPAddress ipAddress = IPAddress.Loopback;

            if (!CassiniNetworkUtils.IsPortAvailable(ipAddress, port))
            {
                throw new Exception(string.Format("Port {0} is in use.", port));
            }

            applicationPath = Path.GetFullPath(applicationPath);

            virtualPath = String.Format("/{0}/", (virtualPath ?? string.Empty).Trim('/')).Replace("//", "/");
            hostName = string.IsNullOrEmpty(hostName) ? "localhost" : hostName;

            StartServer(applicationPath, ipAddress, port, virtualPath, hostName);


        }
        /// <summary>
        /// </summary>
        /// <param name="applicationPath">Physical path to application.</param>
        /// <param name="ipAddress">IP to listen on.</param>
        /// <param name="port">Port to listen on.</param>
        /// <param name="virtualPath">Optional. default value '/'</param>
        /// <param name="hostName">Optional. Used to construct RootUrl. Defaults to 'localhost'</param>
        public virtual void StartServer(string applicationPath, IPAddress ipAddress, int port, string virtualPath, string hostName)
        {
            
            _hostname = hostName;
            _ipAddress = ipAddress;

            // massage and validate arguments
            if (string.IsNullOrEmpty(virtualPath))
            {
                virtualPath = "/";
            }
            if (!virtualPath.StartsWith("/"))
            {
                virtualPath = "/" + virtualPath;
            }
            if (_serverProcess != null)
            {
                throw new InvalidOperationException("Server is running");
            }


            string commandLine = (new CommandLineArguments
            {
                Port = port,
                ApplicationPath = string.Format("\"{0}\"", Path.GetFullPath(applicationPath).Trim('\"').TrimEnd('\\')),
                HostName = hostName,
                IPAddress = ipAddress.ToString(),
                VirtualPath = string.Format("\"{0}\"", virtualPath),
                TimeOut = TimeOut,
                WaitForPort = WaitForPort,
                IPMode = IPMode.Specific,
                PortMode = PortMode.Specific
            }).ToString();


            _serverProcess = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    UseShellExecute = false,
                    ErrorDialog = false,
                    CreateNoWindow = true,
                    RedirectStandardOutput = true,
                    RedirectStandardInput = true,
#if NET40 //TODO: find out the real flag
                                  FileName = "CassiniDev4-console.exe",
#else
                                  FileName = "CassiniDev-console.exe",
#endif
      
                    Arguments = commandLine,
                    WorkingDirectory = Environment.CurrentDirectory
                }
            };

            // we are going to monitor each line of the output until we get a start or error signal
            // and then just ignore the rest

            string line = null;

            _serverProcess.Start();

            _outputThread = new Thread(() =>
            {
                string l = _serverProcess.StandardOutput.ReadLine();
                while (l != null)
                {
                    if (l.StartsWith("started:") || l.StartsWith("error:"))
                    {
                        line = l;
                    }
                    l = _serverProcess.StandardOutput.ReadLine();
                }
            });
            _outputThread.Start();

            // use StandardInput to send the newline to stop the server when required
            _input = _serverProcess.StandardInput;

            // block until we get a signal
            while (line == null)
            {
                Thread.Sleep(10);
            }

            if (!line.StartsWith("started:"))
            {
                throw new Exception(string.Format("Could not start server: {0}", line));
            }

            // line is the root url
            _rootUrl = line.Substring(line.IndexOf(':') + 1);
        }
        /// <summary>
        /// <para>Stops the server, if running.</para>
        /// </summary>
        public virtual void StopServer()
        {
            StopServer(100);
        }

        /// <summary>
        /// <para>Stops the server, if running.</para>
        /// </summary>
        protected virtual void StopServer(int delay)
        {
            Thread.Sleep(delay);
            if (_serverProcess != null)
            {
                try
                {
                    _input.WriteLine();
                    _serverProcess.WaitForExit(10000);
                    Thread.Sleep(10);
                }
                catch
                {
                }
                finally
                {
                    _serverProcess.Dispose();
                    _serverProcess = null;
                }
            }
        }
    }
}