URL = Npm.require('url')
http = Npm.require('http')
https = Npm.require('https')
zlib = Npm.require('zlib')
querystring = Npm.require('querystring')

gunzipSync = Meteor.wrapAsync zlib.gunzip.bind(zlib)
inflateSync = Meteor.wrapAsync zlib.inflate.bind(zlib)

OEmbed = {}

getUrlContent = (urlObj, redirectCount = 5, callback) ->
	if _.isString(urlObj)
		urlObj = URL.parse urlObj

	opts =
		method: 'GET'
		port: urlObj.port
		hostname: urlObj.hostname
		path: urlObj.path
		rejectUnauthorized: !RocketChat.settings.get 'Allow_Invalid_SelfSigned_Certs'
		headers:
			'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'

	httpOrHttps = if urlObj.protocol is 'https:' then https else http

	parsedUrl = _.pick urlObj, ['host', 'hash', 'pathname', 'protocol', 'port', 'query', 'search']

	RocketChat.callbacks.run 'oembed:beforeGetUrlContent',
		requestOptions: opts
		parsedUrl: parsedUrl

	request = httpOrHttps.request opts, Meteor.bindEnvironment (response) ->
		if response.statusCode in [301,302,307] and response.headers.location?
			request.abort()
			console.log response.headers.location

			if redirectCount <= 0
				return callback null, {parsedUrl: parsedUrl}

			getUrlContent response.headers.location, --redirectCount, callback
			return

		if response.statusCode isnt 200
			return callback null, {parsedUrl: parsedUrl}

		chunks = []
		chunksTotalLength = 0
		response.on 'data', (chunk) ->
			chunks.push chunk
			chunksTotalLength += chunk.length
			if chunksTotalLength > 250000
				request.abort()

		response.on 'end', Meteor.bindEnvironment ->
			buffer = Buffer.concat(chunks)

			try
				if response.headers['content-encoding'] is 'gzip'
					buffer = gunzipSync buffer
				else if response.headers['content-encoding'] is 'deflate'
					buffer = inflateSync buffer

			callback null, {
				headers: response.headers
				body: buffer.toString()
				parsedUrl: parsedUrl
			}

		response.on 'error', (error) ->
			callback null, {
				error: error
				parsedUrl: parsedUrl
			}

	request.on 'error', (error) ->
		callback null, {
			error: error
			parsedUrl: parsedUrl
		}

	request.end()

OEmbed.getUrlMeta = (url, withFragment) ->
	getUrlContentSync = Meteor.wrapAsync getUrlContent

	urlObj = URL.parse url

	if withFragment?
		queryStringObj = querystring.parse urlObj.query
		queryStringObj._escaped_fragment_ = ''
		urlObj.query = querystring.stringify queryStringObj

		path = urlObj.pathname
		if urlObj.query?
			path += '?' + urlObj.query

		urlObj.path = path

	content = getUrlContentSync urlObj, 5

	metas = undefined

	if content?.body?
		metas = {}
		content.body.replace /<title>((.|\n)+)<\/title>/gmi, (meta, title) ->
			metas.pageTitle = title

		content.body.replace /<meta[^>]*(?:name|property)=[']([^']*)['][^>]*content=[']([^']*)['][^>]*>/gmi, (meta, name, value) ->
			metas[changeCase.camelCase(name)] = value

		content.body.replace /<meta[^>]*(?:name|property)=["]([^"]*)["][^>]*content=["]([^"]*)["][^>]*>/gmi, (meta, name, value) ->
			metas[changeCase.camelCase(name)] = value

		content.body.replace /<meta[^>]*content=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, (meta, value, name) ->
			metas[changeCase.camelCase(name)] = value

		content.body.replace /<meta[^>]*content=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, (meta, value, name) ->
			metas[changeCase.camelCase(name)] = value


		if metas.fragment is '!' and not withFragment?
			return OEmbed.getUrlMeta url, true

	headers = undefined

	if content?.headers?
		headers = {}
		for header, value of content.headers
			headers[changeCase.camelCase(header)] = value

	RocketChat.callbacks.run 'oembed:afterParseContent',
		meta: metas
		headers: headers
		parsedUrl: content.parsedUrl
		content: content

	return {
		meta: metas
		headers: headers
		parsedUrl: content.parsedUrl
	}

OEmbed.getUrlMetaWithCache = (url, withFragment) ->
	cache = RocketChat.models.OEmbedCache.findOneById url
	if cache?
		return cache.data

	data = OEmbed.getUrlMeta url, withFragment

	if data?
		RocketChat.models.OEmbedCache.createWithIdAndData url, data

		return data

	return

getRelevantHeaders = (headersObj) ->
	headers = {}
	for key, value of headersObj
		if key.toLowerCase() in ['contenttype', 'contentlength'] and value?.trim() isnt ''
			headers[key] = value

	if Object.keys(headers).length > 0
		return headers
	return

getRelevantMetaTags = (metaObj) ->
	tags = {}
	for key, value of metaObj
		if /^(og|fb|twitter|oembed).+|description|title|pageTitle$/.test(key.toLowerCase()) and value?.trim() isnt ''
			tags[key] = value

	if Object.keys(tags).length > 0
		return tags
	return

OEmbed.RocketUrlParser = (message) ->
	if Array.isArray message.urls
		changed = false
		for item in message.urls
			data = OEmbed.getUrlMetaWithCache item.url

			if data?
				if data.meta?
					item.meta = getRelevantMetaTags data.meta

				if data.headers?
					item.headers = getRelevantHeaders data.headers

				item.parsedUrl = data.parsedUrl
				changed = true

		if changed is true
			RocketChat.models.Messages.setUrlsById message._id, message.urls

	return message

if RocketChat.settings.get 'API_Embed'
	RocketChat.callbacks.add 'afterSaveMessage', OEmbed.RocketUrlParser, RocketChat.callbacks.priority.LOW