# -*- 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
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.decimal_to_precision import DECIMAL_PLACES


class bitclude(Exchange):

    def describe(self):
        return self.deep_extend(super(bitclude, self).describe(), {
            'id': 'bitclude',
            'name': 'Bitclude',
            'countries': ['PL'],
            'rateLimit': 2000,
            'certified': False,
            'pro': False,
            'urls': {
                'api': {
                    'public': 'https://api.bitclude.com/',
                    'private': 'https://api.bitclude.com/',
                },
                'www': 'https://bitclude.com',
                'doc': 'https://docs.bitclude.com',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': False,
                'uid': True,
            },
            'has': {
                'fetchMarkets': 'emulated',
                'fetchCurrencies': True,  # private
                'cancelAllOrders': False,
                'fetchClosedOrders': False,
                'createDepositAddress': True,
                'fetchDepositAddress': 'emulated',
                'fetchDeposits': True,
                'fetchFundingFees': 'emulated',
                'fetchMyTrades': True,
                'fetchOHLCV': False,
                'fetchOpenOrders': True,
                'fetchOrder': False,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchTickers': True,
                'fetchTicker': 'emulated',
                'fetchTrades': True,
                'fetchTradingFees': False,
                'fetchWithdrawals': False,
                'withdraw': False,
            },
            'api': {
                'public': {
                    'get': [
                        'stats/ticker.json',
                        'stats/orderbook_{base}{quote}.json',
                        'stats/history_{base}{quote}.json',
                    ],
                },
                'private': {
                    'get': [
                        '',
                    ],
                },
            },
            'exceptions': {
                # stolen, todo rewrite
                'exact': {
                    'Not enough balances': InsufficientFunds,  # {"error":"Not enough balances","success":false}
                    'InvalidPrice': InvalidOrder,  # {"error":"Invalid price","success":false}
                    'Size too small': InvalidOrder,  # {"error":"Size too small","success":false}
                    'Missing parameter price': InvalidOrder,  # {"error":"Missing parameter price","success":false}
                    'Order not found': OrderNotFound,  # {"error":"Order not found","success":false}
                },
                'broad': {
                    'Invalid parameter': BadRequest,  # {"error":"Invalid parameter start_time","success":false}
                    'The requested URL was not found on the server': BadRequest,
                    'No such coin': BadRequest,
                    'No such market': BadRequest,
                    'An unexpected error occurred': ExchangeError,  # {"error":"An unexpected error occurred, please try again later(58BC21C795).","success":false}
                },
            },
            'precisionMode': DECIMAL_PLACES,
        })

    async def fetch_markets(self, params={}):
        response = await self.publicGetStatsTickerJson(params)
        result = []
        ids = list(response.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            baseId, quoteId = id.split('_')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = (base + '/' + quote)
            precision = {
                'price': None,
                'amount': None,
            }
            info = {}
            info[id] = self.safe_value(response, id)
            entry = {
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': True,
                'precision': precision,
                'limits': None,
                'info': info,
            }
            result.append(entry)
        return result

    async def fetch_currencies(self, params={}):
        if not self.apiKey or not self.uid:
            raise AuthenticationError(self.id + " fetchCurrencies is an authenticated endpoint, therefore it requires 'apiKey' and 'uid' credentials. If you don't need currency details, set exchange.has['fetchCurrencies'] = False before calling its methods.")
        request = {
            'method': 'account',
            'action': 'getwalletsstatus',
        }
        response = await self.privateGet(self.extend(request, params))
        ids = list(response.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            if id == 'success':
                continue
            currency = response[id]
            code = self.safe_currency_code(id)
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': None,
                'active': self.safe_value(currency, 'is_online'),
                'fee': self.safe_float(currency, 'current_optimal_fee'),
                'precision': self.safe_integer(currency, 'decimal_point'),
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'current_minimal_amount'),
                        'max': None,
                    },
                },
            }
        return result

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        symbols = self.symbols if (symbols is None) else symbols
        tickers = await self.publicGetStatsTickerJson(params)
        marketIds = list(self.marketsById.keys())
        result = {}
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            market = self.marketsById[marketId]
            symbol = market['symbol']
            ticker = self.safe_value(tickers, marketId)
            if self.in_array(symbol, symbols):
                result[symbol] = self.parse_ticker(ticker, market)
        return result

    async def fetch_ticker(self, symbol, params={}):
        ticker = await self.fetch_tickers([symbol])
        return self.safe_value(ticker, symbol)

    def parse_ticker(self, ticker, market):
        timestamp = self.milliseconds()
        symbol = market['symbol']
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'max24H'),
            'low': self.safe_float(ticker, 'min24H'),
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': self.safe_float(ticker, 'last'),
            'last': self.safe_float(ticker, 'last'),
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': None,
            'quoteVolume': None,
            'info': ticker,
        }

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        baseId, quoteId = market['id'].split('_')
        request = {
            'base': baseId,
            'quote': quoteId,
        }
        response = await self.publicGetStatsOrderbookBaseQuoteJson(self.extend(request, params))
        data = self.safe_value(response, 'data')
        timestamp = self.safe_timestamp(data, 'timestamp')
        parsedOrderBook = self.parse_order_book(response, timestamp, 'bids', 'asks', 1, 0)
        if limit is not None:
            parsedOrderBook['bids'] = self.filter_by_since_limit(parsedOrderBook['bids'], None, limit)
            parsedOrderBook['asks'] = self.filter_by_since_limit(parsedOrderBook['asks'], None, limit)
        return parsedOrderBook

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'base': market['baseId'],
            'quote': market['quoteId'],
        }
        response = await self.publicGetStatsHistoryBaseQuoteJson(self.extend(request, params))
        trades = self.safe_value(response, 'history')
        return self.parse_trades(trades, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'method': 'account',
            'action': 'history',
        }
        response = await self.privateGet(self.extend(request, params))
        trades = self.safe_value(response, 'history', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_trade(self, trade, market=None):
        #  fetchTrades
        #
        #    {
        #         "time":1531917229,
        #         "nr":"786",
        #         "amount":"0.00018620",
        #         "price":"7314.57",
        #         "type":"a"
        #    }
        #
        #  fetchMyTrades
        #
        #    {
        #         "currency1": "btc",
        #         "currency2": "usd",
        #         "amount": "0.00100000",
        #         "time_close": 1516212758,
        #         "price": "4.00",
        #         "fee_taker": "50",  # Idk what does it exactly means
        #         "fee_maker": "0",
        #         "type": "bid",
        #         "action": "open"
        #    }
        id = self.safe_string(trade, 'nr')
        timestamp = self.safe_integer_2(trade, 'time', 'time_close')
        if 'time' in trade:
            # API return timestamp in different formats depending on endpoint. Of course self isn't specified in docs xD
            timestamp = timestamp * 1000
        type = None
        baseId = self.safe_string(trade, 'currency1')
        quoteId = self.safe_string(trade, 'currency2')
        symbol = None
        quote = None
        if baseId is not None and quoteId is not None:
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = (base + '/' + quote)
        else:
            symbol = market['symbol']
            quote = market['quote']
        side = self.safe_string(trade, 'type')
        if side == 'a' or side == 'ask':
            side = 'sell'
        elif side == 'b' or side == 'bid':
            side = 'buy'
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        cost = None
        if price is not None:
            if amount is not None:
                cost = price * amount
                if self.currency(quote)['precision'] is not None:
                    cost = self.currency_to_precision(quote, cost)
        fee = None  # todo
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': type,
            'order': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_balance(self, params={}):
        await self.load_markets()
        request = {
            'method': 'account',
            'action': 'info',
        }
        response = await self.privateGet(self.extend(request, params))
        result = {
            'info': response,
        }
        balances = self.safe_value(response, 'balances', [])
        currencies = list(balances.keys())
        for i in range(0, len(currencies)):
            balance = self.safe_value(balances, currencies[i])
            currencyCode = self.safe_currency_code(currencies[i])
            account = self.account()
            account['free'] = self.safe_float(balance, 'active')
            account['used'] = self.safe_float(balance, 'inactive')
            result[currencyCode] = account
        return self.parse_balance(result)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        orderId = None
        response = None
        feeCost = None
        feeCurrency = None
        if type == 'limit':
            request = {
                'method': 'transactions',
                'action': side,
                'market1': market['baseId'],
                'market2': market['quoteId'],
                'amount': self.currency_to_precision(market['base'], amount),
                'rate': self.currency_to_precision(market['quote'], price),
            }
            response = await self.privateGet(self.extend(request, params))
            order = self.safe_value(response, 'actions')
            orderId = self.safe_string(order, 'order')
        elif type == 'market':
            request = {
                'method': 'account',
                'action': 'convert',
            }
            request['market1'] = market['baseId'] if (side == 'sell') else market['quoteId']
            request['market2'] = market['quoteId'] if (side == 'sell') else market['baseId']
            currencyOfAmount = market['base'] if (side == 'sell') else market['quote']
            request['amount'] = self.currency_to_precision(currencyOfAmount, amount)
            response = await self.privateGet(self.extend(request, params))
            feeCurrency = market['quote'] if (side == 'sell') else market['base']
            feeCost = self.safe_string(response, 'fee')
        timestamp = self.milliseconds()
        return {
            'id': orderId,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': 'open',
            'symbol': market['symbol'],
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'filled': None,
            'remaining': None,
            'cost': None,
            'fee': {
                'currency': feeCurrency,
                'cost': feeCost,
                'rate': None,
            },
            'trades': None,
            'info': response,
        }

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            'method': 'account',
            'action': 'activeoffers',
        }
        response = await self.privateGet(self.extend(request, params))
        result = self.safe_value(response, 'offers', [])
        orders = self.parse_orders(result, None, since, limit)
        if symbol is not None:
            orders = self.filter_by(orders, 'symbol', symbol)
        return orders

    def parse_order(self, order, market=None):
        # due to very diverse structure of orders self method only work for these returned by fetchOpenOrders
        status = 'open'
        side = self.safe_string(order, 'offertype')
        if side == 'ask':
            side = 'sell'
        elif side == 'bid':
            side = 'buy'
        symbol = None
        if market is None:
            baseId = self.safe_string(order, 'currency1')
            quoteId = self.safe_string(order, 'currency2')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = (base + '/' + quote)
        else:
            symbol = market['symbol']
        timestamp = self.safe_integer(order, 'time_open')
        return {
            'info': order,
            'id': self.safe_string(order, 'nr'),
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': None,
            'side': side,
            'price': self.safe_float(order, 'price'),
            'amount': self.safe_float(order, 'amount'),
            'remaining': None,
            'filled': None,
            'status': status,
            'fee': None,
            'cost': None,
            'trades': None,
        }

    async def cancel_order(self, id, symbol=None, params={}):
        side_in_params = ('side' in params)
        if not side_in_params:
            raise ArgumentsRequired(self.id + ' cancelOrder requires a `side` parameter(sell or buy)')
        side = 'bid' if (params['side'] == 'buy') else 'ask'  # Typo could cause cancel wrong order. todo: handle typo
        params = self.omit(params, ['side', 'currency'])
        request = {
            'method': 'transactions',
            'action': 'cancel',
            'order': int(id),
            'typ': side,
        }
        return await self.privateGet(self.extend(request, params))

    def cancel_unified_order(self, order, params={}):
        # https://github.com/ccxt/ccxt/issues/6838
        request = {
            'side': order['side'],
        }
        return self.cancel_order(order['id'], None, self.extend(request, params))

    async def create_deposit_address(self, code, params={}):
        # not yet documented exchange api method
        await self.load_markets()
        currencyId = self.currency_id(code)
        request = {
            'method': 'account',
            'action': 'newaddress',
            'currency': currencyId,
        }
        response = await self.privateGet(self.extend(request, params))
        address = self.safe_string(response, 'address')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'info': response,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currencyId = self.currency_id(code)
        currencyId = currencyId.upper()
        request = {
            'method': 'account',
            'action': 'info',
        }
        response = await self.privateGet(self.extend(request, params))
        deposits = self.safe_value(response, 'deposit')
        deposit = self.safe_value(deposits, currencyId)
        address = self.safe_string(deposit, 'deposit')
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'info': response,
        }

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchDeposits requires a currency code argument')
        await self.load_markets()
        currency = self.currency(code)
        currencyId = currency['id']
        request = {
            'method': 'account',
            'action': 'deposits',
            'currency': currencyId,
        }
        response = await self.privateGet(self.extend(request, params))
        transactions = self.safe_value(response, 'history', [])
        return self.parse_transactions(transactions, currency)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        if code is None:
            raise ArgumentsRequired(self.id + ' fetchDeposits requires a currency code argument')
        await self.load_markets()
        currency = self.currency(code)
        currencyId = currency['id']
        request = {
            'method': 'account',
            'action': 'withdrawals',
            'currency': currencyId,
        }
        response = await self.privateGet(self.extend(request, params))
        transactions = self.safe_value(response, 'history', [])
        return self.parse_transactions(transactions, currency)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #       "time": "1530883428",
        #       "amount": "0.13750000",
        #       "type": "b787400027b4eae298bad72150384540a23342daaa3eec1c8d17459c103c6bbc",
        #       "state": "1"
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "time": "1528715035",
        #         "amount": "1.00000000",
        #         "tx": "01b8ae6437843879574b69daf95542aff43a4aefaa90e8f70ebf572eccf01cad",
        #         "address": "2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF",
        #         "state": "0"
        #     },
        #
        timestamp = self.safe_integer(transaction, 'time')
        currencyCode = self.safe_string(currency, 'code')
        amount = self.safe_float(transaction, 'amount')
        address = self.safe_string(transaction, 'address')
        status = self.safe_string(transaction, 'state')  # todo: ask support
        txid = self.safe_string_2(transaction, 'type', 'tx')
        return {
            'info': transaction,
            'id': None,
            'currency': currencyCode,
            'amount': amount,
            'address': address,
            'tag': None,
            'status': status,
            'type': None,
            'updated': None,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': None,
        }

    async def fetch_trading_fees(self, params={}):
        await self.load_markets()
        request = {
            'method': 'account',
            'action': 'info',
        }
        response = await self.privateGet(self.extend(request, params))
        account = self.safe_value(response, 'account')
        fees = self.safe_value(account, 'fee')
        return {
            'info': response,
            'maker': self.safe_float(fees, 'maker'),
            'taker': self.safe_float(fees, 'taker'),
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        request = '/' + self.implode_params(path, params)
        url = self.urls['api'][api] + request
        if api == 'private':
            self.check_required_credentials()
            params['id'] = self.uid
            params['key'] = self.apiKey
        if params:
            url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
