import { clearScreen, fillRect } from "./window.ts";
import { fontWidth, fontHeight } from "./font.ts";
import { drawText } from "./builtins.ts";
import { COLOR } from "./colors.ts";
import {getSheet, setSheet} from "./sheet.ts";
import { K, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts";
import { tokenize } from "./deps.ts";

const state = {
	scrollX: 0,
	scrollY: 0,
	anchor: 0,
	focus: 0,
	get focusX() {return indexToGrid(this.code, this.focus).x;},
	get focusY() {return indexToGrid(this.code, this.focus).y;},
	get anchorX() {return indexToGrid(this.code, this.anchor).x;},
	get anchorY() {return indexToGrid(this.code, this.anchor).y;},
	isCollapsed() {
		return this.anchor === this.focus;
	},
	clampInRange(n: number) {
		return Math.max(0, Math.min(n, this.code.length))
	},
	setSelection(anchor: number | {x: number, y: number}, focus?: number | {x: number, y: number}) {
		if (typeof anchor !== "number") {
			anchor = gridToIndex(this.code, anchor.x, anchor.y);
		}
		focus = focus ?? anchor;
		if (typeof focus !== "number") {
			focus = gridToIndex(this.code, focus.x, focus.y);
		}
		this.anchor = this.clampInRange(anchor),
		this.focus = this.clampInRange(focus);
	},
	setFocus(focus: number | {x: number, y: number}) {
		if (typeof focus !== "number") {
			focus = gridToIndex(this.code, focus.x, focus.y);
		}
		this.focus = this.clampInRange(focus);
	},
	insertText(text: string) {
		const {code, anchor, focus} = this;
		this.code = code.slice(0, Math.min(anchor, focus)) + text + code.slice(Math.max(anchor, focus));
		this.setSelection(Math.min(anchor, focus) + text.length);
	},
	indent(indentString: string) {
		const lines = this.code.split("\n");
		const {focusX, focusY, anchorX, anchorY} = this;
		const newLines = lines.map((line, i) => {
			if (i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY)) {
				return indentString+line;
			} else {
				return line;
			}
		});
		this.code = newLines.join("\n");
		this.setSelection({x: anchorX+1, y: anchorY}, {x: focusX+1, y: focusY});
	},
	outdent(outdentRegex: RegExp) {
		const lines = this.code.split("\n");
		const {focusX, focusY, anchorX, anchorY} = this;
		const newLines = lines.map((line, i) => {
			const match = line.match(outdentRegex);
			if (i >= Math.min(focusY, anchorY) && i <= Math.max(focusY, anchorY) && match) {
				return line.slice(match[0].length);
			} else {
				return line;
			}
		});
		this.code = newLines.join("\n");
		this.setSelection({x: Math.max(0,anchorX-1), y: anchorY}, {x: Math.max(0,focusX-1), y: focusY});
	},
	backspace() {
		const {code, focus} = this;
		if (this.isCollapsed()) {
			if (focus > 0) {
				this.code = code.slice(0, focus-1) + code.slice(focus);
				this.setSelection(focus-1);
			}
		} else {
			this.insertText("");
		}
	},
	delete() {
		const {code, focus} = this;
		if (this.isCollapsed()) {
			if (focus < code.length) {
				this.code = code.slice(0, focus) + code.slice(1+focus);
			}
		} else {
			this.insertText("");
		}
	},
	scrollToCursor() {
		const {focusY, focusX, scrollY, scrollX} = this;
		const fh = fontHeight + 1;
		const fw = fontWidth;
		if (focusY*fh < scrollY) {
			this.scrollY = focusY*fh;
		}
		if (focusY*fh > scrollY+112-fh) {
			this.scrollY = focusY*fh-112+fh;
		}
		if (focusX*fw < scrollX) {
			this.scrollX = focusX*fw;
		}
		if (focusX*fw > scrollX+128-fw) {
			this.scrollX = focusX*fw-128+fw;
		}
	},
	get code() {
		const {sheet_type, value} = getSheet(0);
		if (sheet_type !== "code") {
			throw "Trying to run a non-code sheet as code."
		}
		return value;
	},
	set code(val) {
		setSheet(0, "code", val);
	}
}

const indexToGrid = (str: string, index: number) => {
	const linesUpTo = str.slice(0,index).split("\n");
	return {
		x: linesUpTo[linesUpTo.length-1].length,
		y: linesUpTo.length - 1,
	}
}

const gridToIndex = (str: string, x: number, y: number) => {
	const lines = str.split("\n");
	if (y < 0) {
		return 0;
	}
	if (y >= lines.length) {
		return str.length;
	}
	return lines.slice(0, y).join("\n").length+Math.min(x, lines[y].length)+1;
}

const keywords = [
    "break",
    "case",
    "catch",
    "class",
    "const",
    "continue",
    "debugger",
    "default",
    "delete",
    "do",
    "else",
    "export",
    "extends",
    "finally",
    "for",
    "function",
    "if",
    "import",
    "in",
    "instanceof",
    "new",
    "return",
    "super",
    "switch",
    "this",
    "throw",
    "try",
    "typeof",
    "var",
    "void",
    "while",
    "with",
	"let",
	"static",
	"yield",
	"await",
	"enum",
    "implements",
    "interface",
    "package",
    "private",
    "protected",
    "public",
	"=>",
];
const values = [
    "false",
    "null",
    "true",
	"undefined",
];
const operator = [
	"&&",
	"||",
	"??",
	"--",
	"++",
	".",
	"?.",
	"<",
	"<=",
	">",
	">=",
	"!=",
	"!==",
	"==",
	"===",
	"+",
	"-",
	"%",
	"&",
	"|",
	"^",
	"/",
	"*",
	"**",
	"<<",
	">>",
	">>>",
	"=",
	"+=",
	"-=",
	"%=",
	"&=",
	"|=",
	"^=",
	"/=",
	"*=",
	"**=",
	"<<=",
	">>=",
	">>>=",
	"!",
	"?",
	"~",
	"...",
];
const punctuation = [
	"(",
	")",
	"[",
	"]",
	"{",
	"}",
	".",
	":",
	";",
	",",
];

