"use strict"

class Poloniex extends Exchange {
	static meta = {
		aliases: [
			"POLONIEX",
		],
		endpoint: "https://poloniex.com",
		fields: {
			public: {
				label: "API Key",
			},
			private: {
				label: "Secret",
			},
		},
		name: "Poloniex",
		permissions: {
			origins: [
				"https://poloniex.com/tradingApi"
			],
		},
		subscriptions: {
			active: [
				"e3fdd77de3a682bfcfd8dc3000a25a5b",
				"4db6261b067dabf8ffb0924c18d95d06",
			],
			inactive: [
				"polon1",
				"polon2",
			],
		},
		website: "https://poloniex.com/",
		desc: "Based in the United States, Poloniex is a leading digital asset exchange offering a wide variety of digital assets.",
		margin: true,
		spot: true,
		listPos: false,
		testSymbol: "BTCUSDT"
	}
	constructor(meta = Poloniex.meta) {
		super(meta)
	}

	async req(method, resource, params = {}) {
		const headers = {}
		if (method !== 'GET') {
			const credentials = this.getCredentials("private")
			params.nonce = this.getNonce()
			params = serialize(params)
			headers["Key"] = credentials.public
			headers["Sign"] = CryptoJS.HmacSHA512(params, credentials.private).toString(CryptoJS.enc.Hex)
		} else this.hasAccess()

		return await request.call(this, method, this.meta.endpoint + resource, params, headers)
	}

	async account(currency, isMargin) {
		let balances = {}

		if (!isMargin) {
			const data = await this.req('POST', "/tradingApi", {command: "returnCompleteBalances"})
			for (const symbol in data) {
				const entry = data[symbol]

				entry.balance = Number(entry.available) + Number(entry.onOrders)
				if (entry.balance || symbol === currency) {
					balances[symbol] = entry
				}
			}
		} else {
			const data = await this.req('POST', "/tradingApi", {command: "returnTradableBalances"})
			for (const symbol in data) {
				const main = symbol.split("_")[1]
				const balance = Number(data[symbol][main])

				if (balance || main === currency) {
					balances[main] = {
						available: balance,
						balance: balance
					}
				}
			}
		}
		return balances
	}

	async symbolInfoImpl(cache) {
		return await this.req('GET', "/public", {command: "return24hVolume"})
	}

	symbolPair(symbol) {
		symbol = symbol.toUpperCase()
		let main = null
		let pair = null

		let reg = /^(BTC|ETH|XMR|USDT|USDC)[-_/]?(.*?)$/i
		let result = reg.exec(symbol)
		if (result) {
			main = result[1]
			pair = result[2]
		}
		reg = /^(.*?)[-_/]?(BTC|ETH|XMR|USDT|USDC)$/i
		result = reg.exec(symbol)
		if (result) {
			main = result[2]
			pair = result[1]
		}
		if (!main || !pair) {
			throw new Error(`Unknown market symbol: ${symbol} – Use the 'Symbols' button in the exchange settings or alert editor to browse for valid symbols!`)
		}

		return {
			main: main,
			pair: pair,
			symbol: main + '_' + pair
		}
	}

	async symbolTickerImpl(symbol) {
		let ticker = null
		try {
			const tickers = await this.req('GET', "/public", {command: "returnTicker"})
			ticker = tickers[symbol]
		} catch(ex) {}

		return ticker && {
			ask: ticker.lowestAsk,
			bid: ticker.highestBid,
			mid: (ticker.lowestAsk + ticker.highestBid) / 2,
			last: ticker.last,
			high: ticker.high24hr,
			low: ticker.low24hr
		}
	}

