<?php

namespace ccxt\pro;

// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code

use Exception; // a common import
use ccxt\ExchangeError;
use \React\Async;
use \React\Promise\PromiseInterface;

class bithumb extends \ccxt\async\bithumb {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'watchBalance' => false,
                'watchTicker' => true,
                'watchTickers' => true,
                'watchTrades' => true,
                'watchOrderBook' => true,
                'watchOHLCV' => false,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://pubwss.bithumb.com/pub/ws',
                ),
            ),
            'options' => array(),
            'streaming' => array(),
            'exceptions' => array(),
        ));
    }

    public function watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             *
             * @see https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->channel] the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
             * @return {array} a {@link https://github.com/ccxt/ccxt/wiki/Manual#ticker-structure ticker structure}
             */
            $url = $this->urls['api']['ws'];
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $messageHash = 'ticker:' . $market['symbol'];
            $request = array(
                'type' => 'ticker',
                'symbols' => [ $market['base'] . '_' . $market['quote'] ],
                'tickTypes' => array( $this->safe_string($params, 'tickTypes', '24H') ),
            );
            return Async\await($this->watch($url, $messageHash, $this->extend($request, $params), $messageHash));
        }) ();
    }

    public function watch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
             *
             * @see https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
             *
             * @param {string[]} $symbols unified $symbol of the $market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $url = $this->urls['api']['ws'];
            $marketIds = array();
            $messageHashes = array();
            $symbols = $this->market_symbols($symbols, null, false, true, true);
            for ($i = 0; $i < count($symbols); $i++) {
                $symbol = $symbols[$i];
                $market = $this->market($symbol);
                $marketIds[] = $market['base'] . '_' . $market['quote'];
                $messageHashes[] = 'ticker:' . $market['symbol'];
            }
            $request = array(
                'type' => 'ticker',
                'symbols' => $marketIds,
                'tickTypes' => array( $this->safe_string($params, 'tickTypes', '24H') ),
            );
            $message = $this->extend($request, $params);
            $newTicker = Async\await($this->watch_multiple($url, $messageHashes, $message, $messageHashes));
            if ($this->newUpdates) {
                $result = array();
                $result[$newTicker['symbol']] = $newTicker;
                return $result;
            }
            return $this->filter_by_array($this->tickers, 'symbol', $symbols);
        }) ();
    }

    public function handle_ticker(Client $client, $message) {
        //
        //    {
        //        "type" : "ticker",
        //        "content" : {
        //            "symbol" : "BTC_KRW",           // 통화코드
        //            "tickType" : "24H",                 // 변동 기준시간- 30M, 1H, 12H, 24H, MID
        //            "date" : "20200129",                // 일자
        //            "time" : "121844",                  // 시간
        //            "openPrice" : "2302",               // 시가
        //            "closePrice" : "2317",              // 종가
        //            "lowPrice" : "2272",                // 저가
        //            "highPrice" : "2344",               // 고가
        //            "value" : "2831915078.07065789",    // 누적거래금액
        //            "volume" : "1222314.51355788",  // 누적거래량
        //            "sellVolume" : "760129.34079004",   // 매도누적거래량
        //            "buyVolume" : "462185.17276784",    // 매수누적거래량
        //            "prevClosePrice" : "2326",          // 전일종가
        //            "chgRate" : "0.65",                 // 변동률
        //            "chgAmt" : "15",                    // 변동금액
        //            "volumePower" : "60.80"         // 체결강도
        //        }
        //    }
        //
        $content = $this->safe_dict($message, 'content', array());
        $marketId = $this->safe_string($content, 'symbol');
        $symbol = $this->safe_symbol($marketId, null, '_');
        $ticker = $this->parse_ws_ticker($content);
        $messageHash = 'ticker:' . $symbol;
        $this->tickers[$symbol] = $ticker;
        $client->resolve ($this->tickers[$symbol], $messageHash);
    }

    public function parse_ws_ticker($ticker, $market = null) {
        //
        //    {
        //        "symbol" : "BTC_KRW",           // 통화코드
        //        "tickType" : "24H",                 // 변동 기준시간- 30M, 1H, 12H, 24H, MID
        //        "date" : "20200129",                // 일자
        //        "time" : "121844",                  // 시간
        //        "openPrice" : "2302",               // 시가
        //        "closePrice" : "2317",              // 종가
        //        "lowPrice" : "2272",                // 저가
        //        "highPrice" : "2344",               // 고가
        //        "value" : "2831915078.07065789",    // 누적거래금액
        //        "volume" : "1222314.51355788",  // 누적거래량
        //        "sellVolume" : "760129.34079004",   // 매도누적거래량
        //        "buyVolume" : "462185.17276784",    // 매수누적거래량
        //        "prevClosePrice" : "2326",          // 전일종가
        //        "chgRate" : "0.65",                 // 변동률
        //        "chgAmt" : "15",                    // 변동금액
        //        "volumePower" : "60.80"         // 체결강도
        //    }
        //
        $date = $this->safe_string($ticker, 'date', '');
        $time = $this->safe_string($ticker, 'time', '');
        $datetime = mb_substr($date, 0, 4 - 0) . '-' . mb_substr($date, 4, 6 - 4) . '-' . mb_substr($date, 6, 8 - 6) . 'T' . mb_substr($time, 0, 2 - 0) . ':' . mb_substr($time, 2, 4 - 2) . ':' . mb_substr($time, 4, 6 - 4);
        $marketId = $this->safe_string($ticker, 'symbol');
        return $this->safe_ticker(array(
            'symbol' => $this->safe_symbol($marketId, $market, '_'),
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'high' => $this->safe_string($ticker, 'highPrice'),
            'low' => $this->safe_string($ticker, 'lowPrice'),
            'bid' => null,
            'bidVolume' => $this->safe_string($ticker, 'buyVolume'),
            'ask' => null,
            'askVolume' => $this->safe_string($ticker, 'sellVolume'),
            'vwap' => null,
            'open' => $this->safe_string($ticker, 'openPrice'),
            'close' => $this->safe_string($ticker, 'closePrice'),
            'last' => null,
            'previousClose' => $this->safe_string($ticker, 'prevClosePrice'),
            'change' => $this->safe_string($ticker, 'chgAmt'),
            'percentage' => $this->safe_string($ticker, 'chgRate'),
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'volume'),
            'quoteVolume' => $this->safe_string($ticker, 'value'),
            'info' => $ticker,
        ), $market);
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
             *
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int} [$limit] the maximum amount of order book entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} A dictionary of {@link https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure order book structures} indexed by $market symbols
             */
            Async\await($this->load_markets());
            $url = $this->urls['api']['ws'];
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $messageHash = 'orderbook' . ':' . $symbol;
            $request = array(
                'type' => 'orderbookdepth',
                'symbols' => [ $market['base'] . '_' . $market['quote'] ],
            );
            $orderbook = Async\await($this->watch($url, $messageHash, $this->extend($request, $params), $messageHash));
            return $orderbook->limit ();
        }) ();
    }

    public function handle_order_book(Client $client, $message) {
        //
        //    {
        //        "type" : "orderbookdepth",
        //            "content" : {
        //            "list" : array(
        //                array(
        //                    "symbol" : "BTC_KRW",
        //                    "orderType" : "ask",        // 주문타입 – bid / ask
        //                    "price" : "10593000",       // 호가
        //                    "quantity" : "1.11223318",  // 잔량
        //                    "total" : "3"               // 건수
        //                ),
        //                array("symbol" : "BTC_KRW", "orderType" : "ask", "price" : "10596000", "quantity" : "0.5495", "total" : "8"),
        //                array("symbol" : "BTC_KRW", "orderType" : "ask", "price" : "10598000", "quantity" : "18.2085", "total" : "10"),
        //                array("symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10532000", "quantity" : "0", "total" : "0"),
        //                array("symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10572000", "quantity" : "2.3324", "total" : "4"),
        //                array("symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10571000", "quantity" : "1.469", "total" : "3"),
        //                array("symbol" : "BTC_KRW", "orderType" : "bid", "price" : "10569000", "quantity" : "0.5152", "total" : "2")
        //            ),
        //            "datetime":1580268255864325     // 일시
        //        }
        //    }
        //
        $content = $this->safe_dict($message, 'content', array());
        $list = $this->safe_list($content, 'list', array());
        $first = $this->safe_dict($list, 0, array());
        $marketId = $this->safe_string($first, 'symbol');
        $symbol = $this->safe_symbol($marketId, null, '_');
        $timestampStr = $this->safe_string($content, 'datetime');
        $timestamp = $this->parse_to_int(mb_substr($timestampStr, 0, 13 - 0));
        if (!(is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks))) {
            $ob = $this->order_book();
            $ob['symbol'] = $symbol;
            $this->orderbooks[$symbol] = $ob;
        }
        $orderbook = $this->orderbooks[$symbol];
        $this->handle_deltas($orderbook, $list);
        $orderbook['timestamp'] = $timestamp;
        $orderbook['datetime'] = $this->iso8601($timestamp);
        $messageHash = 'orderbook' . ':' . $symbol;
        $client->resolve ($orderbook, $messageHash);
    }

    public function handle_delta($orderbook, $delta) {
        //
        //    {
        //        symbol => "ETH_BTC",
        //        orderType => "bid",
        //        price => "0.07349517",
        //        quantity => "0",
        //        total => "0",
        //    }
        //
        $sideId = $this->safe_string($delta, 'orderType');
        $side = ($sideId === 'bid') ? 'bids' : 'asks';
        $bidAsk = $this->parse_bid_ask($delta, 'price', 'quantity');
        $orderbookSide = $orderbook[$side];
        $orderbookSide->storeArray ($bidAsk);
    }

    public function handle_deltas($orderbook, $deltas) {
        for ($i = 0; $i < count($deltas); $i++) {
            $this->handle_delta($orderbook, $deltas[$i]);
        }
    }

    public function watch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent $trades for a particular $symbol
             *
             * @see https://apidocs.bithumb.com/v1.2.0/reference/%EB%B9%97%EC%8D%B8-%EA%B1%B0%EB%9E%98%EC%86%8C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%8B%A0
             *
             * @param {string} $symbol unified $symbol of the $market to fetch $trades for
             * @param {int} [$since] timestamp in ms of the earliest trade to fetch
             * @param {int} [$limit] the maximum amount of $trades to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of {@link https://github.com/ccxt/ccxt/wiki/Manual#public-$trades trade structures}
             */
            Async\await($this->load_markets());
            $url = $this->urls['api']['ws'];
            $market = $this->market($symbol);
            $symbol = $market['symbol'];
            $messageHash = 'trade:' . $symbol;
            $request = array(
                'type' => 'transaction',
                'symbols' => [ $market['base'] . '_' . $market['quote'] ],
            );
            $trades = Async\await($this->watch($url, $messageHash, $this->extend($request, $params), $messageHash));
            if ($this->newUpdates) {
                $limit = $trades->getLimit ($symbol, $limit);
            }
            return $this->filter_by_since_limit($trades, $since, $limit, 'timestamp', true);
        }) ();
    }

    public function handle_trades($client, $message) {
        //
        //    {
        //        "type" : "transaction",
        //        "content" : {
        //            "list" : array(
        //                {
        //                    "symbol" : "BTC_KRW",
        //                    "buySellGb" : "1",
        //                    "contPrice" : "10579000",
        //                    "contQty" : "0.01",
        //                    "contAmt" : "105790.00",
        //                    "contDtm" : "2020-01-29 12:24:18.830039",
        //                    "updn" : "dn"
        //                }
        //            )
        //        }
        //    }
        //
        $content = $this->safe_dict($message, 'content', array());
        $rawTrades = $this->safe_list($content, 'list', array());
        for ($i = 0; $i < count($rawTrades); $i++) {
            $rawTrade = $rawTrades[$i];
            $marketId = $this->safe_string($rawTrade, 'symbol');
            $symbol = $this->safe_symbol($marketId, null, '_');
            if (!(is_array($this->trades) && array_key_exists($symbol, $this->trades))) {
                $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
                $stored = new ArrayCache ($limit);
                $this->trades[$symbol] = $stored;
            }
            $trades = $this->trades[$symbol];
            $parsed = $this->parse_ws_trade($rawTrade);
            $trades->append ($parsed);
            $messageHash = 'trade' . ':' . $symbol;
            $client->resolve ($trades, $messageHash);
        }
    }

    public function parse_ws_trade($trade, $market = null) {
        //
        //    {
        //        "symbol" : "BTC_KRW",
        //        "buySellGb" : "1",
        //        "contPrice" : "10579000",
        //        "contQty" : "0.01",
        //        "contAmt" : "105790.00",
        //        "contDtm" : "2020-01-29 12:24:18.830038",
        //        "updn" : "dn"
        //    }
        //
        $marketId = $this->safe_string($trade, 'symbol');
        $datetime = $this->safe_string($trade, 'contDtm');
        // that date is not UTC iso8601, but exchange's local time, -9hr difference
        $timestamp = $this->parse8601($datetime) - 32400000;
        $sideId = $this->safe_string($trade, 'buySellGb');
        return $this->safe_trade(array(
            'id' => null,
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $this->safe_symbol($marketId, $market, '_'),
            'order' => null,
            'type' => null,
            'side' => ($sideId === '1') ? 'buy' : 'sell',
            'takerOrMaker' => null,
            'price' => $this->safe_string($trade, 'contPrice'),
            'amount' => $this->safe_string($trade, 'contQty'),
            'cost' => $this->safe_string($trade, 'contAmt'),
            'fee' => null,
        ), $market);
    }

    public function handle_error_message(Client $client, $message): Bool {
        //
        //    {
        //        "status" : "5100",
        //        "resmsg" : "Invalid Filter Syntax"
        //    }
        //
        if (!(is_array($message) && array_key_exists('status', $message))) {
            return true;
        }
        $errorCode = $this->safe_string($message, 'status');
        try {
            if ($errorCode !== '0000') {
                $msg = $this->safe_string($message, 'resmsg');
                throw new ExchangeError($this->id . ' ' . $msg);
            }
            return true;
        } catch (Exception $e) {
            $client->reject ($e);
        }
        return true;
    }

    public function handle_message(Client $client, $message) {
        if (!$this->handle_error_message($client, $message)) {
            return;
        }
        $topic = $this->safe_string($message, 'type');
        if ($topic !== null) {
            $methods = array(
                'ticker' => array($this, 'handle_ticker'),
                'orderbookdepth' => array($this, 'handle_order_book'),
                'transaction' => array($this, 'handle_trades'),
            );
            $method = $this->safe_value($methods, $topic);
            if ($method !== null) {
                $method($client, $message);
            }
        }
    }
}
