import { deferred } from './deferred';
import { addInfiniteLoopProtection } from './utils';
import { HtmlModes, CssModes, JsModes } from './codeModes';

const esprima = require('esprima-next');

// computeHtml, computeCss & computeJs evaluate the final code according
// to whatever mode is selected and resolve the returned promise with the code.
export function computeHtml(userCode, mode) {
	var code = userCode;
	var d = deferred();
	if (mode === HtmlModes.HTML) {
		d.resolve({
			code
		});
	} else if (mode === HtmlModes.MARKDOWN) {
		d.resolve(
			window.marked
				? {
						code: marked(code)
					}
				: {
						code
					}
		);
	} else if (mode === HtmlModes.JADE) {
		d.resolve(
			window.jade
				? {
						code: jade.render(code)
					}
				: {
						code
					}
		);
	}

	return d.promise;
}
export function computeCss(userCode, mode, settings) {
	var code = userCode;

	var d = deferred();
	var errors;

	if (mode === CssModes.CSS) {
		d.resolve({
			code
		});
	} else if (mode === CssModes.SCSS || mode === CssModes.SASS) {
		if (window.sass && code) {
			window.sass.compile(
				code,
				{
					indentedSyntax: mode === CssModes.SASS
				},
				function (result) {
					// Something was wrong
					if (result.line && result.message) {
						errors = {
							lang: 'css',
							data: [
								{
									lineNumber: result.line - 1,
									message: result.message
								}
							]
						};
					}
					d.resolve({
						code: result.text,
						errors
					});
				}
			);
		} else {
			d.resolve({
				code
			});
		}
	} else if (mode === CssModes.LESS) {
		less.render(code).then(
			function (result) {
				d.resolve({
					code: result.css
				});
			},
			function (error) {
				errors = {
					lang: 'css',
					data: [
						{
							lineNumber: error.line,
							message: error.message
						}
					]
				};
				d.resolve({
					code: '',
					errors
				});
			}
		);
	} else if (mode === CssModes.STYLUS) {
		stylus(code).render(function (error, result) {
			if (error) {
				window.err = error;
				// Last line of message is the actual message
				var tempArr = error.message.split('\n');
				tempArr.pop(); // This is empty string in the end
				errors = {
					lang: 'css',
					data: [
						{
							lineNumber: +error.message.match(/stylus:(\d+):/)[1] - 298,
							message: tempArr.pop()
						}
					]
				};
			}
			d.resolve({
				code: result,
				errors
			});
		});
	} else if (mode === CssModes.ACSS) {
		if (!window.atomizer) {
			d.resolve({
				code: ''
			});
		} else {
			const html = code;
			const foundClasses = atomizer.findClassNames(html);
			var finalConfig;

			// Regular expression to find unquoted keys: looks for word characters (including _) followed by a colon
			// and put them in quotes
			const fixedConfigJson = (settings.acssConfig || '{}').replace(
				/([{,]\s*)([^\s"{:]+)(\s*:\s*)/g,
				'$1"$2"$3'
			);

			try {
				finalConfig = atomizer.getConfig(
					foundClasses,
					JSON.parse(fixedConfigJson)
				);
			} catch (e) {
				finalConfig = atomizer.getConfig(foundClasses, {});
			}
			const acss = atomizer.getCss(finalConfig);
			d.resolve({
				code: acss
			});
		}
	}

	return d.promise;
}

/* eslint-disable max-params */
/* eslint-disable complexity */
export function computeJs(
	userCode,
	mode,
	shouldPreventInfiniteLoops,
	infiniteLoopTimeout
) {
	var code = userCode;
	var d = deferred();
	var errors;

	if (!code) {
		d.resolve('');
		return d.promise;
	}

	if (mode === JsModes.JS) {
		try {
			esprima.parse(code, {
				tolerant: true
			});
		} catch (e) {
			errors = {
				lang: 'js',
				data: [
					{
						lineNumber: e.lineNumber - 1,
						message: e.description
					}
				]
			};
		} finally {
			if (shouldPreventInfiniteLoops !== false) {
				// If errors are found in last parse, we don't run infinite loop
				// protection otherwise it will again throw error.
				code = errors
					? code
					: addInfiniteLoopProtection(code, {
							timeout: infiniteLoopTimeout
						});
			}

			d.resolve({
				code,
				errors
			});
		}
	} else if (mode === JsModes.COFFEESCRIPT) {
		if (!window.CoffeeScript) {
			d.resolve('');
			return d.promise;
		}
		try {
			code = CoffeeScript.compile(code, {
				bare: true
			});
		} catch (e) {
			errors = {
				lang: 'js',
				data: [
					{
						lineNumber: e.location.first_line,
						message: e.message
					}
				]
			};
		} finally {
			if (shouldPreventInfiniteLoops !== false) {
				code = errors
					? code
					: addInfiniteLoopProtection(code, {
							timeout: infiniteLoopTimeout
						});
			}
			d.resolve({
				code,
				errors
			});
		}
	} else if (mode === JsModes.ES6) {
		if (!window.Babel) {
			d.resolve('');
			return d.promise;
		}
		try {
			esprima.parse(code, {
				tolerant: true,
				jsx: true
			});
		} catch (e) {
			errors = {
				lang: 'js',
				data: [
					{
						lineNumber: e.lineNumber - 1,
						message: e.description
					}
				]
			};
		} finally {
			code = Babel.transform(code, {
				presets: ['latest', 'stage-2', 'react']
			}).code;
			if (shouldPreventInfiniteLoops !== false) {
				code = errors
					? code
					: addInfiniteLoopProtection(code, {
							timeout: infiniteLoopTimeout
						});
			}
			d.resolve({
				code,
				errors
			});
		}
	} else if (mode === JsModes.TS) {
		try {
			if (!window.ts) {
				d.resolve({
					code: ''
				});
				return d.promise;
			}
			code = ts.transpileModule(code, {
				reportDiagnostics: true,
				compilerOptions: {
					noEmitOnError: true,
					diagnostics: true,
					module: ts.ModuleKind.ES2015
				}
			});
			if (code.diagnostics.length) {
				/* eslint-disable no-throw-literal */
				errors = {
					lang: 'js',
					data: [
						{
							message: code.diagnostics[0].messageText,
							lineNumber:
								ts.getLineOfLocalPosition(
									code.diagnostics[0].file,
									code.diagnostics[0].start
								) - 1
						}
					]
				};
			}
			code = code.outputText;
			if (shouldPreventInfiniteLoops !== false && !errors) {
				code = addInfiniteLoopProtection(code, {
					timeout: infiniteLoopTimeout
				});
			}
			d.resolve({
				code,
				errors
			});
		} catch (e) {}
	}

	return d.promise;
}
/* eslint-enable max-params */
/* eslint-enable complexity */
