/*****************************************************************************************/

        /// <summary>
        ///     Connect to a blockchain
        /// </summary>
        /// <param name="hostname">Blockchain host</param>
        /// <param name="blockchain">Blockchain name</param>
        /// <param name="port">Blockchain port</param>
        /// <param name="localPort">Blockchain local port</param>
        /// <param name="rpcPort">Blockchain local RPC port</param>
        /// <param name="clean">Should clean local blockchain directory?</param>
        /// <returns></returns>
        public async Task <MultichainModel> Connect(string hostname, string blockchain, int port, int localPort,
                                                    int rpcPort, bool clean = true)
        {
            MultichainModel chain;

            if (!Connections.TryGetValue(blockchain, out chain))
            {
                chain = new MultichainModel(hostname, port, blockchain, RpcUser, MultiChainTools.RandomString(),
                                            localPort, rpcPort);
                Connections[blockchain] = chain;
            }

            var model = await RunDaemon(chain, clean);

            await ConnectRpc(model);

            return(model);
        }
        /// <summary>
        ///     Runs multichaind in a background process, connecting to a specified blockchain.
        ///     Optionally callsback on successful launch, determined by multichaind's stdout/err.
        /// </summary>
        /// <param name="chain">Blockchain connection/status data</param>
        /// <param name="clean">Should clean local blockchain directory?</param>
        /// <returns>Blockchain connection/status data</returns>
        private static async Task <MultichainModel> RunDaemon(MultichainModel chain, bool clean)
        {
            if (chain.Process != null)
            {
                try
                {
                    if (!chain.Process.HasExited)
                    {
                        return(chain);
                    }
                }
                // May throw exception if process has become unassociated
                catch (InvalidOperationException)
                {
                }
                Debug.WriteLine($"Restarting Multichaind for chain: {chain.Name}!!");
            }

            // Get working directory and multichaind.exe path
            var evotoDir        = MultiChainTools.GetAppDataFolder(allowSubDir: false);
            var multichainDPath = Path.Combine(evotoDir, "multichaind.exe");

            MultiChainTools.EnsureFileExists(multichainDPath, Resources.multichaind);

            var dataDir = MultiChainTools.GetAppDataFolder();

            // Clean if required (multichain bug)
            if (clean)
            {
                MultiChainTools.CleanBlockchain(dataDir, chain.Name);
            }

            Debug.WriteLine(
                $"Starting MultiChain connection to {chain.Name}@{chain.Hostname}:{chain.Port} ({chain.LocalPort})");
            Debug.WriteLine($"RPC Data: {RpcUser} : {chain.RpcPassword} : {chain.RpcPort}");
            var pArgs =
                $"{chain.Name}@{chain.Hostname}:{chain.Port} -daemon -datadir={dataDir} -server -port={chain.LocalPort}" +
                $" -rpcuser={RpcUser} -rpcpassword={chain.RpcPassword} -rpcport={chain.RpcPort}";

            chain.Process = new Process
            {
                StartInfo =
                {
                    // Stop the process from opening a new window
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    UseShellExecute        = false,
                    CreateNoWindow         = true,

                    // Setup executable and parameters
                    FileName  = multichainDPath,
                    Arguments = pArgs
                }
            };

            var taskCompletion = new TaskCompletionSource <bool>();

            // Connect to outputs
            chain.Process.ErrorDataReceived += (sender, args) =>
            {
                if (string.IsNullOrWhiteSpace(args.Data))
                {
                    return;
                }
                Debug.WriteLine($"Multichaind Error ({chain.Name}): {args.Data}");
            };
            chain.Process.OutputDataReceived += (sender, e) =>
            {
                if (string.IsNullOrWhiteSpace(e.Data))
                {
                    return;
                }
                Debug.WriteLine($"Multichaind ({chain.Name}): {e.Data}");
                if (e.Data.Contains("Node started"))
                {
                    taskCompletion.SetResult(true);
                }
            };

            // Launch process
            var success = chain.Process.Start();

            if (!success)
            {
                throw new CouldNotStartProcessException();
            }

            // Read outputs
            chain.Process.BeginOutputReadLine();
            chain.Process.BeginErrorReadLine();
            chain.Process.EnableRaisingEvents = true;

            chain.Process.Exited += (sender, args) =>
            {
                // May have been disposed
                taskCompletion.TrySetResult(false);
            };

            await Task.Run(() =>
            {
                if (!taskCompletion.Task.Wait(TimeSpan.FromMinutes(1)))
                {
                    chain.Process.Kill();
                    throw new CouldNotConnectToBlockchainException("Timed Out");
                }
                if (!taskCompletion.Task.Result)
                {
                    throw new CouldNotConnectToBlockchainException();
                }
            });

            return(chain);
        }
        public static async Task CreateBlockchain(string blockchainName)
        {
            var evotoDir           = MultiChainTools.GetAppDataFolder(allowSubDir: false);
            var multichainUtilPath = Path.Combine(evotoDir, "multichain-util.exe");

            MultiChainTools.EnsureFileExists(multichainUtilPath, Resources.multichain_util);

            Debug.WriteLine($"Creating MultiChain: {blockchainName}");

            var dataDir = MultiChainTools.GetAppDataFolder();
            var process = new Process
            {
                StartInfo =
                {
                    // Stop the process from opening a new window
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    UseShellExecute        = false,
                    CreateNoWindow         = true,

                    // Setup executable and parameters
                    FileName  = multichainUtilPath,
                    Arguments = $"-datadir={dataDir} create {blockchainName}"
                }
            };

            var outputQueue = new Queue <string>();
            var errQueue    = new Queue <string>();

            process.OutputDataReceived += (sender, args) =>
            {
                if (string.IsNullOrWhiteSpace(args.Data))
                {
                    return;
                }
                Debug.WriteLine($"Multichain-util: {args.Data}");
                outputQueue.Enqueue(args.Data);
            };
            process.ErrorDataReceived += (sender, args) =>
            {
                if (string.IsNullOrWhiteSpace(args.Data))
                {
                    return;
                }
                Debug.WriteLine($"Multichain-util Error: {args.Data}");
                errQueue.Enqueue(args.Data);
            };

            // Go
            var success = process.Start();

            if (!success)
            {
                throw new CouldNotCreateBlockchainException("Could not start process");
            }

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
            // Wait for process to end
            await Task.Run(() =>
            {
                if (process.WaitForExit(10 * 1000))
                {
                    // Allow error events time to be processed
                    Thread.Sleep(100);
                    if (errQueue.Count > 0)
                    {
                        throw new CouldNotCreateBlockchainException(string.Join("\n", errQueue));
                    }
                }
                else
                {
                    // Process hanging
                    process.Kill();
                    throw new CouldNotCreateBlockchainException("Process timed out");
                }
            });
        }