@page "/OrderBooks"
@using System.Collections.Concurrent
@using System.Timers
@using Binance.Net.Interfaces
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@using Bitget.Net.Interfaces;
@using BitMart.Net.Interfaces;
@using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces
@using Coinbase.Net.Interfaces
@using CryptoExchange.Net.Interfaces
@using GateIo.Net.Interfaces
@using HTX.Net.Interfaces
@using Kraken.Net.Interfaces
@using Kucoin.Net.Clients
@using Kucoin.Net.Interfaces
@using Mexc.Net.Interfaces
@using OKX.Net.Interfaces;
@inject IBinanceOrderBookFactory binanceFactory
@inject IBingXOrderBookFactory bingXFactory
@inject IBitfinexOrderBookFactory bitfinexFactory
@inject IBitgetOrderBookFactory bitgetFactory
@inject IBitMartOrderBookFactory bitmartFactory
@inject IBybitOrderBookFactory bybitFactory
@inject ICoinbaseOrderBookFactory coinbaseFactory
@inject ICoinExOrderBookFactory coinExFactory
@inject IGateIoOrderBookFactory gateioFactory
@inject IHTXOrderBookFactory htxFactory
@inject IKrakenOrderBookFactory krakenFactory
@inject IKucoinOrderBookFactory kucoinFactory
@inject IMexcOrderBookFactory mexcFactory
@inject IOKXOrderBookFactory okxFactory
@implements IDisposable

<h3>ETH-BTC books, live updates:</h3>
<div style="display:flex; flex-wrap: wrap;">
    @foreach(var book in _books.OrderBy(p => p.Key))
    {
        <div style="margin-bottom: 20px; flex: 1; min-width: 300px;">
            <h4>@book.Key</h4>
            @if (book.Value.AskCount >= 3 && book.Value.BidCount >= 3)
            {
                for (var i = 0; i < 3; i++)
                {
                    <div>@book.Value.Bids.ElementAt(i).Price - @book.Value.Asks.ElementAt(i).Price</div>
                }
            }
        </div>
    }
</div>

@code{
    private Dictionary<string, ISymbolOrderBook> _books = new Dictionary<string, ISymbolOrderBook>();
    private Timer _timer;

    protected override async Task OnInitializedAsync()
    {
        // Since the Kucoin order book stream needs authentication we will need to provide API credentials beforehand
        KucoinRestClient.SetDefaultOptions(options =>
        {
            options.ApiCredentials = new Kucoin.Net.Objects.KucoinApiCredentials("KEY", "SECRET", "PASSPHRASE");
        });

        _books = new Dictionary<string, ISymbolOrderBook>
            {
                { "Binance", binanceFactory.CreateSpot("ETHBTC") },
                { "BingX", bingXFactory.CreateSpot("ETH-BTC") },
                { "Bitfinex", bitfinexFactory.Create("tETHBTC") },
                { "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
                { "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
                { "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
                { "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
                { "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
                { "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
                { "HTX", htxFactory.CreateSpot("ethbtc") },
                { "Kraken", krakenFactory.CreateSpot("ETH/XBT") },
                { "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
                { "Mexc", mexcFactory.CreateSpot("ETHBTC") },
                { "OKX", okxFactory.Create("ETH-BTC") },
            };

        await Task.WhenAll(_books.Select(b => b.Value.StartAsync()));

        // Use a manual update timer so the page isn't refreshed too often
        _timer = new Timer(500);
        _timer.Start();
        _timer.Elapsed += (o, e) => InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        _timer.Stop();
        _timer.Dispose();
        foreach (var book in _books.Where(b => b.Value.Status != CryptoExchange.Net.Objects.OrderBookStatus.Disconnected))
            // It's not necessary to wait for this
            _ = book.Value.StopAsync();
    }
}