'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require('./base/Exchange');

//  ---------------------------------------------------------------------------

module.exports = class nokuexchange extends Exchange {

    describe () {
        return this.deepExtend(super.describe(), {
            'id': 'nokuexchange',
            'name': 'NokuExchange',
            'countries': 'CH',
            'enableRateLimit': true,
            'rateLimit': 2000, // milliseconds = seconds * 1000
            'certified': false,
            'has': {
                'cancelAllOrders': false,
                'cancelOrder': true,
                'cancelOrders': false,
                'CORS': false,
                'createDepositAddress': true,
                'createLimitOrder': false,
                'createMarketOrder': false,
                'createOrder': true,
                'deposit': false,
                'editOrder': false,
                'fetchBalance': true,
                'fetchBidsAsks': false,
                'fetchClosedOrders': false,
                'fetchCurrencies': false,
                'fetchDepositAddress': true,
                'fetchDeposits': true,
                'fetchFundingFees': false,
                'fetchL2OrderBook': false,
                'fetchLedger': false,
                'fetchMarkets': true,
                'fetchMyTrades': true,
                'fetchOHLCV': false,
                'fetchOpenOrders': false,
                'fetchOrder': false,
                'fetchOrderBook': true,
                'fetchOrderBooks': false,
                'fetchOrders': true,
                'fetchTicker': false,
                'fetchTickers': false,
                'fetchTrades': true,
                'fetchTradingFee': false,
                'fetchTradingFees': false,
                'fetchTradingLimits': false,
                'fetchTransactions': false,
                'fetchWithdrawals': true,
                'privateAPI': true,
                'publicAPI': true,
                'withdraw': true,
            },
            'urls': {
                'api': 'https://api.noku.exchange',
                'www': 'https://noku.exchange',
                'doc': 'https://docapi.noku.exchange',
            },
            'api': {
                'public': {
                    'get': [
                        'data/instruments',
                        'data/assets',
                        'data/rates',
                    ],
                    'post': [
                        'data/depth',
                        'data/history',
                        'data/candles',
                    ]
                },
                'private': {
                    'get': [
                        'user/limits',
                        'user/type',
                        'user/balance',
                    ],
                    'post': [
                        'exchange/order',
                        'exchange/transactions',
                        'wallet/depositAddress',
                        'wallet/ccxtdata',
                    ],
                    'put': [
                        'exchange/order',
                        'wallet/withdrawal',
                    ],
                    'delete': [
                        'exchange/order/{market}/{id}',
                    ]
                }
            },
            'aliases': {
                'eur': 'EUR',
                'bitcoin': 'BTC',
                'ethereum': 'ETH',
                'zcash': 'ZEC',
                'dash': 'DASH',
                'litecoin': 'LTC',
                'bitcoincash': 'BCH',
                'pippofranchi': 'PHF',
                'dogecoin': 'DOGE',
                'eosio.token': 'EOS'
            },
            'assets': {
                'EUR': 'eur',
                'BTC': 'bitcoin',
                'ETH': 'ethereum',
                'ZEC': 'zcash',
                'DASH': 'dash',
                'LTC': 'litecoin',
                'BCH': 'bitcoincash',
                'PHF': 'pippofranchi',
                'DOGE': 'dogecoin',
                'EOS': 'eosio.token'
            },
            'markets': {
                'BTCEUR': { 'id': 'btceur', 'symbol': 'BTC/EUR', 'base': 'BTC', 'quote': 'EUR' },
                'ETHEUR': { 'id': 'etheur', 'symbol': 'ETH/EUR', 'base': 'ETH', 'quote': 'EUR' },
                'ETHBTC': { 'id': 'ethbtc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC' },
                'LTCBTC': { 'id': 'ltcbtc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC' },
                'DASHBTC': { 'id': 'dashbtc', 'symbol': 'DASH/BTC', 'base': 'DASH', 'quote': 'BTC' },
                'ZECBTC': { 'id': 'zecbtc', 'symbol': 'ZEC/BTC', 'base': 'ZEC', 'quote': 'BTC' },
                'PHFETH': { 'id': 'phfeth', 'symbol': 'PHF/ETH', 'base': 'PHF', 'quote': 'ETH' }
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '10m': '10',
                '15m': '15',
                '30m': '30',
                '1h': '60',
                '2h': '120',
                '4h': '240',
                '1d': '1440',
                '2d': '2880'
            },
            'version': 'v1',
            'requiredCredentials': {
                'apiKey':     false,
                'secret':     false,
                'uid':        false,
                'login':      false,
                'password':   false,
                'twofa':      false, // 2-factor authentication (one-time password key)
                'privateKey': false, // a "0x"-prefixed hexstring private key for a wallet
                'walletAddress': false, // the wallet address "0x"-prefixed hexstring
                'token':      true, // reserved for HTTP auth in some cases
            },
        }) // return
    } // describe ()

    async fetchMarkets () {
        const response = await this.publicGetDataInstruments();
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const markets = result.map(market => {
            const { ticker, stock, money, options } = market;
            return {
                info: market,
                id: ticker.toLowerCase(),
                symbol: this.markets[ticker].symbol,
                base: stock.alias,
                quote: money.alias,
                baseId: stock.alias.toLowerCase(),
                quoteId: money.alias.toLowerCase(),
                active: true,
                precision: {
                    price: options.decimal_precision,
                    amount: options.stock_precision,
                    cost: 8
                },
                limits: {
                    amount: {
                        min: Math.pow(10, - options.min_amount_pow),
                        max: Math.pow(10, options.max_amount_pow)
                    },
                    price: {
                        min: Math.pow(10, -8),
                        max: Math.pow(10, 8)
                    },
                    cost: {
                        min: Math.pow(10, -8),
                        max: Math.pow(10, 8)
                    }
                }
            };
        });
        return markets;
    }

    async fetchDataAssets () {
        const response = await this.publicGetDataAssets();
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return result;
    }

    async fetchDataRates () {
        const response = await this.publicGetDataRates();
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return result;
    }

    async fetchOHLCV (symbol, resolution, since, limit, params = {}) {
        const request = this.extend({ 
            symbol: symbol.replace('/', ''),
            resolution,
            since: Math.floor(since / 1000),
            limit
        }, params);
        const response = await this.publicPostDataCandles(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        let klines = result;
        if (!(klines instanceof Array)) {
            throw new Error('Invalid klines');
        }
        return klines.map(kline => {
            if (!(kline instanceof Array)) {
                throw new Error('Invalid kline');
            } else {
                kline[0] *= 1000
                return kline.slice(0, 6).map(v => Number(v));
            }
        });
    }

    async fetchOrderBook (market, params = {}) {
        const request = this.extend({
            market: market.replace('/', '')
        }, params);
        const response = await this.publicPostDataDepth(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return result;
    }

    async fetchTrades (market, params = {}) {
        const request = this.extend({
            market: market.replace('/', '')
        }, params);
        const response = await this.publicPostDataHistory(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const trades = result.map(trade => {
            const { id, time, market, side, price, amount } = trade;
            return {
                info: trade,
                id,
                timestamp: Math.floor(time * 1000),
                datetime: new Date(time * 1000),
                symbol: this.markets[market].symbol,
                order: null,
                type: null,
                side,
                price: Number(price),
                amount: Number(amount)
            }
        })
        return trades;
    }

    async fetchMyTrades (market, params = {}) {
        const request = this.extend({
            market: market.replace('/', '')
        }, params);
        const response = await this.privatePostExchangeTransactions(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const trades = result.map(trade => {
            const { id, time, market, side, price, amount, role, fee, feeAsset } = trade;
            return {
                info: trade,
                id,
                timestamp: Math.floor(time * 1000),
                datetime: new Date(time * 1000),
                symbol: this.markets[market].symbol,
                order: null,
                type: null,
                side,
                takerOrMaker: role,
                price: Number(price),
                amount: Number(amount),
                cost: Number(price) * Number(amount),
                fee: {
                    currency: feeAsset,
                    cost: fee
                }
            };
        });
        return trades;
    }

    async fetchOrders (market, offset, limit, params = {}) {
        const request = this.extend({
            market: market.replace('/', ''),
            offset,
            limit
        }, params);
        const response = await this.privatePostExchangeOrder(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const orders = result.map(order => {
            const { id, time, market, type, side, price, amount, left, 
                dealFee,
                feeAsset,
                dealFeeTh,
                feeAssetTh,
                feeAssetPrice
            } = order;
            const fee = dealFeeTh ? dealFeeTh : dealFee, 
                  feeAsset = dealFeeTh ? feeAssetTh : feeAsset, 
            const filled = Number(amount) - Number(left);
            return {
                info: order,
                id,
                timestamp: Math.floor(time * 1000),
                datetime: new Date(time * 1000),
                lastTradeTimestamp: null,
                status: 'open',
                symbol: this.markets[market].symbol,
                type,
                side,
                price: Number(price),
                amount: Number(amount),
                filled,
                remaining: Number(left),
                cost: Number(price) * Number(filled),
                trades: [],
                fee: {
                    currency: feeAsset,
                    cost: fee
                }
            };
        });
        return orders;
    }

    async createOrder (market, type, side, amount, price, params = {}) {
        const request = this.extend({
            market: market.replace('/', ''),
            amount,
            price,
            side,
            type
        }, params);
        for (const param in request) {
            if (request[param] === null) {
                delete request[param];
            }
        }
        const response = await this.privatePutExchangeOrder(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return {
            info: result,
            id: result.id
        };
    }

    async cancelOrder (id, market) {
        const response = await this.privateDeleteExchangeOrderMarketId({ id, market: market.replace('/', '') });
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return {
            info: result,
            id: result.id
        };
    }

    async fetchUserLimits () { 
        const response = await this.privateGetUserLimits();
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return result;
    }

    async fetchUserType () { 
        const response = await this.privateGetUserType();
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        return result;
    }

    async fetchBalance () {
        const response = await this.privateGetUserBalance();
        const { error, refCode } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const result = response.result.default.balance;
        const balance = {
            info: result,
            free: {},
            used: {},
            total: {}
        };
        for (const asset in result) {
            const available = Number(result[asset].available);
            const freeze = Number(result[asset].freeze);
            const total = available + freeze;
            balance.free[asset] = available;
            balance.used[asset] = freeze;
            balance.total[asset] = total;
            balance[asset] = {
                free: available,
                used: freeze,
                total
            };
        }
        return balance;
    }

    async createDepositAddress (asset, params = {}) {
        const request = this.extend({
            asset: this.assets[asset] || asset
        }, params);
        const response = await this.privatePostWalletDepositAddress(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const { asset: currency, address } = result;
        return {
            currency,
            address,
            tag: null,
            info: result
        };
    }

    fetchDepositAddress (asset, params = {}) {
        return this.createDepositAddress(asset, params);
    }

    async withdraw (asset, amount, address, params = {}) {
        const request = this.extend({
            asset: this.assets[asset] || asset,
            amount,
            address
        }, params);
        const response = await this.privatePutWalletWithdrawal(request);
        const { refCode } = response;
        return {
            info: response,
            id: refCode
        };
    }

    async fetchDeposits (action, params = {}) {
        const request = this.extend({ action, type: 'deposit' }, params);
        const response = await this.privatePostWalletCcxtdata(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const deposits = result.map(deposit => {
            const { txid, created, type, amount, asset } = deposit;
            return {
                info: deposit,
                id: null,
                txid,
                timestamp: created * 1000,
                datetime: null,
                address: null,
                type,
                amount: Number(amount),
                currency: this.aliases[asset] || asset,
                status: 'ok'
            };
        });
        return deposits;
    }

    async fetchWithdrawals (action, params = {}) {
        const request = this.extend({ action, type: 'withdrawal' }, params);
        const response = await this.privatePostWalletCcxtdata(request);
        const { error, refCode, result } = response;
        if (error) {
            return {
                info: response,
                id: refCode
            };
        }
        const withdrawals = result.map(withdrawal => {
            const { txid, created, address, type, amount, asset } = withdrawal;
            return {
                info: withdrawal,
                id: null,
                txid,
                timestamp: created * 1000,
                datetime: null,
                address,
                type,
                amount: Number(amount),
                currency: this.aliases[asset] || asset,
                status: 'ok'
            };
        });
        return withdrawals;
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = {}, body) {
        let url = `${this.urls.api}/${path}`;
        if (/{.*}/.test(url)) {
            url = `${this.urls.api}/${this.url(path, params)}`;
        }
        if (['POST', 'PUT'].includes(method)) {
            if (Object.keys(params).length) {
                body = this.json(params);
                headers['Content-Type'] = 'application/json';
            }
        }
        if (this.token) {
            headers['Authorization'] = `Bearer ${this.token}`;
        }
        return { url, method, body, headers };
    }
};