	async trade(cmd, ohlc = {}) {
		const pair = this.symbolPair(cmd.s)
		cmd._sym = pair.symbol
		const currency = cmd.currency = cmd.isMargin ? pair.pair : cmd.isBid ? pair.main : pair.pair
		const ticker = (cmd.pc && !cmd.up && this.getPrice(pair.symbol)) || (await this.symbolTicker(cmd.up, pair.symbol))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${pair.symbol} is not available!`)
		}
		this.checkSlip(cmd, ohlc, pair.symbol, ticker)
		if (cmd.up) {
			this.updatePrice(pair.symbol, ticker)
			if (!cmd.b && !cmd.ub) {
				return ticker
			}
		}

		let balance = {available: 0, balance: 0}
		if ((cmd.y !== "possize" && cmd.q.wasPercent()) || cmd.ub) {
			balance = cmd.bc && !cmd.ub && this.getBalance(currency, cmd.isMargin)
			const balances = !balance && (await this.account(currency, cmd.isMargin))
			balance = balance || balances[currency]
			this.checkBalance(cmd, balance, currency, cmd.isMargin ? 'Margin' : 'Spot')
			if (!cmd.bc || cmd.ub) {
				this.updateBalance(balances, currency, cmd.isMargin)
				if (cmd.ub) {
					return Object.assign(ticker, balance)
				}
			}
		}

		const first = ticker[cmd.pr] || this.ohlc(cmd, ohlc, 'pr') || ((cmd.isBid && cmd.t !== "market") || (cmd.isAsk && cmd.t === "market") ? ticker.bid : ticker.ask)
		let price = cmd.fp ? cmd.fp.resolve(8) : cmd.p._().relative(first).minmax(cmd.minp, cmd.maxp).resolve(8)
		if (price <= 0) {
			this.warn("your p parameter might be incorrect! (price of 0 calculated)")
		}

		let available = ohlc[cmd.y] || cmd.y === "equity" ? balance.balance : balance.available
		if (cmd.y !== "possize" && !cmd.isMargin && cmd.isBid) {
			available /= price
		}

		let order = {
			currencyPair: pair.symbol,
			command: cmd.isMargin ? (cmd.isBid ? "marginBuy" : "marginSell") : (cmd.isBid ? "buy" : "sell"),
			amount: cmd.q._().div(cmd.u === "currency" && !cmd.q.wasPercent() ? price : 1)
				.reference(available).mul(this.ohlc(cmd, ohlc, 'qr') || 1).add(this.addpos(cmd, ohlc)).minmax(cmd.minq, cmd.maxq).resolve(8),
			rate: price
		}
		if (cmd.lq && ohlc.left) {
			this.info("using leftover quantity from last order: "+ohlc.left)
			order.amount = ohlc.left.toFixed(8)
		}
		if (cmd.t === "fok") {
			order.fillOrKill = 1
		} else if (cmd.t === "ioc") {
			order.immediateOrCancel = 1
		} else if (cmd.t === "post") {
			order.postOnly = 1
		}
		if (order.amount <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated)")
		}

		this.info("placing order: "+stringify(order))
		if (cmd.d) {
			this.msgDisabled(cmd)
			return order
		}
		const result = await this.req('POST', "/tradingApi", order)
		cmd.id && result?.orderNumber && this.cacheId(result.orderNumber, cmd.id)
		return result
	}

	async ordersCancel(cmd, ohlc = {}) {
		const pair = this.symbolPair(cmd.s)
		cmd._sym = pair.symbol
		let orders = await this.req('POST', "/tradingApi", {command: "returnOpenOrders", currencyPair: pair.symbol})
		if (!orders || !orders.filter) {
			this.msgOrderErr()
			return false
		} else if (!orders.length) {
			this.msgOrdersNone()
			return false
		}

		const ids = (cmd.id || "").split(',')
		const match = cmd.cr ? false : true
		const tickers = {}
		await this.filterOrderStart(cmd, ohlc, tickers, orders)
		const totalOrders = orders.length
		orders = orders.filter(order => {
			if (cmd.b && cmd.b !== order.type) {
				return false
			}
			if (cmd.fp && cmd.fp.compare(order.rate)) {
				return !match
			}
			if (cmd.id && !this.checkId(order.orderNumber, ids)) {
				return !match
			}
			if (!this.filterOrder(cmd, ohlc, tickers, order)) {
				return !match
			}
			return match
		})

		if (cmd.cm._().reference(orders.length).getMax() < orders.length) {
			const sort = {
				"newest": 	["orderNumber", true],
				"oldest": 	["orderNumber", false],
				"highest": 	["rate", true],
				"lowest": 	["rate", false],
				"biggest": 	["amount", true],
				"smallest": ["amount", false]
			}
			cmd.cmo === 'random' ? shuffle(orders) : sortByIndex(orders, sort[cmd.cmo][0], sort[cmd.cmo][1])
			orders = orders.slice(0, cmd.cm.resolve(0))
		}
		if(!orders.length) {
			this.msgOrders(cmd, orders, totalOrders)
			return []
		}

		this.msgOrders(cmd, orders, totalOrders)
		if (cmd.ch || cmd.d) {
			this.msgDisabled(cmd)
			return orders
		}

		let cancelOrders = []
		for (const order of orders) {
			const result = await this.req('POST', "/tradingApi", {command: "cancelOrder", orderNumber: order.orderNumber})
			if (result?.success === 1) {
				cancelOrders.push(order)
				this.removeId(order.orderNumber)
			}
		}
		return cancelOrders
	}

	async positionsClose(cmd, pos, ohlc = {}) {
		const pair = this.symbolPair(cmd.s)
		cmd._sym = pair.symbol
		const ticker = (cmd.pc && !cmd.up && this.getPrice(pair.symbol)) || (await this.symbolTicker(cmd.up, pair.symbol))
		if (!ticker) {
			throw new ReferenceError(`Ticker ${pair.symbol} is not available!`)
		}
		ticker.pos = pos.basePrice
		this.checkSlip(cmd, ohlc, pair.symbol, ticker)
		cmd.up && this.updatePrice(pair.symbol, ticker)

		const first = ticker[cmd.sl || cmd.tp || cmd.ts ? 'pos' : cmd.pr] || this.ohlc(cmd, ohlc, 'pr') || ((cmd.isBid && cmd.t !== "market") || (cmd.isAsk && cmd.t === "market") ? ticker.bid : ticker.ask)
		let price = (cmd.fp || cmd.p._().relative(first).minmax(cmd.minp, cmd.maxp)).resolve(8)
		if (price <= 0) {
			this.warn("your p parameter might be incorrect! (price of 0 calculated)")
		}

		let order = {
			currencyPair: pair.symbol,
			command: pos.type === "long" ? "marginSell" : "marginBuy",
			amount: cmd.q._().div(cmd.u === "currency" && !cmd.q.wasPercent() ? price : 1)
				.reference(pos.amount).mul(this.ohlc(cmd, ohlc, 'qr') || 1).add(this.addpos(cmd, ohlc, pos)).minmax(cmd.minq, cmd.maxq).resolve(8),
			rate: price
		}
		if (cmd.t === "fok") {
			order.fillOrKill = 1
		} else if (cmd.t === "ioc") {
			order.immediateOrCancel = 1
		} else if (cmd.t === "post") {
			order.postOnly = 1
		}
		if (order.amount <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated)")
		}

		!cmd.ch && this.info("placing Close Position order: "+stringify(order))
		if (cmd.ch || cmd.d) {
			this.msgDisabled(cmd)
			return order
		}
		const result = await this.req('POST', "/tradingApi", order)
		cmd.id && result?.orderNumber && this.cacheId(result.orderNumber, cmd.id)
		return result
	}

	async positionsCloseAll(cmd, ohlc, positions) {
		if (!cmd.s) return false
		const pair = this.symbolPair(cmd.s)
		cmd._sym = pair.symbol
		cmd.currency = cmd.isMargin ? pair.pair : cmd.isBid ? pair.main : pair.pair

		if (!positions) {
			const response = await this.req('POST', "/tradingApi", {command: "getMarginPosition", currencyPair: "all"})
			if (!response) {
				this.msgPosErr()
				return false
			}
			positions = []
			for (const currency in response) {
				const pos = response[currency]
				if (pos.type !== "none" && currency === pair.symbol) {
					pos.symbol = currency
					positions.push(pos)
				}
			}
			if (!positions.length) {
				this.msgPosNone()
				return false
			}
		}

		const match = cmd.cr ? false : true
		const tickers = {}
		await this.filterPosStart(cmd, ohlc, tickers, positions)
		const totalPos = positions.length
		positions = positions.filter(pos => {
			if (cmd.b && pos.type !== cmd.b) {
				return false
			}
			pos.amount = Math.abs(pos.amount)
			pos.pnl = pos.pl / pos.total
			if (!this.filterPos(cmd, ohlc, tickers, pos, undefined, 8, true)) {
				return !match
			}
			return match
		})

		if (cmd.cm._().reference(positions.length).getMax() < positions.length) {
			if (["newest", "oldest"].includes(cmd.cmo)) {
				this.warn(`Close Maximum Order option "${cmd.cmo}" is currently not supported!`)
			} else {
				const sort = {
					"highest": 	["basePrice", true],
					"lowest": 	["basePrice", false],
					"biggest": 	["amount", true],
					"smallest": ["amount", false]
				}
				cmd.cmo === 'random' ? shuffle(positions) : sortByIndex(positions, sort[cmd.cmo][0], sort[cmd.cmo][1])
				positions = positions.slice(0, cmd.cm.resolve(0))
			}
		}
		if(!positions.length) {
			this.msgPos(cmd, positions, totalPos)
			return []
		} else if (cmd.ch) {
			this.msgPos(cmd, positions, totalPos)
			return positions
		}

		let cancelOrders = []
		for (const pos of positions) {
			const order = await this.positionsClose(cmd, pos, ohlc)
			if (order) {
				closeOrders.push(order)
				Object.assign(closeOrders[closeOrders.length-1], pos)
			}
		}
		return cancelOrders
	}
}

Broker.add(new Poloniex())
