"use strict"

class OKCoin extends Exchange {
	static meta = {
		aliases: [
			"OKCOIN-USD",
			"OKCOIN",
		],
		endpoint: "https://www.okcoin.com/api/v1/",
		fields: {
			public: {
				label: "API Key",
			},
			private: {
				label: "Secret Key",
			},
		},
		name: "OKCoin",
		patterns: [],
		permissions: {
			origins: [
				"https://www.okcoin.com/api/*",
			],
		},
		subscriptions: {
			active: [
				"e3fdd77de3a682bfcfd8dc3000a25a5b",
				"4db6261b067dabf8ffb0924c18d95d06",
			],
			inactive: [
				"okcoi1",
				"okcoi2",
			],
		},
		website: "https://www.okcoin.com/?invid=2016110",
		desc: "OKCoin is the leading global bitcoin exchange. Secured with bank-level SSL encryption and cold storage. Distributed servers for high-speed bitcoin trading based on real-time data.",
		testSymbol: "BTCUSD"
	}
	constructor() {
		super(OKCoin.meta)
	}

	*req(method, resource, params = {}) {
		if (method !== 'GET') {
			const credentials = this.getCredentials("private")
			params.api_key = credentials.public
			const signature = serialize(params, null, true).replace(/%2C/g, ',') + "&secret_key=" + credentials.private
			params.sign = md5(signature).toUpperCase()
		} else this.hasAccess()

		const response = yield request.bind(this, method, this.meta.endpoint + resource, params, null)
		if (!response.result) {
			if (response.error_code == 20022) {
				return {}
			}
			if (!window.OKCOIN_ERRORS) {
				window.OKCOIN_ERRORS = yield request.bind(this, 'GET', chrome.runtime.getURL("/cache/okcoin/api_errors.json"), null, null)
			}
			throw new Error((window.OKCOIN_ERRORS[response.error_code] || "Unknown connection error with API endpoint!") + ` (Error Code ${response.error_code})`)
		}
		return response
	}

	normalize(amount, ret) {
		function add(amount, balance) {
			return (Number(amount) || 0) + (Number(balance) || 0)
		}

		if (typeof ret === "object") {
			ret.available = add(amount, ret.available)
			ret.total = add(amount, ret.total)
		} else {
			ret = {
				available: add(amount),
				total: add(amount)
			}
		}
		return ret
	}

	*account() {
		const response = yield* this.req('POST', "/userinfo.do")
		const account = response.info.funds
		let balances = {}

		for (const code in account.free) {
			balances[code] = this.normalize(account.free[code])
		}
		if (account.borrow) {
			for (const code in account.borrow) {
				balances[code] = this.normalize(account.borrow[code], balances[code])
			}
		}
		return balances
	}

	symbolPair(symbol) {
		const regexp = /^([a-z]{3})[-_/]?([a-z]{3})$/i
		const result = regexp.exec(symbol.toLowerCase())

		if (!result) {
			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: result[2] || "",
			pair: result[1] || "",
			symbol: (result[1] || "") + "_" + (result[2] || "")
		}
	}

	*symbolTicker(symbol) {
		let ticker = null
		try {
			const response = yield* this.req('GET', "/ticker.do", {symbol: symbol})
			ticker = response.ticker
		} catch (ex) {}

		return ticker && {
			ask: Number(ticker.sell),
			bid: Number(ticker.buy),
			mid: (Number(ticker.sell) + Number(ticker.buy)) / 2,
			last: Number(ticker.last)
		}
	}

	*trade(cmd, ohlc = {}) {
		if (cmd.isMargin) {
			throw new Error(this.getName() + " does not support Margin trading!")
		}

		const pair = this.symbolPair(cmd.s)
		cmd._sym = pair.symbol
		const ticker = (cmd.pc && !cmd.up && this.getPrice(pair.symbol)) || (yield* this.symbolTicker(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
			}
		}

		const currency = cmd.isBid ? pair.main : pair.pair
		let balance = {available: 0, balance: 0}
		if (cmd.q.wasPercent() || cmd.ub) {
			balance = cmd.bc && !cmd.ub && this.getBalance(currency)
			const balances = !balance && (yield* this.account(currency))
			balance = balance || balances[currency]
			this.checkBalance(cmd, balance, currency)
			if (!cmd.bc || cmd.ub) {
				this.updateBalance(balances, currency)
				if (cmd.ub) {
					return Object.assign(ticker, balance)
				}
			}
		}

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

		let available = cmd.y === "equity" ? balance.total : balance.available
		if (cmd.isBid) {
			available /= price
		}

		ohlc[cmd.qr] && this.info(`using custom quantity multiplier: ${ohlc[cmd.qr]} (${cmd.qr})`)
		let order = {
			symbol: pair.symbol,
			type: cmd.isBid ? "buy" : "sell",
			amount: cmd.q._().div(cmd.u === "currency" && !cmd.q.wasPercent() ? price : 1)
				.reference(available).mul(ohlc[cmd.qr] || 1).minmax(cmd.minq, cmd.maxq).resolve(4)
		}
		if (cmd.lq && ohlc.left) {
			this.info("using leftover quantity from last order: "+ohlc.left)
			order.amount = ohlc.left.toFixed(4)
		}
		if (order.amount <= 0) {
			this.warn("your q parameter might be incorrect! (quantity of 0 calculated)")
		}
		if (cmd.t === "market") {
			order.type += "_market"
		} else {
			order.price = price
		}

		this.info("placing order: "+stringify(order))
		if (cmd.d) {
			this.msgDisabled(cmd)
			return order
		}

		const result = yield* this.req('POST', "/trade.do", order)
		cmd.id && result && result.order_id && this.cacheId(result.order_id, cmd.id)
		return result
	}

	*ordersCancel(cmd, ohlc = {}) {
		const pair = this.symbolPair(cmd.s)
		cmd._sym = pair.symbol
		const response = yield* this.req('POST', "/order_info.do", {order_id: -1, symbol: pair.symbol})
		let orders = response && response.orders
		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 = {}
		yield* 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.price_avg)) {
				return !match
			}
			if (cmd.id && !this.checkId(order.order_id, 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": 	["create_date", true],
				"oldest": 	["create_date", false],
				"highest": 	["price_avg", true],
				"lowest": 	["price_avg", 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))
		}

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

		let cancelOrders = []
		for (const order of orders) {
			const response = yield* this.req('POST', "/cancel_order.do", {order_id: order.order_id, symbol: order.symbol})
			if (response && response.result) {
				cancelOrders.push(order)
				this.removeId(order.order_id)
			}
		}
		return cancelOrders
	}

	*positionsCloseAll() {
		throw new ReferenceError(this.getName() + " does not support Margin trading!")
	}
}

Broker.add(new OKCoin())