const keywordColor = COLOR.PURPLE;
const operatorColor = COLOR.CYAN;
const valueColor = COLOR.ORANGE;
const stringColor = COLOR.GREEN;
const regexColor = stringColor;
const punctuationColor = COLOR.WHITE;
const commentColor = COLOR.GRAY;
const identifierColor = COLOR.LIGHTGRAY;
const invalidColor = COLOR.RED;

const tokenColors = {
	"StringLiteral": stringColor,
	"NoSubstitutionTemplate": stringColor,
	"TemplateHead": stringColor,
	"TemplateMiddle": stringColor,
	"TemplateTail": stringColor,
	"RegularExpressionLiteral": regexColor,
	"MultiLineComment": commentColor,
	"SingleLineComment": commentColor,
	"IdentifierName": identifierColor,
	"PrivateIdentifier": identifierColor,
	"NumericLiteral": valueColor,
	"Punctuator": punctuationColor,
	"WhiteSpace": punctuationColor,
	"LineTerminatorSequence": punctuationColor,
	"Invalid": invalidColor,
}

const drawCodeField = (code: string, x: number, y: number, w: number, h: number) => {
	const {
		scrollX,
		scrollY,
		anchor,
		focus,
	} = state;
	const {
		x: focusX,
		y: focusY,
	} = indexToGrid(code, focus);
	const {
		x: anchorX,
		y: anchorY,
	} = indexToGrid(code, anchor);
	fillRect(x, y, w, h, COLOR.DARKBLUE);
	if (anchor === focus) {
		fillRect(x+focusX*fontWidth-scrollX, y+focusY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.RED);
	} else {
		// TODO: Draw this selection better
		fillRect(x+anchorX*fontWidth-scrollX, y+anchorY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.GREEN);
		fillRect(x+focusX*fontWidth-scrollX, y+focusY*(fontHeight+1)-scrollY, fontWidth+1, fontHeight+1, COLOR.YELLOW);
	}
	// TODO: syntax highlight built-in functions
	const tokens = [...tokenize(code)];
	let cx = 0;
	let cy = 0;
	tokens.forEach((token) => {
		const lines = token.value.split("\n");
		lines.forEach((line, i) => {
			let color = tokenColors[token.type];
			if (keywords.includes(token.value)) {
				color = keywordColor;
			}
			if (values.includes(token.value)) {
				color = valueColor;
			}
			if (operator.includes(token.value)) {
				color = operatorColor;
			}
			if (punctuation.includes(token.value)) {
				color = punctuationColor;
			}
			drawText(x+cx-scrollX, 1+y+cy-scrollY, line, color);
			if (i === lines.length-1) {
				cx += fontWidth*line.length;
			} else {
				cx=0;
				cy+=fontHeight+1;
			}
		});
	})
}

const update = () => {
	const { focus, focusX, focusY} = state;
	const keyboardString = getKeyboardString();
	if (keyboardString) {
		state.insertText(keyboardString);
		state.scrollToCursor();
	}
	// TODO: Handle ctrl-C, ctrl-V, ctrl-X, ctrl-Z
	// TODO: Make ctrl-/ do commenting out (take inspiration from tab)

	if (keyPressed(K.ENTER)) {
		// TODO: Make this play nicely with indentation
		state.insertText("\n");
		state.scrollToCursor();
	}
	if (keyPressed(K.TAB)) {
		if (!shiftKeyDown()) {
			if (state.isCollapsed()) {
				state.insertText("\t");
			} else {
				state.indent("\t");
			}
		} else {
			state.outdent(/^(\t| )/);
		}
		state.scrollToCursor();
	}
	if (keyPressed(K.BACKSPACE)) {
		state.backspace();
		state.scrollToCursor();
	}
	if (keyPressed(K.DELETE)) {
		state.delete();
		state.scrollToCursor();
	}
	if (keyPressed(K.ARROW_RIGHT)) {
		if (shiftKeyDown()) {
			state.setFocus(focus+1);
		} else {
			state.setSelection(focus+1);
		}
		state.scrollToCursor();
	}
	if (keyPressed(K.ARROW_LEFT)) {
		if (shiftKeyDown()) {
			state.setFocus(focus-1);
		} else {
			state.setSelection(focus-1);
		}
		state.scrollToCursor();
	}
	if (keyPressed(K.ARROW_DOWN)) {
		if (shiftKeyDown()) {
			state.setFocus({x: focusX, y: focusY+1});
		} else {
			state.setSelection({x: focusX, y: focusY+1});
		}
		state.scrollToCursor();
	}
	if (keyPressed(K.ARROW_UP)) {
		if (shiftKeyDown()) {
			state.setFocus({x: focusX, y: focusY-1});
		} else {
			state.setSelection({x: focusX, y: focusY-1});
		}
		state.scrollToCursor();
	}
}

const draw = () => {
	clearScreen();
	drawCodeField(state.code, 0, 8, 128, 112);
}

export const codetab = {
	update,
	draw,
}