import { Component, Setter, createEffect, createSignal, on } from 'solid-js';

import styles from './Page.module.css';
import Page from './Page';

const basicChars = '0123456789abcdefghijklmnopqrstuvwxyz';
// const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890+/';

function group(str: string, groupSize: number) {
	const subs = [];
	for (let i = 0; i < Math.ceil(str.length / groupSize); i++) {
		let sub = str.slice(Math.max(0, str.length - i * groupSize - groupSize), str.length - i * groupSize);
		while (sub.length < groupSize) sub = '0' + sub;
		subs.push(sub);
	}

	return subs.reverse().join(' ');
}

function parse(value: string, base: number) {
	return [...value].reduce((r, v) => r * BigInt(base) + BigInt(parseInt(v, base)), 0n);
}

/*function parseBase64(value: string) {
	return [...value].reduce((r, v) => r * BigInt(64) + BigInt(base64Chars.indexOf(v)), 0n);
}

function toBase64(value: bigint) {
	let str = '';
	while (value > 0) {
		str = base64Chars[(Number(value % 64n))] + str;
		value /= 64n;
	}
	return str;
}*/

function parseAscii(value: string) {
	return [...value].reduce((r, v) => r * BigInt(256) + BigInt(v.charCodeAt(0)), 0n);
}

function toAscii(value: bigint) {
	let str = '';
	while (value > 0) {
		str = String.fromCharCode(Number(value % 256n)) + str;
		value /= 256n;
	}
	return str;
}

const ConvertBase: Component = () => {
	const [error, setError] = createSignal(' ');
	const [value, setValue] = createSignal(BigInt(0));
	const [decimal, setDecimal] = createSignal('0');
	const [binary, setBinary] = createSignal('0');
	const [binaryOctets, setBinaryOctet] = createSignal(true);
	const [octal, setOctal] = createSignal('0');
	const [octalTriplets, setOctalTriplet] = createSignal(true);
	const [hex, setHex] = createSignal('0');
	const [hexPairs, setHexPairs] = createSignal(true);
	const [base36, setBase36] = createSignal('0');
	//const [base64, setBase64] = createSignal('');
	const [ascii, setAscii] = createSignal('');

	function updateExcept(value: string, base: number, except: Setter<string>, preserveSpaces: boolean = true) {
		const raw = preserveSpaces ? value : value.replaceAll(/ /g, '');

		setError('');

		let num;
		if (base <= 36) {
			if ([...raw].find(c => !basicChars.slice(0, base).includes(c) && !basicChars.toUpperCase().slice(0, base).includes(c))) {
				setError('Invalid base ' + base + ' number');
				return;
			}

			num = parse(raw, base);
		}/* else if (base == 64) {
			if ([...raw].find(c => !base64Chars.includes(c))) {
				setError('Invalid base 64 string');
				return;
			}

			num = parseBase64(raw);
		}*/ else if (base == 256) {
			num = parseAscii(raw);
		} else {
			setError('Invalid base');
			return;
		}


		setValue(num);
		if (setDecimal != except) setDecimal(num.toString());
		if (setBinary != except) setBinary(binaryOctets() ? group(num.toString(2), 8) : num.toString(2));
		if (setOctal != except) setOctal(octalTriplets() ? group(num.toString(8), 3) : num.toString(8));
		if (setHex != except) setHex(hexPairs() ? group(num.toString(16), 2) : num.toString(16));
		if (setBase36 != except) setBase36(num.toString(36));
		//if (setBase64 != except) setBase64(toBase64(num));
		if (setAscii != except) setAscii(toAscii(num));
	}

	createEffect(on(binaryOctets, () => setBinary(binaryOctets() ? group(value().toString(2), 8) : value().toString(2))));
	createEffect(on(octalTriplets, () => setOctal(octalTriplets() ? group(value().toString(8), 3) : value().toString(8))));
	createEffect(on(hexPairs, () => setHex(hexPairs() ? group(value().toString(16), 2) : value().toString(16))));

	return (
		<Page title={'Convert Base'}>
			<span>{error()}</span>
			<h2>Decimal</h2>
			<textarea value={decimal()} oninput={e => updateExcept(e.currentTarget.value, 10, setDecimal)} />
			<h2>Binary</h2>
			<textarea value={binary()} oninput={e => updateExcept(e.currentTarget.value, 2, setBinary)} />
			<div class={styles.checkbox}>
				<input id={'octets'} type={'checkbox'} checked={binaryOctets()} onchange={e => setBinaryOctet(e.currentTarget.checked)} />
				<label for={'octets'}>Octets</label>
			</div>
			<h2>Octal</h2>
			<textarea value={octal()} oninput={e => updateExcept(e.currentTarget.value, 8, setOctal)} />
			<div class={styles.checkbox}>
				<input id={'triplets'} type={'checkbox'} checked={octalTriplets()} onchange={e => setOctalTriplet(e.currentTarget.checked)} />
				<label for={'triplets'}>Triplets</label>
			</div>
			<h2>Hex</h2>
			<textarea value={hex()} oninput={e => updateExcept(e.currentTarget.value, 16, setHex)} />
			<div class={styles.checkbox}>
				<input id={'pairs'} type={'checkbox'} checked={hexPairs()} onchange={e => setHexPairs(e.currentTarget.checked)} />
				<label for={'pairs'}>Pairs</label>
			</div>
			<h2>Base 36</h2>
			<textarea value={base36()} oninput={e => updateExcept(e.currentTarget.value, 36, setBase36)} />
			{/*<h2>Base 64</h2>
			<textarea value={base64()} oninput={e => updateExcept(e.currentTarget.value, 64, setBase64)} />*/}
			<h2>ASCII</h2>
			<textarea value={ascii()} oninput={e => updateExcept(e.currentTarget.value, 256, setAscii, true)} />
		</Page >
	);
};

export default ConvertBase;
