<?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 \React\Async;
use \React\Promise\PromiseInterface;

class coinone extends \ccxt\async\coinone {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'watchOrderBook' => true,
                'watchOrders' => false,
                'watchTrades' => true,
                'watchTradesForSymbols' => false,
                'watchOHLCV' => false,
                'watchTicker' => true,
                'watchTickers' => false,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://stream.coinone.co.kr',
                ),
            ),
            'options' => array(
                'expiresIn' => '',
                'userId' => '',
                'wsSessionToken' => '',
                'watchOrderBook' => array(
                    'snapshotDelay' => 6,
                    'snapshotMaxRetries' => 3,
                ),
                'tradesLimit' => 1000,
                'OHLCVLimit' => 1000,
            ),
            'exceptions' => array(
                'exact' => array(
                    '4009' => '\\ccxt\\AuthenticationError',
                ),
            ),
            'streaming' => array(
                'ping' => array($this, 'ping'),
                'keepAlive' => 20000,
            ),
        ));
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             *
             * @see https://docs.coinone.co.kr/reference/public-websocket-$orderbook
             *
             * @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://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $messageHash = 'orderbook:' . $market['symbol'];
            $url = $this->urls['api']['ws'];
            $request = array(
                'request_type' => 'SUBSCRIBE',
                'channel' => 'ORDERBOOK',
                'topic' => array(
                    'quote_currency' => $market['quote'],
                    'target_currency' => $market['base'],
                ),
            );
            $message = $this->extend($request, $params);
            $orderbook = Async\await($this->watch($url, $messageHash, $message, $messageHash));
            return $orderbook->limit ();
        }) ();
    }

    public function handle_order_book($client, $message) {
        //
        //     {
        //         "response_type" => "DATA",
        //         "channel" => "ORDERBOOK",
        //         "data" => {
        //             "quote_currency" => "KRW",
        //             "target_currency" => "BTC",
        //             "timestamp" => 1705288918649,
        //             "id" => "1705288918649001",
        //             "asks" => array(
        //                 {
        //                     "price" => "58412000",
        //                     "qty" => "0.59919807"
        //                 }
        //             ),
        //             "bids" => array(
        //                 {
        //                     "price" => "58292000",
        //                     "qty" => "0.1045"
        //                 }
        //             )
        //         }
        //     }
        //
        $data = $this->safe_value($message, 'data', array());
        $baseId = $this->safe_string_upper($data, 'target_currency');
        $quoteId = $this->safe_string_upper($data, 'quote_currency');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $this->symbol($base . '/' . $quote);
        $timestamp = $this->safe_integer($data, 'timestamp');
        $orderbook = $this->safe_value($this->orderbooks, $symbol);
        if ($orderbook === null) {
            $orderbook = $this->order_book();
        } else {
            $orderbook->reset ();
        }
        $orderbook['symbol'] = $symbol;
        $asks = $this->safe_value($data, 'asks', array());
        $bids = $this->safe_value($data, 'bids', array());
        $this->handle_deltas($orderbook['asks'], $asks);
        $this->handle_deltas($orderbook['bids'], $bids);
        $orderbook['timestamp'] = $timestamp;
        $orderbook['datetime'] = $this->iso8601($timestamp);
        $messageHash = 'orderbook:' . $symbol;
        $this->orderbooks[$symbol] = $orderbook;
        $client->resolve ($orderbook, $messageHash);
    }

    public function handle_delta($bookside, $delta) {
        $bidAsk = $this->parse_bid_ask($delta, 'price', 'qty');
        $bookside->storeArray ($bidAsk);
    }

    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://docs.coinone.co.kr/reference/public-websocket-ticker
             *
             * @param {string} $symbol 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());
            $market = $this->market($symbol);
            $messageHash = 'ticker:' . $market['symbol'];
            $url = $this->urls['api']['ws'];
            $request = array(
                'request_type' => 'SUBSCRIBE',
                'channel' => 'TICKER',
                'topic' => array(
                    'quote_currency' => $market['quote'],
                    'target_currency' => $market['base'],
                ),
            );
            $message = $this->extend($request, $params);
            return Async\await($this->watch($url, $messageHash, $message, $messageHash));
        }) ();
    }

    public function handle_ticker(Client $client, $message) {
        //
        //     {
        //         "response_type" => "DATA",
        //         "channel" => "TICKER",
        //         "data" => {
        //             "quote_currency" => "KRW",
        //             "target_currency" => "BTC",
        //             "timestamp" => 1705301117198,
        //             "quote_volume" => "19521465345.504",
        //             "target_volume" => "334.81445168",
        //             "high" => "58710000",
        //             "low" => "57276000",
        //             "first" => "57293000",
        //             "last" => "58532000",
        //             "volume_power" => "100",
        //             "ask_best_price" => "58537000",
        //             "ask_best_qty" => "0.1961",
        //             "bid_best_price" => "58532000",
        //             "bid_best_qty" => "0.00009258",
        //             "id" => "1705301117198001",
        //             "yesterday_high" => "59140000",
        //             "yesterday_low" => "57273000",
        //             "yesterday_first" => "58897000",
        //             "yesterday_last" => "57301000",
        //             "yesterday_quote_volume" => "12967227517.4262",
        //             "yesterday_target_volume" => "220.09232233"
        //         }
        //     }
        //
        $data = $this->safe_value($message, 'data', array());
        $ticker = $this->parse_ws_ticker($data);
        $symbol = $ticker['symbol'];
        $this->tickers[$symbol] = $ticker;
        $messageHash = 'ticker:' . $symbol;
        $client->resolve ($this->tickers[$symbol], $messageHash);
    }

    public function parse_ws_ticker($ticker, ?array $market = null): array {
        //
        //     {
        //         "quote_currency" => "KRW",
        //         "target_currency" => "BTC",
        //         "timestamp" => 1705301117198,
        //         "quote_volume" => "19521465345.504",
        //         "target_volume" => "334.81445168",
        //         "high" => "58710000",
        //         "low" => "57276000",
        //         "first" => "57293000",
        //         "last" => "58532000",
        //         "volume_power" => "100",
        //         "ask_best_price" => "58537000",
        //         "ask_best_qty" => "0.1961",
        //         "bid_best_price" => "58532000",
        //         "bid_best_qty" => "0.00009258",
        //         "id" => "1705301117198001",
        //         "yesterday_high" => "59140000",
        //         "yesterday_low" => "57273000",
        //         "yesterday_first" => "58897000",
        //         "yesterday_last" => "57301000",
        //         "yesterday_quote_volume" => "12967227517.4262",
        //         "yesterday_target_volume" => "220.09232233"
        //     }
        //
        $timestamp = $this->safe_integer($ticker, 'timestamp');
        $last = $this->safe_string($ticker, 'last');
        $baseId = $this->safe_string($ticker, 'target_currency');
        $quoteId = $this->safe_string($ticker, 'quote_currency');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $this->symbol($base . '/' . $quote);
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'high'),
            'low' => $this->safe_string($ticker, 'low'),
            'bid' => $this->safe_number($ticker, 'bid_best_price'),
            'bidVolume' => $this->safe_number($ticker, 'bid_best_qty'),
            'ask' => $this->safe_number($ticker, 'ask_best_price'),
            'askVolume' => $this->safe_number($ticker, 'ask_best_qty'),
            'vwap' => null,
            'open' => $this->safe_string($ticker, 'first'),
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => null,
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'target_volume'),
            'quoteVolume' => $this->safe_string($ticker, 'quote_volume'),
            'info' => $ticker,
        ), $market);
    }

    public function watch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * watches information on multiple $trades made in a $market
             *
             * @see https://docs.coinone.co.kr/reference/public-websocket-trade
             *
             * @param {string} $symbol unified $market $symbol of the $market $trades were made in
             * @param {int} [$since] the earliest time in ms to fetch $trades for
             * @param {int} [$limit] the maximum number of trade structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $messageHash = 'trade:' . $market['symbol'];
            $url = $this->urls['api']['ws'];
            $request = array(
                'request_type' => 'SUBSCRIBE',
                'channel' => 'TRADE',
                'topic' => array(
                    'quote_currency' => $market['quote'],
                    'target_currency' => $market['base'],
                ),
            );
            $message = $this->extend($request, $params);
            $trades = Async\await($this->watch($url, $messageHash, $message, $messageHash));
            if ($this->newUpdates) {
                $limit = $trades->getLimit ($market['symbol'], $limit);
            }
            return $this->filter_by_since_limit($trades, $since, $limit, 'timestamp', true);
        }) ();
    }

    public function handle_trades(Client $client, $message) {
        //
        //     {
        //         "response_type" => "DATA",
        //         "channel" => "TRADE",
        //         "data" => {
        //             "quote_currency" => "KRW",
        //             "target_currency" => "BTC",
        //             "id" => "1705303667916001",
        //             "timestamp" => 1705303667916,
        //             "price" => "58490000",
        //             "qty" => "0.0008",
        //             "is_seller_maker" => false
        //         }
        //     }
        //
        $data = $this->safe_value($message, 'data', array());
        $trade = $this->parse_ws_trade($data);
        $symbol = $trade['symbol'];
        $stored = $this->safe_value($this->trades, $symbol);
        if ($stored === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $stored = new ArrayCache ($limit);
            $this->trades[$symbol] = $stored;
        }
        $stored->append ($trade);
        $messageHash = 'trade:' . $symbol;
        $client->resolve ($stored, $messageHash);
    }

    public function parse_ws_trade(array $trade, ?array $market = null): array {
        //
        //     {
        //         "quote_currency" => "KRW",
        //         "target_currency" => "BTC",
        //         "id" => "1705303667916001",
        //         "timestamp" => 1705303667916,
        //         "price" => "58490000",
        //         "qty" => "0.0008",
        //         "is_seller_maker" => false
        //     }
        //
        $baseId = $this->safe_string_upper($trade, 'target_currency');
        $quoteId = $this->safe_string_upper($trade, 'quote_currency');
        $base = $this->safe_currency_code($baseId);
        $quote = $this->safe_currency_code($quoteId);
        $symbol = $base . '/' . $quote;
        $timestamp = $this->safe_integer($trade, 'timestamp');
        $market = $this->safe_market($symbol, $market);
        $isSellerMaker = $this->safe_value($trade, 'is_seller_maker');
        $side = null;
        if ($isSellerMaker !== null) {
            $side = $isSellerMaker ? 'sell' : 'buy';
        }
        $priceString = $this->safe_string($trade, 'price');
        $amountString = $this->safe_string($trade, 'qty');
        return $this->safe_trade(array(
            'id' => $this->safe_string($trade, 'id'),
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'order' => null,
            'symbol' => $market['symbol'],
            'type' => null,
            'side' => $side,
            'takerOrMaker' => null,
            'price' => $priceString,
            'amount' => $amountString,
            'cost' => null,
            'fee' => null,
        ), $market);
    }

    public function handle_error_message(Client $client, $message): Bool {
        //
        //     {
        //         "response_type" => "ERROR",
        //         "error_code" => 160012,
        //         "message" => "Invalid Topic"
        //     }
        //
        $type = $this->safe_string($message, 'response_type', '');
        if ($type === 'ERROR') {
            return true;
        }
        return false;
    }

    public function handle_message(Client $client, $message) {
        if ($this->handle_error_message($client, $message)) {
            return;
        }
        $type = $this->safe_string($message, 'response_type');
        if ($type === 'PONG') {
            $this->handle_pong($client, $message);
            return;
        }
        if ($type === 'DATA') {
            $topic = $this->safe_string($message, 'channel', '');
            $methods = array(
                'ORDERBOOK' => array($this, 'handle_order_book'),
                'TICKER' => array($this, 'handle_ticker'),
                'TRADE' => array($this, 'handle_trades'),
            );
            $exacMethod = $this->safe_value($methods, $topic);
            if ($exacMethod !== null) {
                $exacMethod($client, $message);
                return;
            }
            $keys = is_array($methods) ? array_keys($methods) : array();
            for ($i = 0; $i < count($keys); $i++) {
                $key = $keys[$i];
                if (mb_strpos($topic, $keys[$i]) !== false) {
                    $method = $methods[$key];
                    $method($client, $message);
                    return;
                }
            }
        }
    }

    public function ping(Client $client) {
        return array(
            'request_type' => 'PING',
        );
    }

    public function handle_pong(Client $client, $message) {
        //
        //     {
        //         "response_type":"PONG"
        //     }
        //
        $client->lastPong = $this->milliseconds();
        return $message;
    }
}
