"use strict";

process.hrtime = require('browser-hrtime');

const React = require("react");
const ReactDOM = require("react-dom");
const debounce = require("debounce");
const util = require("util");
const { AggregrateValidationError } = require("@validatem/core");
const Editor = require("react-simple-code-editor").default;
const classnames = require("classnames");
const { highlight, languages } = require("prismjs");
require("prismjs/components/prism-sql.js");
require("prismjs/components/prism-plsql.js");

const operations = require("../../src/operations");
const optimizeAST = require("../../src/ast/optimize");
const astToQuery = require("../../src/ast-to-query");
const optimizers = require("../../src/optimizers");

function evaluateCode(code) {
	let {
		select, onlyColumns, addColumns, alias, 
		where, column, foreignColumn, table,
		value, parameter, 
		not, anyOf, allOf,
		lessThan, moreThan, equals,
		expression, unsafeSQL,
	} = operations;

	let query;

	try {
		eval(code);

		return {
			success: true,
			ast: query
		};
	} catch (error) {
		return {
			success: false,
			error: error
		};
	}
}

function processCode(code, activeOptimizers) {
	let result = evaluateCode(code);

	if (!result.success) {
		return result;
	} else {
		try {
			let { timings, ast } = optimizeAST(result.ast, optimizers.filter((optimizer) => activeOptimizers.has(optimizer.name)));
			let query = astToQuery(ast);

			return {
				success: true,
				query: query,
				ast: ast,
				timings: timings
			};
		} catch (error) {
			return {
				success: false,
				error: error
			};
		}
	}
}

let sampleCode = `
// Edit me!

/* Available functions:
select, onlyColumns, addColumns, alias, 
where, column, foreignColumn, table,
value, parameter, 
not, anyOf, allOf,
lessThan, moreThan, equals,
expression, unsafeSQL
*/

let niceNumbers = anyOf([ 1, 2, 3 ]);

query = select("projects", [
	where({
		number_one: niceNumbers,
		number_two: niceNumbers
	}),
	where({
		number_three: anyOf([ 42, column("number_one") ]),
		number_four: moreThan(1337)
	})
]);
`.trim();

function JSEditor({ onChanged }) {
	let [ code, setCode ] = React.useState(sampleCode);
	let [ maybeRunCode, setMaybeRunCode ] = React.useState();

	React.useEffect(() => {
		setMaybeRunCode(() => debounce((code) => {
			onChanged(code);
		}, 200));
	}, [ onChanged ]);

	return (
		<Editor
			value={code}
			onValueChange={(value) => {
				setCode(value);
				maybeRunCode(value);
			}}
			highlight={(code) => highlight(code, languages.js)}
			padding={10}
			insertSpaces={false}
			style={{
				// fontFamily: '"Fira code", "Fira Mono", monospace',
				// fontSize: 16,
				fontFamily: "monospace",
				fontSize: 16,
				height: "100%",
				tabSize: 4
			}}
		/>
	);
}

function HighlightedCode({ code, language }) {
	return <pre dangerouslySetInnerHTML={{ __html: highlight(code, languages[language]) }} />;
}

function HighlightedData({ data }) {
	return <HighlightedCode code={util.inspect(data, { depth: null })} language="js" />;
}

function App() {
	let [ result, setResult ] = React.useState(null);
	let [ code, setCode ] = React.useState(sampleCode);
	let [ lastGoodResult, setLastGoodResult ] = React.useState(null);
	let [ activeOptimizers, setActiveOptimizers ] = React.useState([ new Set(optimizers.map((optimizer) => optimizer.name)) ]);

	React.useEffect(() => {
		if (result != null && result.success === true) {
			setLastGoodResult(result);
		} else if (result != null && result.success === false) {
			console.error(result.error);
		}
	}, [ result ]);

	React.useEffect(() => {
		if (code != null) {
			setResult(processCode(code, activeOptimizers[0]));
		}
	}, [ code, activeOptimizers ]);

	let configurableOptimizers = new Set(optimizers
		.filter((optimizer) => !optimizer.category.includes("normalization"))
		.map((optimizer) => optimizer.name));

	return (<>
		<div className="layout">
			<div className={classnames("result", { failed: (result != null && !result.success) })}>
				{(lastGoodResult != null)
					? <>
						<HighlightedCode
							code={lastGoodResult.query.query}
							language="plsql"
						/>
						<HighlightedData data={lastGoodResult.query.params} />
					</>
					: "Result goes here..."
				}
			</div>
			<div className="editor">
				<div className="code">
					<JSEditor onChanged={(code) => {
						setCode(code);
					}} />
				</div>
				<div className={classnames("ast", { failed: (result != null && !result.success) })}>
					{(lastGoodResult != null)
						? <HighlightedData data={result.ast} />
						// ? <HighlightedData data={result} />
						: "Please wait..."
					}
				</div>
				<div className={classnames("options", { failed: (result != null && !result.success) })}>
					<h1>Options</h1>
					{optimizers.map((optimizer) => {
						let configurable = configurableOptimizers.has(optimizer.name);

						return <div className="optimizerOption" key={optimizer.name}>
							<input
								type="checkbox"
								name={optimizer.name}
								id={optimizer.name}
								checked={activeOptimizers[0].has(optimizer.name)}
								disabled={!configurable}
								onChange={() => {
									if (activeOptimizers[0].has(optimizer.name)) {
										activeOptimizers[0].delete(optimizer.name);
									} else {
										activeOptimizers[0].add(optimizer.name);
									}

									setActiveOptimizers([ activeOptimizers[0] ]);
								}}
							/>
							<label htmlFor={optimizer.name}>
								<span className="time">
									{(lastGoodResult != null && lastGoodResult.timings[optimizer.name] != null)
										? `${(lastGoodResult.timings[optimizer.name] / 1e6).toFixed()} ms`
										: "? ms"
									}
								</span>
								{optimizer.name}
								{(configurable)
									? null
									: " (required)"
								}
							</label>
						</div>;
					})}
				</div>
			</div>
		</div>
		{(result != null && result.success === false)
			? <div className="error">
				<strong>Oh no!</strong>
				<pre class="errorMessage">{result.error.message}</pre>
				{(result.error instanceof AggregrateValidationError)
					? null
					: <pre class="errorStack">{result.error.stack}</pre>
				}
			</div>
			: null
		}
	</>);
}

ReactDOM.render(<App />, document.querySelector("#app"));
