# -*- 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

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import OnMaintenance
from ccxt.base.errors import InvalidNonce


class tokensnet (Exchange):

    def describe(self):
        return self.deep_extend(super(tokensnet, self).describe(), {
            'id': 'tokensnet',
            'name': 'Tokens.net',
            'countries': ['GB'],
            'rateLimit': 200,  # 1R/200ms = 300/min = 5/s
            'certified': False,
            'has': {
                'fetchMarkets': True,
                'fetchTickers': True,
                'fetchTicker': True,
                'fetchCurrencies': True,
                'fetchOrderBook': True,
                'fetchTrades': True,
                'fetchBalance': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'createOrder': True,
                'cancelOrder': True,
                'fetchMyTrades': True,
                'fetchOHLCV': False,
                'fetchStatus': False,
                'fetchOrders': False,
                'fetchClosedOrders': False,
            },
            'urls': {
                'api': 'https://api.tokens.net/',
                'doc': 'https://www.tokens.net/api/',
                'logo': 'https://user-images.githubusercontent.com/4224211/52278879-67fcea80-2958-11e9-9cec-94a7333a9d7e.png',
                'www': 'https://www.tokens.net',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': True,
            },
            'api': {
                'public': {
                    'get': [
                        'public/trading-pairs/get/all/',
                        'public/ticker/all/',
                        'public/ticker/{pair}/',
                        'public/currency/all/',
                        'public/order-book/{pair}/',
                        'public/trades/hour/{pair}/',
                    ],
                },
                'private': {
                    'get': [
                        'private/balance/all/',
                        'private/orders/get/all/',
                        'private/orders/get/{pair}/',
                        'private/orders/get/{id}/',
                        'private/trades/all/{page}/',
                        'private/trades/{pair}/{page}/',
                    ],
                    'post': [
                        'private/orders/add/limit/',
                        'private/orders/cancel/{id}/',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'taker': 0.2 / 100,
                    'maker': 0,
                },
            },
            'exceptions': {
                '100': ArgumentsRequired,
                '101': ArgumentsRequired,
                '102': ArgumentsRequired,
                '110': InvalidNonce,
                '111': InvalidNonce,
                '120': PermissionDenied,
                '121': PermissionDenied,
                '122': PermissionDenied,
                '123': BadRequest,
                '124': BadRequest,
                '130': BadRequest,
                '131': BadRequest,
                '140': BadRequest,
                '150': BadRequest,
                '151': BadRequest,
                '152': BadRequest,
                '160': BadRequest,
                '161': BadRequest,
                '429': DDoSProtection,
                '503': OnMaintenance,
            },
        })

    async def fetch_markets(self, params={}):
        markets = await self.publicGetPublicTradingPairsGetAll(params)
        #    {
        #        "xtzeurs": {
        #            "counterCurrency": "eurs",
        #            "priceDecimals": 4,
        #            "baseCurrency": "xtz",
        #            "amountDecimals": 6,
        #            "minAmount": "1",
        #            "title": "XTZ/EURS"
        #         },
        #         ...
        #    }
        result = []
        keys = list(markets.keys())
        for i in range(0, len(keys)):
            market = markets[keys[i]]
            id = keys[i]
            baseId = self.safe_string_upper(market, 'baseCurrency')
            base = self.safe_currency_code(baseId)
            quoteId = self.safe_string_upper(market, 'counterCurrency')
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'price': market['priceDecimals'],
                'amount': market['amountDecimals'],
                'cost': market['amountDecimals'],
            }
            cost = self.safe_float(market, 'minAmount')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': True,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision['amount']),
                        'max': None,
                    },
                    'price': {
                        'min': math.pow(10, -precision['price']),
                        'max': None,
                    },
                    'cost': {
                        'min': float(cost),
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        tickers = await self.publicGetPublicTickerAll(params)
        #    {
        #        "status": "ok",
        #        "timestamp": 1572945773,
        #        "btcusdt": {
        #            "last": "9381.24",
        #            "volume_usdt": "272264.02067160",
        #            "open": "9616.58",
        #            "volume": "28.54974563",
        #            "ask": "9381.25",
        #            "low": "9340.37",
        #            "bid": "9371.87",
        #            "vwap": "9536.47798478",
        #            "high": "9702.76"
        #        },
        #        ...
        #    }
        result = []
        keys = list(tickers.keys())
        for i in range(0, len(keys)):
            symbolId = keys[i]
            ticker = tickers[symbolId]
            if symbolId != 'status' and symbolId != 'timestamp' and symbolId in self.marketsById:
                market = self.marketsById[symbolId]
                result.append(self.parse_ticker(ticker, market))
        return self.index_by(result, 'symbol')

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.markets[symbol]
        ticker = await self.publicGetPublicTickerPair(self.extend({
            'pair': self.market_id(symbol),
        }, params))
        #    {
        #        "vwap": "9536.47798478",
        #        "volume_usdt": "272264.02067160",
        #        "open": "9616.58",
        #        "ask": "9381.25",
        #        "status": "ok",
        #        "high": "9702.76",
        #        "timestamp": 1572265101,
        #        "volume": "28.54974563",
        #        "bid": "9371.87",
        #        "last": "9381.24",
        #        "low": "9340.37"
        #    }
        return self.parse_ticker(ticker, market)

    async def fetch_currencies(self, params={}):
        response = await self.publicGetPublicCurrencyAll(params)
        #    {
        #        "status": "ok",
        #        "timestamp": 1572945773,
        #        "currencies": {
        #            "BTC": {
        #                "canWithdraw": True,
        #                "ethValue": "50.00000000",
        #                "namePlural": "Bitcoins",
        #                "minimalOrder": "0.0001",
        #                "withdrawalFee": "0.0003",
        #                "btcValue": "1.00000000",
        #                "decimals": 8,
        #                "name": "Bitcoin",
        #                "usdtValue": "9302.93000000"
        #            },
        #            ...
        #        },
        #    },
        currencies = self.safe_value(response, 'currencies', [])
        # Currencies are returned as a dict(historical reasons)
        keys = list(currencies.keys())
        result = {}
        for i in range(0, len(keys)):
            currencySymbol = keys[i]
            currency = currencies[currencySymbol]
            result[currencySymbol] = {
                'id': currencySymbol,
                'name': self.safe_string(currency, 'name'),
                'code': currencySymbol,
                'precision': self.safe_integer(currency, 'decimals'),
                'info': currency,
            }
        return result

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        orderbook = await self.publicGetPublicOrderBookPair(self.extend({
            'pair': market['id'],
        }, params))
        #    {
        #        "timestamp":1573031500,
        #        "status":"ok",
        #        "asks":[
        #            [
        #                "0.02126453",
        #                "192.94"
        #            ],
        #            [
        #                "0.02322024",
        #                "192.95"
        #            ],
        #            ...
        #        ],
        #        "bids":[
        #            [
        #                "0.00486816",
        #                "192.75"
        #            ],
        #            [
        #                "0.02811401",
        #                "192.56"
        #            ],
        #            ...
        #        ]
        #    }
        timestamp = self.safe_timestamp(orderbook, 'timestamp')
        parsedOrderbook = self.parse_order_book(orderbook, timestamp, 'bids', 'asks', 1, 0)
        parsedOrderbook['nonce'] = self.nonce()
        return parsedOrderbook

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        response = await self.publicGetPublicTradesHourPair(self.extend({
            'pair': market['id'],
        }, params))
        #    {
        #        "timestamp":1573031391,
        #        "trades":[
        #            {
        #                "datetime":1573028240,
        #                "price":"192.93",
        #                "id":3655654,
        #                "amount":"1.51166933",
        #                "type":"buy"
        #            },
        #            ...
        #        ],
        #        "status":"ok"
        #    }
        return self.parse_trades(response['trades'], market, since, limit)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetPrivateBalanceAll(params)
        #    {
        #      timestamp: 1573045496,
        #      status: 'ok',
        #      balances: {
        #        BTC: {
        #          available: '0.00009176',
        #          usdtValue: '40.60',
        #          btcValue: '0.00433932',
        #          total: '0.00433932'
        #        },
        #        ...
        #    }
        balances = self.safe_value(response, 'balances', [])
        keys = list(balances.keys())
        result = {
            'info': response,
        }
        for i in range(0, len(keys)):
            currencySymbol = keys[i]
            balance = balances[currencySymbol]
            code = self.safe_currency_code(currencySymbol)
            total = self.safe_float(balance, 'total', 0)
            available = self.safe_float(balance, 'available', 0)
            account = self.account()
            account['free'] = available
            account['used'] = total - available
            result[code] = account
        return self.parse_balance(result)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        response = None
        if symbol is None:
            response = await self.privateGetPrivateOrdersGetAll()
        else:
            response = await self.privateGetPrivateOrdersGetPair()
        #    {
        #        timestamp: 1573045496,
        #        status: 'ok',
        #        openOrders: [
        #            {
        #                id: '682aeee9-2826-416c-a15a-7e95c1645495',
        #                price: '0.00000448',
        #                type: 'sell',
        #                takeProfit: null,
        #                created: 1571145633,
        #                currencyPair: 'dtrbtc',
        #                remainingAmount: '30.00000000',
        #                amount: '30.00000000'
        #            },
        #            ...
        #        ],
        #    }
        orders = self.safe_value(response, 'openOrders', [])
        result = []
        for i in range(0, len(orders)):
            orderExchange = response['openOrders'][i]
            self.extend(orderExchange, {
                'status': 'open',
            })
            order = await self.parse_order(orderExchange)
            result.append(order)
        return self.filter_by_since_limit(result, since, limit)

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        response = await self.privateGetPrivateOrdersGetId(self.extend({
            'id': id,
        }, params))
        #    {
        #      id: '821e4178-5b97-4ead-8f29-85cd16d3f891',
        #      price: '0.00000444',
        #      amount: '30.00000000',
        #      timestamp: 1573129169,
        #      status: 'ok',
        #      takeProfit: '0.00000448',
        #      type: 'buy',
        #      remainingAmount: '0.00000000',
        #      currencyPair: 'dtrbtc',
        #      orderStatus: 'filled',
        #      created: 1571145631,
        #      trades: [
        #          {
        #              value: '0.00013320',
        #              type: 'sell',
        #              price: '0.00000444',
        #              amount: '30.00000000',
        #              datetime: 1571145632,
        #              id: 3509679
        #          },
        #          ...
        #      ]
        #    }
        return self.parse_order(response)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'tradingPair': market['id'],
            'amount': self.amount_to_precision(symbol, amount),
            'side': side,
            'type': type,
            'price': self.price_to_precision(symbol, price),
        }
        # not !not  takeProfit = forms.DecimalField(required=False, max_digits=30, decimal_places=8)
        # not !not  expireDate = forms.IntegerField(required=False)
        response = await self.privatePostPrivateOrdersAddLimit(self.extend(request, params))
        order = {
            'id': self.safe_string(response, 'orderId'),
            'currencyPair': market['id'],
            'created': self.safe_integer(response, 'timestamp'),
            'status': 'open',
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'remainingAmount': 0,
            'info': response,
        }
        return self.parse_order(order, market)

    async def cancel_order(self, id, symbol=None, params={}):
        response = await self.privatePostPrivateOrdersCancelId(self.extend({
            'id': id,
        }, params))
        return response

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = None
        response = None
        if symbol is None:
            response = await self.privateGetPrivateTradesAllPage(self.extend({
                'page': '1',
            }, params))
        else:
            market = self.market(symbol)
            response = await self.privateGetPrivateTradesPairPage(self.extend({
                'pair': market['id'],
                'page': '1',
            }, params))
        #    {
        #        trades: [
        #            {
        #                id: 3625965,
        #                datetime: 1572720454,
        #                type: 'sell',
        #                amount: '30.00000000',
        #                currencyPair: 'dtrbtc',
        #                price: '0.00000311',
        #                fee: '0.00000000'
        #            },
        #            ...
        #        ],
        #        timestamp: 1573134052,
        #        page: 1,
        #        status: 'ok',
        #        pages: 9
        #    }
        trades = self.safe_value(response, 'trades', [])
        return self.parse_trades(trades, market, since, limit)

    def parse_ticker(self, ticker, market=None):
        symbol = None
        symbolId = self.safe_string(market, 'symbol')
        if (market is None) and (symbolId in list(self.marketsById.keys())):
            market = self.marketsById[symbolId]
        if market is not None:
            symbol = market['symbol']
        lastPrice = self.safe_float(ticker, 'last')
        openPrice = self.safe_float(ticker, 'open')
        change = lastPrice - openPrice
        percentage = 0
        if openPrice != 0:
            percentage = (change / openPrice) * 100
        timestamp = self.safe_timestamp(ticker, 'timestamp')
        baseVolume = self.safe_float(ticker, 'volume')
        vwap = self.safe_float(ticker, 'vwap')
        quoteVolume = baseVolume * vwap
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            'vwap': vwap,
            'open': openPrice,
            'close': lastPrice,
            'last': lastPrice,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': quoteVolume,
            'info': ticker,
        }

    def parse_trade(self, trade, market=None):
        timestamp = self.safe_timestamp(trade, 'datetime')
        id = self.safe_string(trade, 'id')
        side = self.safe_string(trade, 'type')
        price = self.safe_float(trade, 'price')
        amount = self.safe_float(trade, 'amount')
        symbol = None
        if market is not None:
            symbol = self.safe_string(market, 'symbol')
        return {
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'id': id,
            'symbol': symbol,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': price * amount,
            'info': trade,
            'order': None,
            'takerOrMaker': None,
            'type': None,
            'fee': None,
        }

    async def parse_order(self, order, market=None):
        id = self.safe_string(order, 'id')
        timestamp = self.safe_timestamp(order, 'created')
        status = self.parse_order_status('open')
        type = 'limit'
        side = self.safe_string_lower(order, 'type')
        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'amount')
        remaining = self.safe_float(order, 'remainingAmount')
        filled = amount - remaining
        market = self.markets_by_id[order['currencyPair']]
        symbol = self.safe_string(market, 'symbol')
        cost = price * remaining
        trades = None
        tradeExchange = self.safe_value(order, 'trades')
        if tradeExchange is not None:
            trades = self.parse_trades(tradeExchange, market)
        return {
            'id': id,
            'symbol': symbol,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': None,
            'status': status,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'remaining': remaining,
            'cost': cost,
            'filled': filled,
            'trades': trades,
            'info': order,
            'fee': None,
        }

    def parse_order_status(self, status):
        statuses = {
            'open': 'open',
            'filled': 'closed',
            'canceled': 'canceled',
            'expired': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api']
        url += self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            nonce = str(self.nonce())
            auth = nonce + self.apiKey
            signature = self.encode(self.hmac(self.encode(auth), self.encode(self.secret)))
            if method != 'GET':
                body = self.urlencode(query)
            headers = {
                'key': self.apiKey,
                'signature': signature.upper(),
                'nonce': nonce,
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        # 100 API Key is missing
        # 101 Nonce is missing
        # 102 Signature is missing
        # 110 Nonce has to be integer
        # 111 Provided nonce is less or equal to the last nonce
        # 120 Invalid API key
        # 121 Signature is invalid
        # 122 Insufficient permissions
        # 123 IP address not allowed
        # 124 API key not confirmed by e-mail
        # 130 Invalid trading pair
        # 131 Invalid order id
        # 140 Only opened orders can be canceled
        # 150 Parameter {parameter} is invalid with error: {error}
        # 151 Invalid input short
        # 152 General error: {}
        # 160 Invalid currency code
        # 161 Invalid {}
        # 429 API rate limit exceeded
        # 503 API is down for maintenance
        if not isinstance(body, basestring):
            return
        if len(body) == 0:
            return
        if body[0] != '{':
            return
        status = self.safe_string(response, 'status')
        if status == 'error':
            errorCode = self.safe_string(response, 'errorCode')
            reason = self.safe_string(response, 'reason')
            exceptions = self.exceptions
            if errorCode in exceptions:
                raise exceptions[errorCode](reason)
            raise ExchangeError(reason)
