# -*- 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.base.exchange import Exchange
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import RequestTimeout
from ccxt.base.decimal_to_precision import ROUND
from ccxt.base.decimal_to_precision import DECIMAL_PLACES
from ccxt.base.decimal_to_precision import NO_PADDING


class dx (Exchange):

    def describe(self):
        return self.deep_extend(super(dx, self).describe(), {
            'id': 'dx',
            'name': 'DX.Exchange',
            'countries': ['GB', 'EU'],
            'rateLimit': 1500,
            'version': 'v1',
            'has': {
                'cancelAllOrders': False,
                'cancelOrder': True,
                'cancelOrders': False,
                'CORS': False,
                'createDepositAddress': False,
                'createLimitOrder': True,
                'createMarketOrder': True,
                'createOrder': True,
                'deposit': False,
                'editOrder': False,
                'fetchBalance': True,
                'fetchBidsAsks': False,
                'fetchClosedOrders': True,
                'fetchCurrencies': False,
                'fetchDepositAddress': False,
                'fetchDeposits': False,
                'fetchFundingFees': False,
                'fetchL2OrderBook': False,
                'fetchLedger': False,
                'fetchMarkets': True,
                'fetchMyTrades': False,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': False,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrders': False,
                'fetchTicker': True,
                'fetchTickers': False,
                'fetchTrades': False,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTradingLimits': False,
                'fetchTransactions': False,
                'fetchWithdrawals': False,
                'privateAPI': True,
                'publicAPI': True,
                'withdraw': False,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '1h': '1h',
                '1d': '1d',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/57979980-6483ff80-7a2d-11e9-9224-2aa20665703b.jpg',
                'api': 'https://acl.dx.exchange',
                'www': 'https://dx.exchange',
                'doc': 'https://apidocs.dx.exchange',
                'fees': 'https://dx.exchange/fees',
                'referral': 'https://dx.exchange/registration?dx_cid=20&dx_scname=100001100000038139',
            },
            'requiredCredentials': {
                'apiKey': True,
                'secret': False,
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': 0.25 / 100,
                    'maker': 0.25 / 100,
                    'tiers': {
                        'taker': [
                            [0, 0.25 / 100],
                            [1000000, 0.2 / 100],
                            [5000000, 0.15 / 100],
                            [10000000, 0.1 / 100],
                        ],
                        'maker': [
                            [0, 0.25 / 100],
                            [1000000, 0.2 / 100],
                            [5000000, 0.15 / 100],
                            [10000000, 0.1 / 100],
                        ],
                    },
                },
                'funding': {
                },
            },
            'exceptions': {
                'exact': {
                    'EOF': BadRequest,
                },
                'broad': {
                    'json: cannot unmarshal object into Go value of type': BadRequest,
                    'not allowed to cancel self order': BadRequest,
                    'request timed out': RequestTimeout,
                    'balance_freezing.freezing validation.balance_freeze': InsufficientFunds,
                    'order_creation.validation.validation': InvalidOrder,
                },
            },
            'api': {
                'public': {
                    'post': [
                        'AssetManagement.GetInstruments',
                        'AssetManagement.GetTicker',
                        'AssetManagement.History',
                        'Authorization.LoginByToken',
                        'OrderManagement.GetOrderBook',
                    ],
                },
                'private': {
                    'post': [
                        'Balance.Get',
                        'OrderManagement.Cancel',
                        'OrderManagement.Create',
                        'OrderManagement.OpenOrders',
                        'OrderManagement.OrderHistory',
                    ],
                },
            },
            'commonCurrencies': {
                'BCH': 'Bitcoin Cash',
            },
            'precisionMode': DECIMAL_PLACES,
            'options': {
                'orderTypes': {
                    'market': 1,
                    'limit': 2,
                },
                'orderSide': {
                    'buy': 1,
                    'sell': 2,
                },
            },
        })

    def number_to_object(self, number):
        string = self.decimal_to_precision(number, ROUND, 10, DECIMAL_PLACES, NO_PADDING)
        decimals = self.precision_from_string(string)
        valueStr = string.replace('.', '')
        return {
            'value': self.safe_integer({'a': valueStr}, 'a', None),
            'decimals': decimals,
        }

    def object_to_number(self, obj):
        value = self.decimal_to_precision(obj['value'], ROUND, 0, DECIMAL_PLACES, NO_PADDING)
        decimals = self.decimal_to_precision(-obj['decimals'], ROUND, 0, DECIMAL_PLACES, NO_PADDING)
        return self.safe_float({
            'a': value + 'e' + decimals,
        }, 'a', None)

    def fetch_markets(self, params={}):
        markets = self.publicPostAssetManagementGetInstruments(params)
        instruments = markets['result']['instruments']
        result = []
        for i in range(0, len(instruments)):
            instrument = instruments[i]
            id = self.safe_string(instrument, 'id')
            numericId = self.safe_integer(instrument, 'id')
            asset = self.safe_value(instrument, 'asset', {})
            fullName = self.safe_string(asset, 'fullName')
            base, quote = fullName.split('/')
            amountPrecision = 0
            if instrument['meQuantityMultiplier'] != 0:
                amountPrecision = int(math.log10(instrument['meQuantityMultiplier']))
            base = self.safe_currency_code(base)
            quote = self.safe_currency_code(quote)
            baseId = self.safe_string(asset, 'baseCurrencyId')
            quoteId = self.safe_string(asset, 'quotedCurrencyId')
            baseNumericId = self.safe_integer(asset, 'baseCurrencyId')
            quoteNumericId = self.safe_integer(asset, 'quotedCurrencyId')
            symbol = base + '/' + quote
            result.append({
                'id': id,
                'numericId': numericId,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'baseNumericId': baseNumericId,
                'quoteNumericId': quoteNumericId,
                'info': instrument,
                'precision': {
                    'amount': amountPrecision,
                    'price': self.safe_integer(asset, 'tailDigits'),
                },
                'limits': {
                    'amount': {
                        'min': self.safe_float(instrument, 'minOrderQuantity'),
                        'max': self.safe_float(instrument, 'maxOrderQuantity'),
                    },
                    'price': {
                        'min': 0,
                        'max': None,
                    },
                    'cost': {
                        'min': 0,
                        'max': None,
                    },
                },
            })
        return result

    def parse_ticker(self, ticker, market=None):
        tickerKeys = list(ticker.keys())
        # Python needs an integer to access self.markets_by_id
        # and a string to access the ticker object
        tickerKey = tickerKeys[0]
        instrumentId = self.safe_integer({'a': tickerKey}, 'a')
        ticker = ticker[tickerKey]
        symbol = self.markets_by_id[instrumentId]['symbol']
        last = self.safe_float(ticker, 'last')
        timestamp = self.safe_integer(ticker, 'time') / 1000
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high24'),
            'low': self.safe_float(ticker, 'low24'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': self.safe_float(ticker, 'change'),
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume24'),
            'quoteVolume': self.safe_float(ticker, 'volume24converted'),
            'info': ticker,
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrumentIds': [market['numericId']],
            'currencyId': market['quoteNumericId'],
        }
        response = self.publicPostAssetManagementGetTicker(self.extend(request, params))
        return self.parse_ticker(response['result']['tickers'], market)

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        #
        #     {
        #         "date":1546878960,
        #         "open":0.038064,
        #         "high":0.038064,
        #         "low":0.038064,
        #         "close":0.038064,
        #         "volume":0.00755418,
        #         "id":169042,
        #         "instrumentId":1015,
        #         "type":"1m"
        #     }
        #
        return [
            self.safe_timestamp(ohlcv, 'date'),
            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'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'timestampFrom': since,
            'timestampTill': None,
            'instrumentId': market['numericId'],
            'type': self.timeframes[timeframe],
            'pagination': {
                'limit': limit,
                'offset': 0,
            },
        }
        response = self.publicPostAssetManagementHistory(self.extend(request, params))
        #
        #     {
        #         "id":"1.565248994048e+12",
        #         "result":{
        #             "assets":[
        #                 {"date":1546878960,"open":0.038064,"high":0.038064,"low":0.038064,"close":0.038064,"volume":0.00755418,"id":169042,"instrumentId":1015,"type":"1m"},
        #                 {"date":1546878660,"open":0.037863,"high":0.037863,"low":0.037863,"close":0.037863,"volume":0.0075726,"id":169028,"instrumentId":1015,"type":"1m"},
        #                 {"date":1546860360,"open":0.03864,"high":0.03864,"low":0.03864,"close":0.03864,"volume":0.0013524,"id":168924,"instrumentId":1015,"type":"1m"},
        #                 {"date":1546848480,"open":0.038969,"high":0.038969,"low":0.038969,"close":0.038969,"volume":0.01654819,"id":168880,"instrumentId":1015,"type":"1m"},
        #             ],
        #             "total":{
        #                 "count":52838
        #             }
        #         },
        #         "error":null
        #     }
        #
        return self.parse_ohlcvs(response['result']['assets'], market, timeframe, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'pagination': {
                'limit': limit,
                'offset': 0,
            },
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrumentId'] = market['numericId']
        response = self.privatePostOrderManagementOpenOrders(self.extend(request, params))
        return self.parse_orders(response['result']['orders'], market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        request = {
            'pagination': {
                'limit': limit,
                'offset': 0,
            },
        }
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['instrumentId'] = market['numericId']
        response = self.privatePostOrderManagementOrderHistory(self.extend(request, params))
        return self.parse_orders(response['result']['ordersForHistory'], market, since, limit)

    def parse_order(self, order, market=None):
        orderStatusMap = {
            '1': 'open',
        }
        innerOrder = self.safe_value(order, 'order', None)
        if innerOrder is not None:
            # fetchClosedOrders returns orders in an extra object
            order = innerOrder
            orderStatusMap = {
                '1': 'closed',
                '2': 'canceled',
            }
        side = 'buy'
        if order['direction'] == self.options['orderSide']['sell']:
            side = 'sell'
        status = None
        orderStatus = self.safe_string(order, 'status', None)
        if orderStatus in orderStatusMap:
            status = orderStatusMap[orderStatus]
        marketId = self.safe_string(order, 'instrumentId')
        symbol = None
        if marketId in self.markets_by_id:
            market = self.markets_by_id[marketId]
            symbol = market['symbol']
        orderType = 'limit'
        if order['orderType'] == self.options['orderTypes']['market']:
            orderType = 'market'
        timestamp = self.safe_timestamp(order, 'time')
        quantity = self.object_to_number(order['quantity'])
        filledQuantity = self.object_to_number(order['filledQuantity'])
        id = self.safe_string(order, 'externalOrderId')
        return {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': orderType,
            'side': side,
            'price': self.object_to_number(order['price']),
            'average': None,
            'amount': quantity,
            'remaining': quantity - filledQuantity,
            'filled': filledQuantity,
            'status': status,
            'fee': None,
        }

    def parse_bid_ask(self, bidask, priceKey=0, amountKey=1):
        price = self.object_to_number(bidask[priceKey])
        amount = self.object_to_number(bidask[amountKey])
        return [price, amount]

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'instrumentId': market['numericId'],
        }
        response = self.publicPostOrderManagementGetOrderBook(self.extend(request, params))
        orderbook = self.safe_value(response, 'result')
        return self.parse_order_book(orderbook, None, 'sell', 'buy', 'price', 'qty')

    def sign_in(self, params={}):
        self.check_required_credentials()
        request = {
            'token': self.apiKey,
            'secret': self.secret,
        }
        response = self.publicPostAuthorizationLoginByToken(self.extend(request, params))
        expiresIn = response['result']['expiry']
        self.options['expires'] = self.sum(self.milliseconds(), expiresIn * 1000)
        self.options['accessToken'] = response['result']['token']
        return response

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privatePostBalanceGet(params)
        result = {'info': response}
        balances = self.safe_value(response['result'], 'balance')
        currencyIds = list(balances.keys())
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            balance = self.safe_value(balances, currencyId, {})
            code = self.safe_currency_code(currencyId)
            account = {
                'free': self.safe_float(balance, 'available'),
                'used': self.safe_float(balance, 'frozen'),
                'total': self.safe_float(balance, 'total'),
            }
            result[code] = account
        return self.parse_balance(result)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        direction = self.safe_integer(self.options['orderSide'], side)
        market = self.market(symbol)
        order = {
            'direction': direction,
            'instrumentId': market['numericId'],
            'orderType': 2,
            'quantity': self.number_to_object(amount),
        }
        order['orderType'] = self.options['orderTypes'][type]
        if type == 'limit':
            order['price'] = self.number_to_object(price)
        request = {
            'order': order,
        }
        result = self.privatePostOrderManagementCreate(self.extend(request, params))
        # todo: rewrite for parseOrder
        return {
            'info': result,
            'id': result['result']['externalOrderId'],
        }

    def cancel_order(self, id, symbol=None, params={}):
        request = {'externalOrderId': id}
        return self.privatePostOrderManagementCancel(self.extend(request, params))

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        if isinstance(params, list):
            arrayLength = len(params)
            if arrayLength == 0:
                # In PHP params = array() causes self to fail, because
                # the API requests an object, not an array, even if it is empty
                params = {'__associative': True}
        parameters = {
            'jsonrpc': '2.0',
            'id': self.milliseconds(),
            'method': path,
            'params': [params],
        }
        url = self.urls['api']
        headers = {'Content-Type': 'application/json-rpc'}
        if method == 'GET':
            if parameters:
                url += '?' + self.urlencode(parameters)
        else:
            body = self.json(parameters)
        if api == 'private':
            token = self.safe_string(self.options, 'accessToken')
            if token is None:
                raise AuthenticationError(self.id + ' ' + path + ' endpoint requires a prior call to signIn() method')
            expires = self.safe_integer(self.options, 'expires')
            if expires is not None:
                if self.milliseconds() >= expires:
                    raise AuthenticationError(self.id + ' accessToken expired, call signIn() method')
            headers['Authorization'] = token
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if not response:
            return  # fallback to default error handler
        error = response['error']
        if error:
            feedback = self.id + ' ' + self.json(response)
            exact = self.exceptions['exact']
            if error in exact:
                raise exact[error](feedback)
            broad = self.exceptions['broad']
            broadKey = self.findBroadlyMatchedKey(broad, error)
            if broadKey is not None:
                raise broad[broadKey](feedback)
            raise ExchangeError(feedback)  # unknown error
