# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt_zagrava/ccxt_zagrava/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt_zagrava.base.exchange import Exchange
from ccxt_zagrava.abstract.btcbox import ImplicitAPI
import hashlib
import json
from ccxt_zagrava.base.types import Balances, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import DDoSProtection
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class btcbox(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(btcbox, self).describe(), {
            'id': 'btcbox',
            'name': 'BtcBox',
            'countries': ['JP'],
            'rateLimit': 1000,
            'version': 'v1',
            'pro': False,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelOrder': True,
                'closeAllPositions': False,
                'closePosition': False,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLeverage': False,
                'fetchMarginMode': False,
                'fetchMarkOHLCV': False,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': True,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTransfer': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': False,
                'reduceMargin': False,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': False,
                'ws': False,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87327317-98c55400-c53c-11ea-9a11-81f7d951cc74.jpg',
                'api': {
                    'rest': 'https://www.btcbox.co.jp/api',
                },
                'www': 'https://www.btcbox.co.jp/',
                'doc': 'https://blog.btcbox.jp/en/archives/8762',
                'fees': 'https://support.btcbox.co.jp/hc/en-us/articles/360001235694-Fees-introduction',
            },
            'api': {
                'public': {
                    'get': [
                        'depth',
                        'orders',
                        'ticker',
                        'tickers',
                    ],
                },
                'private': {
                    'post': [
                        'balance',
                        'trade_add',
                        'trade_cancel',
                        'trade_list',
                        'trade_view',
                        'wallet',
                    ],
                },
            },
            'precisionMode': TICK_SIZE,
            'exceptions': {
                '104': AuthenticationError,
                '105': PermissionDenied,
                '106': InvalidNonce,
                '107': InvalidOrder,  # price should be an integer
                '200': InsufficientFunds,
                '201': InvalidOrder,  # amount too small
                '202': InvalidOrder,  # price should be [0 : 1000000]
                '203': OrderNotFound,
                '401': OrderNotFound,  # cancel canceled, closed or non-existent order
                '402': DDoSProtection,
            },
        })

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for ace
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.publicGetTickers()
        #
        marketIds = list(response.keys())
        markets = []
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            symbolParts = marketId.split('_')
            baseCurr = self.safe_string(symbolParts, 0)
            quote = self.safe_string(symbolParts, 1)
            quoteId = quote.lower()
            id = baseCurr.lower()
            res = response[marketId]
            symbol = baseCurr + '/' + quote
            fee = self.parse_number('0.0005') if (id == 'BTC') else self.parse_number('0.0010')
            markets.append(self.safe_market_structure({
                'id': id,
                'uppercaseId': None,
                'symbol': symbol,
                'base': baseCurr,
                'baseId': id,
                'quote': quote,
                'quoteId': quoteId,
                'settle': None,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'taker': fee,
                'maker': fee,
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                },
                'precision': {
                    'price': None,
                    'amount': None,
                },
                'active': None,
                'created': None,
                'info': res,
            }))
        return markets

    def parse_market(self, market: dict) -> Market:
        baseId = self.safe_string(market, 'base')
        base = self.safe_currency_code(baseId)
        quoteId = self.safe_string(market, 'quote')
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        return {
            'id': self.safe_string(market, 'symbol'),
            'uppercaseId': None,
            'symbol': symbol,
            'base': base,
            'baseId': baseId,
            'quote': quote,
            'quoteId': quoteId,
            'settle': None,
            'settleId': None,
            'type': 'spot',
            'spot': True,
            'margin': False,
            'swap': False,
            'future': False,
            'option': False,
            'contract': False,
            'linear': None,
            'inverse': None,
            'contractSize': None,
            'expiry': None,
            'expiryDatetime': None,
            'strike': None,
            'optionType': None,
            'limits': {
                'amount': {
                    'min': self.safe_number(market, 'minLimitBaseAmount'),
                    'max': self.safe_number(market, 'maxLimitBaseAmount'),
                },
                'price': {
                    'min': None,
                    'max': None,
                },
                'cost': {
                    'min': None,
                    'max': None,
                },
                'leverage': {
                    'min': None,
                    'max': None,
                },
            },
            'precision': {
                'price': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
                'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'basePrecision'))),
            },
            'active': None,
            'created': None,
            'info': market,
        }

    def parse_balance(self, response) -> Balances:
        result: dict = {'info': response}
        codes = list(self.currencies.keys())
        for i in range(0, len(codes)):
            code = codes[i]
            currency = self.currency(code)
            currencyId = currency['id']
            free = currencyId + '_balance'
            if free in response:
                account = self.account()
                used = currencyId + '_lock'
                account['free'] = self.safe_string(response, free)
                account['used'] = self.safe_string(response, used)
                result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://blog.btcbox.jp/en/archives/8762#toc13
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        self.load_markets()
        response = self.privatePostBalance(params)
        return self.parse_balance(response)

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://blog.btcbox.jp/en/archives/8762#toc6
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {}
        numSymbols = len(self.symbols)
        if numSymbols > 1:
            request['coin'] = market['baseId']
        response = self.publicGetDepth(self.extend(request, params))
        return self.parse_order_book(response, market['symbol'])

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        symbol = self.safe_symbol(None, market)
        last = self.safe_string(ticker, 'last')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'buy'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'sell'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'vol'),
            'quoteVolume': self.safe_string(ticker, 'volume'),
            'info': ticker,
        }, market)

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://blog.btcbox.jp/en/archives/8762#toc5
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {}
        numSymbols = len(self.symbols)
        if numSymbols > 1:
            request['coin'] = market['baseId']
        response = self.publicGetTicker(self.extend(request, params))
        return self.parse_ticker(response, market)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[] [symbols]: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        response = self.publicGetTickers(params)
        return self.parse_tickers(response, symbols)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # fetchTrades(public)
        #
        #      {
        #          "date":"0",
        #          "price":3,
        #          "amount":0.1,
        #          "tid":"1",
        #          "type":"buy"
        #      }
        #
        timestamp = self.safe_timestamp(trade, 'date')
        market = self.safe_market(None, market)
        id = self.safe_string(trade, 'tid')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'amount')
        type = None
        side = self.safe_string(trade, 'type')
        return self.safe_trade({
            'info': trade,
            'id': id,
            'order': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': market['symbol'],
            'type': type,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': None,
        }, market)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://blog.btcbox.jp/en/archives/8762#toc7
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {}
        numSymbols = len(self.symbols)
        if numSymbols > 1:
            request['coin'] = market['baseId']
        response = self.publicGetOrders(self.extend(request, params))
        #
        #     [
        #          {
        #              "date":"0",
        #              "price":3,
        #              "amount":0.1,
        #              "tid":"1",
        #              "type":"buy"
        #          },
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://blog.btcbox.jp/en/archives/8762#toc18
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'amount': amount,
            'price': price,
            'type': side,
            'coin': market['baseId'],
        }
        response = self.privatePostTradeAdd(self.extend(request, params))
        #
        #     {
        #         "result":true,
        #         "id":"11"
        #     }
        #
        return self.parse_order(response, market)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://blog.btcbox.jp/en/archives/8762#toc17
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        # a special case for btcbox – default symbol is BTC/JPY
        if symbol is None:
            symbol = 'BTC/JPY'
        market = self.market(symbol)
        request: dict = {
            'id': id,
            'coin': market['baseId'],
        }
        response = self.privatePostTradeCancel(self.extend(request, params))
        #
        #     {"result":true, "id":"11"}
        #
        return self.parse_order(response, market)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            # TODO: complete list
            'part': 'open',  # partially or not at all executed
            'all': 'closed',  # fully executed
            'cancelled': 'canceled',
            'closed': 'closed',  # never encountered, seems to be bug in the doc
            'no': 'closed',  # not clarified in the docs...
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #     {
        #         "id":11,
        #         "datetime":"2014-10-21 10:47:20",
        #         "type":"sell",
        #         "price":42000,
        #         "amount_original":1.2,
        #         "amount_outstanding":1.2,
        #         "status":"closed",
        #         "trades":[]  # no clarification of trade value structure of order endpoint
        #     }
        #
        id = self.safe_string(order, 'id')
        datetimeString = self.safe_string(order, 'datetime')
        timestamp = None
        if datetimeString is not None:
            timestamp = self.parse8601(order['datetime'] + '+09:00')  # Tokyo time
        amount = self.safe_string(order, 'amount_original')
        remaining = self.safe_string(order, 'amount_outstanding')
        price = self.safe_string(order, 'price')
        # status is set by fetchOrder method only
        status = self.parse_order_status(self.safe_string(order, 'status'))
        # fetchOrders do not return status, use heuristic
        if status is None:
            if Precise.string_equals(remaining, '0'):
                status = 'closed'
        trades = None  # todo: self.parse_trades(order['trades'])
        market = self.safe_market(None, market)
        side = self.safe_string(order, 'type')
        return self.safe_order({
            'id': id,
            'clientOrderId': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'amount': amount,
            'remaining': remaining,
            'filled': None,
            'side': side,
            'type': None,
            'timeInForce': None,
            'postOnly': None,
            'status': status,
            'symbol': market['symbol'],
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'cost': None,
            'trades': trades,
            'fee': None,
            'info': order,
            'average': None,
        }, market)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://blog.btcbox.jp/en/archives/8762#toc16
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.load_markets()
        # a special case for btcbox – default symbol is BTC/JPY
        if symbol is None:
            symbol = 'BTC/JPY'
        market = self.market(symbol)
        request = self.extend({
            'id': id,
            'coin': market['baseId'],
        }, params)
        response = self.privatePostTradeView(self.extend(request, params))
        #
        #      {
        #          "id":11,
        #          "datetime":"2014-10-21 10:47:20",
        #          "type":"sell",
        #          "price":42000,
        #          "amount_original":1.2,
        #          "amount_outstanding":1.2,
        #          "status":"closed",
        #          "trades":[]
        #      }
        #
        return self.parse_order(response, market)

    def fetch_orders_by_type(self, type, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        self.load_markets()
        # a special case for btcbox – default symbol is BTC/JPY
        if symbol is None:
            symbol = 'BTC/JPY'
        market = self.market(symbol)
        request: dict = {
            'type': type,  # 'open' or 'all'
            'coin': market['baseId'],
        }
        response = self.privatePostTradeList(self.extend(request, params))
        #
        # [
        #      {
        #          "id":"7",
        #          "datetime":"2014-10-20 13:27:38",
        #          "type":"buy",
        #          "price":42750,
        #          "amount_original":0.235,
        #          "amount_outstanding":0.235
        #      },
        # ]
        #
        orders = self.parse_orders(response, market, since, limit)
        # status(open/closed/canceled) is None
        # btcbox does not return status, but we know it's 'open' queried for open orders
        if type == 'open':
            for i in range(0, len(orders)):
                orders[i]['status'] = 'open'
        return orders

    def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple orders made by the user
        :see: https://blog.btcbox.jp/en/archives/8762#toc15
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        return self.fetch_orders_by_type('all', symbol, since, limit, params)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://blog.btcbox.jp/en/archives/8762#toc15
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        return self.fetch_orders_by_type('open', symbol, since, limit, params)

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

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        url = self.urls['api']['rest'] + '/' + self.version + '/' + path
        if api == 'public':
            if params:
                url += '?' + self.urlencode(params)
        else:
            self.check_required_credentials()
            nonce = str(self.nonce())
            query = self.extend({
                'key': self.apiKey,
                'nonce': nonce,
            }, params)
            request = self.urlencode(query)
            secret = self.hash(self.encode(self.secret), 'sha256')
            query['signature'] = self.hmac(self.encode(request), self.encode(secret), hashlib.sha256)
            body = self.urlencode(query)
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if response is None:
            return None  # resort to defaultErrorHandler
        # typical error response: {"result":false,"code":"401"}
        if httpCode >= 400:
            return None  # resort to defaultErrorHandler
        result = self.safe_value(response, 'result')
        if result is None or result is True:
            return None  # either public API(no error codes expected) or success
        code = self.safe_value(response, 'code')
        feedback = self.id + ' ' + body
        self.throw_exactly_matched_exception(self.exceptions, code, feedback)
        raise ExchangeError(feedback)  # unknown message

    def request(self, path, api='public', method='GET', params={}, headers=None, body=None, config={}):
        response = self.fetch2(path, api, method, params, headers, body, config)
        if isinstance(response, str):
            # sometimes the exchange returns whitespace prepended to json
            response = self.strip(response)
            if not self.is_json_encoded_object(response):
                raise ExchangeError(self.id + ' ' + response)
            response = json.loads(response)
        return response
