# -*- coding: utf-8 -*-

# 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

from ccxt.async_support.base.exchange import Exchange
import base64
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import DDoSProtection


class bitopro(Exchange):

    def describe(self):
        result = self.deep_extend(super(bitopro, self).describe(), {
            'id': 'bitopro',
            'name': 'BitoPro',
            'countries': ['TW'],
            'version': 'v3',
            'rateLimit': 1000,
            'has': {
                'CORS': False,
                'fetchStatus': False,
                'fetchCurrencies': True,
                'fetchOrderBook': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchOHLCV': True,
                'fetchBalance': True,
                'fetchOrders': True,
                'fetchOpenOrders': True,
                'fetchClosedOrders': True,
                'fetchMyTrades': True,
                'createOrder': True,
                'cancelOrder': True,
                'fetchOrder': True,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '3h': '3h',
                '6h': '6h',
                '12h': '12h',
                '1d': '1d',
                '1w': '1w',
                '1M': '1M',
            },
            'urls': {
                'logo': 'https://www.bitopro.com/bitoPro_logo.svg',
                'api': 'https://api.bitopro.com/v3',
                'www': 'https://www.bitopro.com',
                'doc': [
                    'https://developers.bitopro.com',
                ],
                'fees': 'https://www.bitopro.com/fees',
                'referral': 'https://www.bitopro.com/landing_pages/reward',
            },
            'api': {
                'public': {
                    'get': [
                        'order-book/{pair}',
                        'tickers',
                        'tickers/{pair}',
                        'trades/{pair}',
                        'provisioning/trading-pairs',
                        'trading-history/{pair}',
                    ],
                },
                'private': {
                    'get': [
                        'accounts/balance',
                        'orders/history',
                        'orders/{pair}',
                        'orders/{pair}/{orderId}',
                    ],
                    'post': [
                        'orders/{pair}',
                    ],
                    'delete': [
                        'orders/{pair}/{id}',
                    ],
                },
            },
        })
        return result

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        return [
            self.safe_integer(ohlcv, 'timestamp'),
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='5m', since=None, limit=500, params={}):
        await self.load_markets()
        market = self.market(symbol)
        nonce = int(self.nonce() / 1000)
        fromTimstamp = (nonce - 86400)
        if since is not None:
            fromTimstamp = int(since / 1000)
        toTimstamp = nonce
        to = self.safe_value(params, 'to')
        if to:
            toTimstamp = int(to / 1000)
        resolution = self.timeframes[timeframe]
        request = {
            'pair': market['id'],
            'resolution': resolution,
            'from': fromTimstamp,
            'to': toTimstamp,
        }
        response = await self.publicGetTradingHistoryPair(self.extend(request, params))
        data = self.safe_value(response, 'data', [])
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    async def fetch_currencies(self, params={}):
        response = await self.fetch_tickers()
        keys = list(response.keys())
        symbolLength = len(keys)
        result = {}
        for c in range(0, symbolLength):
            pair = keys[c]
            currency = response[pair]
            symbols = pair.split('/')
            symbolLength = len(symbols)
            for i in range(0, symbolLength):
                symbol = symbols[i]
                id = symbol
                name = symbol
                code = self.common_currency_code(symbol)
                result[code] = {
                    'id': id,
                    'code': code,
                    'info': currency,
                    'name': name,
                    'active': True,
                    'fee': None,
                    'precision': None,
                    'limits': {
                        'amount': {
                            'min': None,
                            'max': None,
                        },
                        'price': {
                            'min': None,
                            'max': None,
                        },
                        'cost': {
                            'min': None,
                            'max': None,
                        },
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
        return result

    async def fetch_markets(self, params={}):
        response = await self.publicGetProvisioningTradingPairs()
        markets = self.safe_value(response, 'data', [])
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            active = not market['maintain']
            pair = self.safe_string(market, 'pair')
            base = self.safe_string(market, 'base')
            quote = self.safe_string(market, 'quote')
            id = pair
            base = self.common_currency_code(base)
            base = base.upper()
            quote = self.common_currency_code(quote)
            quote = quote.upper()
            symbol = base + '/' + quote
            precision = {
                'price': self.safe_integer(market, 'quotePrecision'),
                'amount': self.safe_integer(market, 'basePrecision'),
                'cost': None,
            }
            limits = {
                'amount': {
                    'min': self.safe_float(market, 'minLimitBaseAmount'),
                    'max': self.safe_float(market, 'maxLimitBaseAmount'),
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            }
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': base,
                'quoteId': quote,
                'limits': limits,
                'precision': precision,
                'active': active,
                'info': market,
            })
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetAccountsBalance(params)
        balances = self.safe_value(response, 'data', [])
        result = {
            'info': balances,
        }
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            currencyId = currencyId.upper()
            amount = self.safe_float(balance, 'amount')
            available = self.safe_float(balance, 'available')
            stake = self.safe_float(balance, 'stake')
            account = {
                'free': available,
                'stake': stake,
                'used': amount - available - stake,
                'total': amount,
            }
            result[currencyId] = account
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        timestamp = self.milliseconds()
        await self.load_markets()
        request = {
            'pair': self.market_id(symbol),
        }
        if limit is not None:
            request['limit'] = limit
        else:
            limit = 5
            request['limit'] = limit
        response = await self.publicGetOrderBookPair(self.extend(request, params))
        return self.parse_order_book(response, timestamp, 'bids', 'asks', 'price', 'amount')

    def parse_ticker(self, ticker, market=None):
        timestamp = self.milliseconds()
        pair = self.safe_string(ticker, 'pair')
        pair = pair.upper()
        base, quote = pair.split('_')
        base = self.common_currency_code(base)
        quote = self.common_currency_code(quote)
        symbol = base + '/' + quote
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high24hr'),
            'low': self.safe_float(ticker, 'low24hr'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': self.safe_float(ticker, 'lastPrice'),
            'last': self.safe_float(ticker, 'lastPrice'),
            'previousClose': None,
            'change': None,
            'percentage': self.safe_float(ticker, 'priceChange24hr'),
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume24hr'),
            'quoteVolume': None,
            'info': ticker,
        }

    async def fetch_tickers(self, symbols=None, params={}):
        response = await self.publicGetTickers()
        tickers = self.safe_value(response, 'data', [])
        result = {}
        for i in range(0, len(tickers)):
            ticker = self.parse_ticker(tickers[i])
            symbol = ticker['symbol']
            result[symbol] = ticker
        return result

    async def fetch_ticker(self, symbol, params={}):
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        response = await self.publicGetTickersPair(self.extend(request, params))
        ticker = self.safe_value(response, 'data', {})
        return self.parse_ticker(ticker, market)

    def parse_trade(self, trade, market):
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        timestampSeconds = self.safe_value(trade, 'timestamp')
        timestamp = timestampSeconds * 1000
        side = 'buy' if (trade['isBuyer']) else 'sell'
        return {
            'info': trade,
            'id': None,
            'order': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'takerOrMaker': None,
            'type': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': None,
            'fee': None,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        response = await self.publicGetTradesPair(self.extend(request, params))
        trades = self.safe_value(response, 'data', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_order_execution(self, order):
        orderId = self.safe_string(order, 'orderId')
        return {
            'id': orderId,
            'info': order,
        }

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        if symbol is None:
            raise ArgumentsRequired('createOrder requires the symbol parameter')
        request = {
            'type': type.upper(),
            'pair': self.market_id(symbol),
            'action': side.upper(),
            'amount': amount,
            'timestamp': self.nonce(),
        }
        if type.lower() == 'limit':
            request['price'] = price
        response = await self.privatePostOrdersPair(self.extend(request, params), params)
        return self.parse_order_execution(response)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        if symbol is None:
            raise ArgumentsRequired('cancelOrder requires the symbol parameter')
        request = {
            'pair': self.market_id(symbol),
            'id': id,
        }
        response = await self.privateDeleteOrdersPairId(self.extend(request, params))
        return self.parse_order_execution(response)

    def parse_order_status(self, status):
        statuses = {
            '0': 'open',
            '1': 'open',
            '2': 'closed',
            '3': 'closed',
            '4': 'canceled',
        }
        return self.safe_string(statuses, status, None)

    def parse_order(self, order, market=None):
        orderId = self.safe_string(order, 'id')
        timestamp = self.safe_integer(order, 'timestamp')
        marketId = self.safe_string(order, 'pair')
        market = self.safe_value(self.markets_by_id, marketId)
        status = self.parse_order_status(self.safe_string(order, 'status'))
        symbol = self.safe_string(market, 'symbol')
        type = self.safe_string(order, 'type').lower()
        side = self.safe_string(order, 'action').lower()
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'originalAmount')
        filled = self.safe_float(order, 'executedAmount')
        remaining = self.safe_float(order, 'remainingAmount')
        fee = self.safe_float(order, 'fee')
        feeSymbol = self.safe_string(order, 'feeSymbol')
        feeSymbol = feeSymbol.upper()
        return {
            'id': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'type': type,
            'status': status,
            'symbol': symbol,
            'side': side,
            'price': price,
            'amount': amount,
            'filled': filled,
            'cost': None,
            'remaining': remaining,
            'average': None,
            'trades': None,
            'fee': {
                'currency': feeSymbol,
                'cost': fee,
            },
            'info': order,
        }

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'orderId': id,
            'pair': self.market_id(symbol),
        }
        response = await self.privateGetOrdersPairOrderId(self.extend(request, params))
        return self.parse_order(response)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['pair'] = market['id']
        response = await self.privateGetOrdersPair(self.extend(request, params), params)
        orders = self.safe_value(response, 'data', [])
        orderLength = len(orders)
        result = []
        for i in range(0, orderLength):
            order = orders[i]
            parsedOrder = self.parse_order(order)
            result.append(parsedOrder)
        return result

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'active': True,
        }
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        request = {
            'active': False,
        }
        orders = await self.fetch_orders(symbol, since, limit, self.extend(request, params))
        result = []
        orderLength = len(orders)
        for i in range(0, orderLength):
            order = orders[i]
            status = self.safe_string(order, 'status')
            if status == 'closed' or status == 'canceled':
                result.append(order)
        return result

    def parse_my_trade(self, myTrade):
        orderId = self.safe_string(myTrade, 'id')
        timestamp = self.safe_integer(myTrade, 'timestamp')
        marketId = self.safe_string(myTrade, 'pair')
        market = self.safe_value(self.markets_by_id, marketId)
        symbol = self.safe_string(market, 'symbol')
        type = self.safe_string(myTrade, 'type').lower()
        side = self.safe_string(myTrade, 'action').lower()
        price = self.safe_float(myTrade, 'price')
        filled = self.safe_float(myTrade, 'executedAmount')
        fee = self.safe_float(myTrade, 'fee')
        feeSymbol = self.safe_string(myTrade, 'feeSymbol')
        feeSymbol = feeSymbol.upper()
        return {
            'info': myTrade,
            'id': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': filled,
            'cost': None,
            'fee': {
                'cost': fee,
                'currency': feeSymbol,
                'rate': None,
            },
        }

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'pair': market['id'],
        }
        response = await self.privateGetOrdersHistory(self.extend(request, params))
        trades = self.safe_value(response, 'data', [])
        tradesLength = len(trades)
        result = []
        for i in range(0, tradesLength):
            trade = trades[i]
            executedAmount = self.safe_float(trade, 'executedAmount')
            if executedAmount > 0 and trade['pair'] == market['id']:
                myTrade = self.parse_my_trade(trade)
                result.append(myTrade)
        return result

    def nonce(self):
        return self.milliseconds()

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if headers is None:
            headers = {}
        headers['X-BITOPRO-API'] = 'ccxt'
        if api == 'private':
            self.check_required_credentials()
            if method == 'POST':
                body = self.json(params)
                payload = base64.b64encode(body)
                signature = self.hmac(payload, self.secret, hashlib.sha384, 'hex')
                headers['X-BITOPRO-APIKEY'] = self.apiKey
                headers['X-BITOPRO-PAYLOAD'] = payload
                headers['X-BITOPRO-SIGNATURE'] = signature
            elif method == 'GET' or method == 'DELETE':
                if query:
                    url += '?' + self.urlencode(query)
                nonce = self.nonce()
                rawData = {
                    'identity': self.email,
                    'nonce': nonce,
                }
                rawData = self.json(rawData)
                payload = base64.b64encode(rawData)
                signature = self.hmac(payload, self.secret, hashlib.sha384, 'hex')
                headers['X-BITOPRO-APIKEY'] = self.apiKey
                headers['X-BITOPRO-PAYLOAD'] = payload
                headers['X-BITOPRO-SIGNATURE'] = signature
        elif api == 'public' and method == 'GET':
            if query:
                url += '?' + self.urlencode(query)
        url = self.urls['api'] + url
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if code >= 200 and code < 300:
            return
        if code == 401:
            raise AuthenticationError(body)
        if code == 429:
            raise DDoSProtection(body)
        feedback = body
        raise ExchangeError(feedback)
