import Barcode from "../Barcode.js";
import { SHIFT, SET_A, SET_B, MODULO, STOP, FNC1, SET_BY_CODE, SWAP, BARS } from './constants';

// This is the master class,
// it does require the start code to be included in the string
class CODE128 extends Barcode {
	constructor(data, options) {
		super(data.substring(1), options);

		// Get array of ascii codes from data
		this.bytes = data.split('')
			.map(char => char.charCodeAt(0));
	}

	valid() {
		// ASCII value ranges 0-127, 200-211
		return /^[\x00-\x7F\xC8-\xD3]+$/.test(this.data);
	}

	// The public encoding function
	encode() {
		const bytes = this.bytes;
		// Remove the start code from the bytes and set its index
		const startIndex = bytes.shift() - 105;
		// Get start set by index
		const startSet = SET_BY_CODE[startIndex];

		if (startSet === undefined) {
			throw new RangeError('The encoding does not start with a start character.');
		}

		if (this.shouldEncodeAsEan128() === true) {
			bytes.unshift(FNC1);
		}

		// Start encode with the right type
		const encodingResult = CODE128.next(bytes, 1, startSet);

		return {
			text:
				this.text === this.data
					? this.text.replace(/[^\x20-\x7E]/g, '')
					: this.text,
			data:
				// Add the start bits
				CODE128.getBar(startIndex) +
				// Add the encoded bits
				encodingResult.result +
				// Add the checksum
				CODE128.getBar((encodingResult.checksum + startIndex) % MODULO) +
				// Add the end bits
				CODE128.getBar(STOP)
		};
	}

	// GS1-128/EAN-128
	shouldEncodeAsEan128() {
		let isEAN128 = this.options.ean128 || false;
		if (typeof isEAN128 === 'string') {
			isEAN128 = isEAN128.toLowerCase() === 'true';
		}
		return isEAN128;
	}

	// Get a bar symbol by index
	static getBar(index) {
		return BARS[index] ? BARS[index].toString() : '';
	}

	// Correct an index by a set and shift it from the bytes array
	static correctIndex(bytes, set) {
		if (set === SET_A) {
			const charCode = bytes.shift();
			return charCode < 32 ? charCode + 64 : charCode - 32;
		} else if (set === SET_B) {
			return bytes.shift() - 32;
		} else {
			return (bytes.shift() - 48) * 10 + bytes.shift() - 48;
		}
	}

	static next(bytes, pos, set) {
		if (!bytes.length) {
			return { result: '', checksum: 0 };
		}

		let nextCode, index;

		// Special characters
		if (bytes[0] >= 200){
			index = bytes.shift() - 105;
			const nextSet = SWAP[index];

			// Swap to other set
			if (nextSet !== undefined) {
				nextCode = CODE128.next(bytes, pos + 1, nextSet);
			}
			// Continue on current set but encode a special character
			else {
				// Shift
				if ((set === SET_A || set === SET_B) && index === SHIFT) {
					// Convert the next character so that is encoded correctly
					bytes[0] = (set === SET_A)
						? bytes[0] > 95 ? bytes[0] - 96 : bytes[0]
						: bytes[0] < 32 ? bytes[0] + 96 : bytes[0];
				}
				nextCode = CODE128.next(bytes, pos + 1, set);
			}
		}
		// Continue encoding
		else {
			index = CODE128.correctIndex(bytes, set);
			nextCode = CODE128.next(bytes, pos + 1, set);
		}

		// Get the correct binary encoding and calculate the weight
		const enc = CODE128.getBar(index);
		const weight = index * pos;

		return {
			result: enc + nextCode.result,
			checksum: weight + nextCode.checksum
		};
	}
}

export default CODE128;
