# -*- 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 InsufficientFunds
from ccxt.base.errors import DDoSProtection
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import InvalidNonce


class bitforex (Exchange):

    def describe(self):
        return self.deep_extend(super(bitforex, self).describe(), {
            'id': 'bitforex',
            'name': 'BitForex',
            'countries': 'SG',
            'version': 'v1',
            'has': {
                'fetchTickers': True,
                'fetchOpenOrders': True,
                'fetchOrders': True,
                'fetchMyTrades': True,
            },
            'urls': {
                'logo': '',
                'api': 'https://api.bitforex.com/api',
                'www': 'https://www.bitforex.com',
                'doc': 'https://github.com/bitforexapi/API_Docs/wiki',
                'fees': 'https://support.bitforex.com/hc/en-us/articles/360006824872-Trading-Fees',
                'referral': 'https://www.bitforex.com',
            },
            'api': {
                'public': {
                    'get': [
                        'market/symbols',
                        'market/ticker',
                        'market/depth',
                        'market/trades',
                        'market/kline',
                    ],
                },
                'private': {
                    'post': [
                        'fund/mainAccount',
                        'fund/allAccount',
                        'trade/placeOrder',
                        'trade/placeMultiOrder',
                        'trade/cancelOrder',
                        'trade/cancelMultiOrder',
                        'trade/cancelAllOrder',
                        'trade/orderInfo',
                        'trade/multiOrderInfo',
                        'trade/orderInfos',
                    ],
                },
            },
            'exceptions': {
                # see https://github.com/bitforexapi/API_Docs/wiki/%E5%BC%82%E5%B8%B8%E4%BB%A3%E7%A0%81%E8%AF%B4%E6%98%8E
                '1000': ExchangeNotAvailable,  # "syntax error"
                '1001': ExchangeNotAvailable,
                '1002': ExchangeError,
                '1003': ExchangeError,
                '1010': ExchangeNotAvailable,
                '1011': AuthenticationError,
                '1012': InvalidNonce,
                '1013': AuthenticationError,
                '1014': ExchangeError,
                '1015': DDoSProtection,
                '1016': AuthenticationError,
                '1017': ExchangeNotAvailable,
                '1018': ExchangeNotAvailable,
                '1019': ExchangeError,
                '1020': ExchangeError,
                '1021': ExchangeError,
                '3001': ExchangeError,
                '3002': InsufficientFunds,
                '4001': ExchangeError,
                '4002': ExchangeError,
                '4003': ExchangeError,
                '4004': ExchangeError,
            },
            'fees': {
                'trading': {
                    'maker': 0,
                    'taker': 0.05 / 100,
                },
                'funding': {
                    # HARDCODING IS DEPRECATED THE FEES BELOW ARE TO BE REMOVED SOON
                    'withdraw': {
                    },
                },
            },
        })

    def fetch_markets(self):
        response = self.publicGetMarketSymbols()
        markets = response['data']
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            id = market['symbol']
            symbolParams = id.split('-')
            baseId = symbolParams[2]
            quoteId = symbolParams[1]
            base = self.common_currency_code(baseId)
            quote = self.common_currency_code(quoteId)
            symbol = base + '/' + quote
            precision = {
                'amount': market['amountPrecision'],
                'price': market['pricePrecision'],
            }
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': True,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -market['minOrderAmount']),
                        'max': None,
                    },
                    'price': {
                        'min': math.pow(10, -market['pricePrecision']),
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    def parse_ticker(self, ticker, market=None):
        #
        #     [
        #         {
        #             "volume": "190.4925000000000000",
        #             "open": "0.0777371200000000",
        #             "market_uuid": "38dd30bf-76c2-4777-ae2a-a3222433eef3",
        #             "market_id": "ETH-BTC",
        #             "low": "0.0742925600000000",
        #             "high": "0.0789150000000000",
        #             "daily_change_perc": "-0.3789180767180466680525339760",
        #             "daily_change": "-0.0002945600000000",
        #             "close": "0.0774425600000000",  # last price
        #             "bid": {
        #                 "price": "0.0764777900000000",
        #                 "amount": "6.4248000000000000"
        #             },
        #             "ask": {
        #                 "price": "0.0774425600000000",
        #                 "amount": "1.1741000000000000"
        #             }
        #         }
        #     ]
        #
        if market is not None:
            marketId = self.safe_string(ticker, 'market_id')
            if marketId in self.markets_by_id:
                market = self.markets_by_id[marketId]
        symbol = None
        if market is not None:
            symbol = market['symbol']
        timestamp = self.milliseconds()
        close = self.safe_float(ticker, 'close')
        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'], 'price'),
            'bidVolume': self.safe_float(ticker['bid'], 'amount'),
            'ask': self.safe_float(ticker['ask'], 'price'),
            'askVolume': self.safe_float(ticker['ask'], 'amount'),
            'vwap': None,
            'open': self.safe_float(ticker, 'open'),
            'close': close,
            'last': close,
            'previousDayClose': None,
            'change': self.safe_float(ticker, 'daily_change'),
            'percentage': self.safe_float(ticker, 'daily_change_perc'),
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': None,
            'info': ticker,
        }

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

    def fetch_order_book(self, symbol, params={}):
        self.load_markets()
        response = self.publicGetMarketsSymbolBook(self.extend({
            'symbol': self.market_id(symbol),
        }, params))
        return self.parse_order_book(response['data'], None, 'bids', 'asks', 'price', 'amount')

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market_id': market['id'],
        }
        response = self.privateGetViewerOrders(self.extend(request, params))
        return self.parse_orders(response['data']['edges'], market, since, limit)

    def parse_trade(self, trade, market=None):
        timestamp = self.parse8601(trade['created_at'])
        price = float(trade['price'])
        amount = float(trade['amount'])
        symbol = market['symbol']
        cost = self.cost_to_precision(symbol, price * amount)
        side = trade['trade_side'] == 'sell' if 'ASK' else 'buy'
        return {
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': self.safe_string(trade, 'trade_id'),
            'order': None,
            'type': 'limit',
            'side': side,
            'price': price,
            'amount': amount,
            'cost': float(cost),
            'fee': None,
            'info': trade,
        }

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

    def fetch_balance(self, params={}):
        self.load_markets()
        response = self.privatePostFundAllAccount(params)
        result = {'info': response}
        balances = response['data']
        for i in range(0, len(balances)):
            balance = balances[i]
            id = balance['currency']
            currency = self.common_currency_code(id)
            account = {
                'free': float(balance['active']),
                'used': float(balance['frozen']),
                'total': float(balance['fix']),
            }
            result[currency] = account
        return self.parse_balance(result)

    def parse_order(self, order, market=None):
        order_data = self.safe_value(order, 'node')
        if order_data:
            order = order['node']
        marketId = self.safe_string(order, 'market_id')
        symbol = None
        if marketId and not market and(marketId in list(self.marketsById.keys())):
            market = self.marketsById[marketId]
        if market:
            symbol = market['symbol']
        timestamp = self.parse8601(order['inserted_at'])
        price = float(order['price'])
        amount = self.safe_float(order, 'amount')
        filled = self.safe_float(order, 'filled_amount')
        remaining = amount - filled
        status = self.parse_order_status(order['state'])
        if status == 'filled':
            status = 'closed'
        side = self.safe_string(order, 'side')
        if side == 'BID':
            side = 'buy'
        else:
            side = 'sell'
        return {
            'id': self.safe_string(order, 'id'),
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'status': status,
            'symbol': symbol,
            'type': 'limit',
            'side': side,
            'price': price,
            'cost': None,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'trades': None,
            'fee': None,
            'info': order,
        }

    def parse_order_status(self, status):
        statuses = {
            'CANCELED': 'canceled',
            'PENDING': 'open',
            'FILLED': 'closed',
        }
        if status in statuses:
            return statuses[status]
        return status

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        response = self.privatePostViewerOrders(self.extend({
            'market_id': market['info']['uuid'],
            'side': (side == 'BID' if 'buy' else 'ASK'),
            'amount': amount,
            'price': price,
        }, params))
        # TODO: what's the actual response here
        return response['data']

    def cancel_order(self, id, symbol=None, params={}):
        response = self.privatePostViewerOrdersOrderIdCancel(self.extend({
            'order_id': id,
        }, params))
        return response

    def cancel_orders(self, symbol=None, params={}):
        request = {}
        if symbol is not None:
            self.load_markets()
            market = self.market(symbol)
            request['market_id'] = market['info']['uuid']
        #
        # the caching part to be removed
        #
        #     response = self.privatePostOrderCancelAll(self.extend(request, params))
        #     openOrders = self.filter_by(self.orders, 'status', 'open')
        #     for i in range(0, len(openOrders)):
        #         order = openOrders[i]
        #         orderId = order['id']
        #         self.orders[orderId]['status'] = 'canceled'
        #     }
        #     return response
        #
        return self.privatePostViewerOrdersCancelAll(self.extend(request, params))

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        response = self.privateGetOrdersId(self.extend({
            'id': id,
        }, params))
        return self.parse_order(response['data'])

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        # TODO: check if it's for open orders only
        request = {
            'market': market['id'],
        }
        if limit:
            request['limit'] = limit
        response = self.privateGetOrders(self.extend(request, params))
        return self.parse_orders(response['data'], market, since, limit)

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
        }
        if limit:
            request['limit'] = limit
        response = self.privateGetTrades(self.extend(request, params))
        trades = response['data']['trade_history']
        return self.parse_trades(trades, market, since, limit)

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        response = self.privateGetAccountsCurrency(self.extend({
            'currency': currency['id'],
        }, params))
        address = self.safe_string(response['data'], 'public_key')
        status = 'ok' if address else 'none'
        return {
            'currency': code,
            'address': address,
            'status': status,
            'info': response,
        }

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'withdrawal_type': currency['id'],
            'address': address,
            'amount': amount,
            # 'fee': 0.0,
            # 'asset_pin': 'YOUR_ASSET_PIN',
        }
        if tag:
            # probably it's not the same
            request['label'] = tag
        response = self.privatePostWithdrawals(self.extend(request, params))
        return {
            'id': None,
            'info': response,
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        query = self.omit(params, self.extract_params(path))
        url = self.urls['api'] + '/' + self.version + '/' + self.implode_params(path, params)
        if api == 'public':
            if query:
                url += '?' + self.urlencode(query)
        else:
            self.check_required_credentials()
            nonce = self.nonce()
            request = {
                'type': 'OpenAPI',
                'sub': self.apiKey,
                'nonce': nonce * 1000000000,
            }
            headers['Authorization'] = 'Bearer ' + self.jwt(request, self.secret)
            if method == 'GET':
                if query:
                    url += '?' + self.urlencode(query)
            elif method == 'POST':
                headers['Content-Type'] = 'application/json'
                body = self.json(query)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def request(self, path, api='public', method='GET', params={}, headers=None, body=None):
        response = self.fetch2(path, api, method, params, headers, body)
        error = self.safe_value(response, 'error')
        data = self.safe_value(response, 'data')
        if error or data is None:
            code = self.safe_integer(error, 'code')
            errorClasses = {
                '401': AuthenticationError,
            }
            message = self.safe_string(error, 'description', 'Error')
            ErrorClass = self.safe_string(errorClasses, code, ExchangeError)
            raise ErrorClass(message)
        return response
