# -*- 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 base64
import hashlib
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import NotSupported
from ccxt.base.errors import DDoSProtection


class btcmarkets (Exchange):

    def describe(self):
        return self.deep_extend(super(btcmarkets, self).describe(), {
            'id': 'btcmarkets',
            'name': 'BTC Markets',
            'countries': ['AU'],  # Australia
            'rateLimit': 1000,  # market data cached for 1 second(trades cached for 2 seconds)
            'has': {
                'CORS': False,
                'fetchOHLCV': True,
                'fetchOrder': True,
                'fetchOrders': True,
                'fetchClosedOrders': 'emulated',
                'fetchOpenOrders': True,
                'fetchMyTrades': True,
                'cancelOrders': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/29142911-0e1acfc2-7d5c-11e7-98c4-07d9532b29d7.jpg',
                'api': {
                    'public': 'https://api.btcmarkets.net',
                    'private': 'https://api.btcmarkets.net',
                    'web': 'https://btcmarkets.net/data',
                },
                'www': 'https://btcmarkets.net',
                'doc': 'https://github.com/BTCMarkets/API',
            },
            'api': {
                'public': {
                    'get': [
                        'market/{id}/tick',
                        'market/{id}/orderbook',
                        'market/{id}/trades',
                        'v2/market/{id}/tickByTime/{timeframe}',
                        'v2/market/{id}/trades',
                        'v2/market/active',
                    ],
                },
                'private': {
                    'get': [
                        'account/balance',
                        'account/{id}/tradingfee',
                        'v2/order/open',
                        'v2/order/open/{id}',
                        'v2/order/history/{id}',
                        'v2/order/trade/history/{id}',
                        'v2/transaction/history/{currency}',
                    ],
                    'post': [
                        'fundtransfer/withdrawCrypto',
                        'fundtransfer/withdrawEFT',
                        'order/create',
                        'order/cancel',
                        'order/history',
                        'order/open',
                        'order/trade/history',
                        'order/createBatch',  # they promise it's coming soon...
                        'order/detail',
                    ],
                },
                'web': {
                    'get': [
                        'market/BTCMarkets/{id}/tickByTime',
                    ],
                },
            },
            'timeframes': {
                '1m': 'minute',
                '1h': 'hour',
                '1d': 'day',
            },
            'exceptions': {
                '3': InvalidOrder,
                '6': DDoSProtection,
            },
        })

    def fetch_markets(self, params={}):
        response = self.publicGetV2MarketActive()
        result = []
        markets = response['markets']
        for i in range(0, len(markets)):
            market = markets[i]
            baseId = market['instrument']
            quoteId = market['currency']
            id = baseId + '/' + quoteId
            base = self.common_currency_code(baseId)
            quote = self.common_currency_code(quoteId)
            symbol = base + '/' + quote
            fee = 0.0085 if (quote == 'AUD') else 0.0022
            pricePrecision = 2
            amountPrecision = 4
            minAmount = 0.001  # where does it come from?
            minPrice = None
            if quote == 'AUD':
                if (base == 'XRP') or (base == 'OMG'):
                    pricePrecision = 4
                amountPrecision = -math.log10(minAmount)
                minPrice = math.pow(10, -pricePrecision)
            precision = {
                'amount': amountPrecision,
                'price': pricePrecision,
            }
            limits = {
                'amount': {
                    'min': minAmount,
                    'max': None,
                },
                'price': {
                    'min': minPrice,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
            }
            result.append({
                'info': market,
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'maker': fee,
                'taker': fee,
                'limits': limits,
                'precision': precision,
            })
        return result

    def fetch_balance(self, params={}):
        self.load_markets()
        balances = self.privateGetAccountBalance()
        result = {'info': balances}
        for b in range(0, len(balances)):
            balance = balances[b]
            currency = balance['currency']
            multiplier = 100000000
            total = float(balance['balance'] / multiplier)
            used = float(balance['pendingFunds'] / multiplier)
            free = total - used
            account = {
                'free': free,
                'used': used,
                'total': total,
            }
            result[currency] = account
        return self.parse_balance(result)

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        multiplier = 100000000  # for price and volume
        return [
            ohlcv[0],
            float(ohlcv[1]) / multiplier,
            float(ohlcv[2]) / multiplier,
            float(ohlcv[3]) / multiplier,
            float(ohlcv[4]) / multiplier,
            float(ohlcv[5]) / multiplier,
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'id': market['id'],
            'timeWindow': self.timeframes[timeframe],
        }
        if since is not None:
            request['since'] = since
        response = self.webGetMarketBTCMarketsIdTickByTime(self.extend(request, params))
        return self.parse_ohlcvs(response['ticks'], market, timeframe, since, limit)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        orderbook = self.publicGetMarketIdOrderbook(self.extend({
            'id': market['id'],
        }, params))
        timestamp = orderbook['timestamp'] * 1000
        return self.parse_order_book(orderbook, timestamp)

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

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        ticker = self.publicGetMarketIdTick(self.extend({
            'id': market['id'],
        }, params))
        return self.parse_ticker(ticker, market)

    def parse_trade(self, trade, market):
        timestamp = trade['date'] * 1000
        return {
            'info': trade,
            'id': str(trade['tid']),
            'order': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': None,
            'side': None,
            'price': trade['price'],
            'amount': trade['amount'],
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        response = self.publicGetMarketIdTrades(self.extend({
            # 'since': 59868345231,
            'id': market['id'],
        }, params))
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        multiplier = 100000000  # for price and volume
        orderSide = 'Bid' if (side == 'buy') else 'Ask'
        order = self.ordered({
            'currency': market['quote'],
        })
        order['currency'] = market['quote']
        order['instrument'] = market['base']
        order['price'] = int(price * multiplier)
        order['volume'] = int(amount * multiplier)
        order['orderSide'] = orderSide
        order['ordertype'] = self.capitalize(type)
        order['clientRequestId'] = str(self.nonce())
        response = self.privatePostOrderCreate(order)
        return {
            'info': response,
            'id': str(response['id']),
        }

    def cancel_orders(self, ids, symbol=None, params={}):
        self.load_markets()
        for i in range(0, len(ids)):
            ids[i] = int(ids[i])
        return self.privatePostOrderCancel({'orderIds': ids})

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        return self.cancel_orders([id])

    def calculate_fee(self, symbol, type, side, amount, price, takerOrMaker='taker', params={}):
        market = self.markets[symbol]
        rate = market[takerOrMaker]
        currency = None
        cost = None
        if market['quote'] == 'AUD':
            currency = market['quote']
            cost = float(self.cost_to_precision(symbol, amount * price))
        else:
            currency = market['base']
            cost = float(self.amount_to_precision(symbol, amount))
        return {
            'type': takerOrMaker,
            'currency': currency,
            'rate': rate,
            'cost': float(self.fee_to_precision(symbol, rate * cost)),
        }

    def parse_my_trade(self, trade, market):
        multiplier = 100000000
        timestamp = trade['creationTime']
        side = 'buy' if (trade['side'] == 'Bid') else 'sell'
        # BTCMarkets always charge in AUD for AUD-related transactions.
        currency = market['quote'] if (market['quote'] == 'AUD') else market['base']
        return {
            'info': trade,
            'id': str(trade['id']),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': None,
            'side': side,
            'price': trade['price'] / multiplier,
            'fee': {
                'currency': currency,
                'cost': trade['fee'] / multiplier,
            },
            'amount': trade['volume'] / multiplier,
            'order': self.safe_string(trade, 'orderId'),
        }

    def parse_my_trades(self, trades, market=None, since=None, limit=None):
        result = []
        for i in range(0, len(trades)):
            trade = self.parse_my_trade(trades[i], market)
            result.append(trade)
        return result

    def parse_order(self, order, market=None):
        multiplier = 100000000
        side = 'buy' if (order['orderSide'] == 'Bid') else 'sell'
        type = 'limit' if (order['ordertype'] == 'Limit') else 'market'
        timestamp = order['creationTime']
        if market is None:
            market = self.market(order['instrument'] + '/' + order['currency'])
        status = 'open'
        if order['status'] == 'Failed' or order['status'] == 'Cancelled' or order['status'] == 'Partially Cancelled' or order['status'] == 'Error':
            status = 'canceled'
        elif order['status'] == 'Fully Matched' or order['status'] == 'Partially Matched':
            status = 'closed'
        price = self.safe_float(order, 'price') / multiplier
        amount = self.safe_float(order, 'volume') / multiplier
        remaining = self.safe_float(order, 'openVolume', 0.0) / multiplier
        filled = amount - remaining
        cost = price * amount
        trades = self.parse_my_trades(order['trades'], market)
        result = {
            'info': order,
            'id': str(order['id']),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': market['symbol'],
            'type': type,
            'side': side,
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'trades': trades,
            'fee': None,
        }
        return result

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        ids = [int(id)]
        response = self.privatePostOrderDetail(self.extend({
            'orderIds': ids,
        }, params))
        numOrders = len(response['orders'])
        if numOrders < 1:
            raise OrderNotFound(self.id + ' No matching order found: ' + id)
        order = response['orders'][0]
        return self.parse_order(order)

    def prepare_history_request(self, market, since=None, limit=None):
        request = self.ordered({
            'currency': market['quote'],
            'instrument': market['base'],
        })
        if limit is not None:
            request['limit'] = limit
        else:
            request['limit'] = 100
        if since is not None:
            request['since'] = since
        else:
            request['since'] = 0
        return request

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise NotSupported(self.id + ': fetchOrders requires a `symbol` parameter.')
        self.load_markets()
        market = self.market(symbol)
        request = self.prepare_history_request(market, since, limit)
        response = self.privatePostOrderHistory(self.extend(request, params))
        return self.parse_orders(response['orders'], market)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise NotSupported(self.id + ': fetchOpenOrders requires a `symbol` parameter.')
        self.load_markets()
        market = self.market(symbol)
        request = self.prepare_history_request(market, since, limit)
        response = self.privatePostOrderOpen(self.extend(request, params))
        return self.parse_orders(response['orders'], market)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        orders = self.fetch_orders(symbol, since, limit, params)
        return self.filter_by(orders, 'status', 'closed')

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise NotSupported(self.id + ': fetchMyTrades requires a `symbol` parameter.')
        self.load_markets()
        market = self.market(symbol)
        request = self.prepare_history_request(market, since, limit)
        response = self.privatePostOrderTradeHistory(self.extend(request, params))
        return self.parse_my_trades(response['trades'], market)

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        uri = '/' + self.implode_params(path, params)
        url = self.urls['api'][api] + uri
        if api == 'private':
            self.check_required_credentials()
            nonce = str(self.nonce())
            # eslint-disable-next-line quotes
            auth = uri + "\n" + nonce + "\n"
            headers = {
                'Content-Type': 'application/json',
                'apikey': self.apiKey,
                'timestamp': nonce,
            }
            if method == 'POST':
                body = self.json(params)
                auth += body
            secret = base64.b64decode(self.secret)
            signature = self.hmac(self.encode(auth), secret, hashlib.sha512, 'base64')
            headers['signature'] = self.decode(signature)
        else:
            if params:
                url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response):
        if len(body) < 2:
            return  # fallback to default error handler
        if body[0] == '{':
            if 'success' in response:
                if not response['success']:
                    error = self.safe_string(response, 'errorCode')
                    message = self.id + ' ' + self.json(response)
                    if error in self.exceptions:
                        ExceptionClass = self.exceptions[error]
                        raise ExceptionClass(message)
                    else:
                        raise ExchangeError(message)
