/**
 * @file Utility functions for creating frames
 */
'use strict'

/**
 * Creates a text frame
 * @param {string} data
 * @param {boolean} [masked=false] if the frame should be masked
 * @returns {Buffer}
 * @private
 */
exports.createTextFrame = function (data, masked) {
	var payload, meta

	payload = Buffer.from(data)
	meta = generateMetaData(true, 1, masked === undefined ? false : masked, payload)

	return Buffer.concat([meta, payload], meta.length + payload.length)
}

/**
 * Create a binary frame
 * @param {Buffer} data
 * @param {boolean} [masked=false] if the frame should be masked
 * @param {boolean} [first=true] if this is the first frame in a sequence
 * @param {boolean} [fin=true] if this is the final frame in a sequence
 * @returns {Buffer}
 * @private
 */
exports.createBinaryFrame = function (data, masked, first, fin) {
	var payload, meta

	first = first === undefined ? true : first
	masked = masked === undefined ? false : masked
	if (masked) {
		payload = Buffer.alloc(data.length)
		data.copy(payload)
	} else {
		payload = data
	}
	meta = generateMetaData(fin === undefined ? true : fin, first ? 2 : 0, masked, payload)

	return Buffer.concat([meta, payload], meta.length + payload.length)
}

/**
 * Create a close frame
 * @param {number} code
 * @param {string} [reason='']
 * @param {boolean} [masked=false] if the frame should be masked
 * @returns {Buffer}
 * @private
 */
exports.createCloseFrame = function (code, reason, masked) {
	var payload, meta

	if (code !== undefined && code !== 1005) {
		payload = Buffer.from(reason === undefined ? '--' : '--' + reason)
		payload.writeUInt16BE(code, 0)
	} else {
		payload = Buffer.alloc(0)
	}
	meta = generateMetaData(true, 8, masked === undefined ? false : masked, payload)

	return Buffer.concat([meta, payload], meta.length + payload.length)
}

/**
 * Create a ping frame
 * @param {string} data
 * @param {boolean} [masked=false] if the frame should be masked
 * @returns {Buffer}
 * @private
 */
exports.createPingFrame = function (data, masked) {
	var payload, meta

	payload = Buffer.from(data)
	meta = generateMetaData(true, 9, masked === undefined ? false : masked, payload)

	return Buffer.concat([meta, payload], meta.length + payload.length)
}

/**
 * Create a pong frame
 * @param {string} data
 * @param {boolean} [masked=false] if the frame should be masked
 * @returns {Buffer}
 * @private
 */
exports.createPongFrame = function (data, masked) {
	var payload, meta

	payload = Buffer.from(data)
	meta = generateMetaData(true, 10, masked === undefined ? false : masked, payload)

	return Buffer.concat([meta, payload], meta.length + payload.length)
}

/**
 * Creates the meta-data portion of the frame
 * If the frame is masked, the payload is altered accordingly
 * @param {boolean} fin
 * @param {number} opcode
 * @param {boolean} masked
 * @param {Buffer} payload
 * @returns {Buffer}
 * @private
 */
function generateMetaData(fin, opcode, masked, payload) {
	var len, meta, start, mask, i

	len = payload.length

	// Creates the buffer for meta-data
	meta = Buffer.alloc(2 + (len < 126 ? 0 : (len < 65536 ? 2 : 8)) + (masked ? 4 : 0))

	// Sets fin and opcode
	meta[0] = (fin ? 128 : 0) + opcode

	// Sets the mask and length
	meta[1] = masked ? 128 : 0
	start = 2
	if (len < 126) {
		meta[1] += len
	} else if (len < 65536) {
		meta[1] += 126
		meta.writeUInt16BE(len, 2)
		start += 2
	} else {
		// Warning: JS doesn't support integers greater than 2^53
		meta[1] += 127
		meta.writeUInt32BE(Math.floor(len / Math.pow(2, 32)), 2)
		meta.writeUInt32BE(len % Math.pow(2, 32), 6)
		start += 8
	}

	// Set the mask-key
	if (masked) {
		mask = Buffer.alloc(4)
		for (i = 0; i < 4; i++) {
			meta[start + i] = mask[i] = Math.floor(Math.random() * 256)
		}
		for (i = 0; i < payload.length; i++) {
			payload[i] ^= mask[i % 4]
		}
		start += 4
	}

	return meta
}