// Fetched from https://github.com/PretendoNetwork/account/blob/master/src/mii.js on 2021-12-16

const KaitaiStream = require('kaitai-struct/KaitaiStream');

class Mii extends KaitaiStream {
	constructor(arrayBuffer, byteOffset) {
		super(arrayBuffer, byteOffset);

		this.decode();
	}

	decode() {
		// Decode raw data
		// A lot of this goes unused
		this.unknown1 = this.readU1();
		this.characterSet = this.readBitsIntBe(2);
		this.regionLock = this.readBitsIntBe(2);
		this.profanityFlag = this.readBitsIntBe(1) !== 0;
		this.copying = this.readBitsIntBe(1) !== 0;
		this.unknown2 = this.readBitsIntBe(2);
		this.slotIndex = this.readBitsIntBe(4);
		this.pageIndex = this.readBitsIntBe(4);
		this.version = this.readBitsIntBe(4);
		this.unknown3 = this.readBitsIntBe(4);
		this.systemId = Array(8).fill().map(() => this.readU1());
		this.avatarId = Array(4).fill().map(() => this.readU1());
		this.clientId = Array(6).fill().map(() => this.readU1());
		this.padding = this.readU2le();
		this.miiMetaData = this.readU2le();
		this.miiName = Buffer.from(this.readBytes(20)).toString('utf16le');
		this.height = this.readU1();
		this.build = this.readU1();
		this.faceColor = this.readBitsIntBe(3);
		this.faceType = this.readBitsIntBe(4);
		this.mingle = this.readBitsIntBe(1) !== 0;
		this.faceMakeup = this.readBitsIntBe(4);
		this.faceWrinkles = this.readBitsIntBe(4);
		this.alignToByte();
		this.hairType = this.readU1();
		this.unknown5 = this.readBitsIntBe(4);
		this.hairFlip = this.readBitsIntBe(1) !== 0;
		this.hairColor = this.readBitsIntBe(3);
		this.alignToByte();
		this.eyeData = this.readU4le();
		this.eyebrowData = this.readU4le();
		this.noseData = this.readU2le();
		this.mouthData = this.readU2le();
		this.mouthData2 = this.readU2le();
		this.facialHairData = this.readU2le();
		this.glassesData = this.readU2le();
		this.moleData = this.readU2le();
		this.creatorName = Buffer.from(this.readBytes(20)).toString('utf16le');
		this.padding2 = this.readU2le();
		this.checksum = this.readU2le();

		// Carve out more specific data from the above values
		// TODO: read these bits directly instead of getting them later

		this.gender = (this.miiMetaData & 1);
		this.birthMonth = ((this.miiMetaData >> 1) & 15);
		this.birthDay = ((this.miiMetaData >> 5) & 31);
		this.favoriteColor = ((this.miiMetaData >> 10) & 15);
		this.favorite = ((this.miiMetaData >> 14) & 1);

		this.eyeType = (this.eyeData & 63);
		this.eyeColor = ((this.eyeData >> 6) & 7);
		this.eyeSize = ((this.eyeData >> 9) & 7);
		this.eyeStretch = ((this.eyeData >> 13) & 7);
		this.eyeRotation = ((this.eyeData >> 16) & 31);
		this.eyeHorizontal = ((this.eyeData >> 21) & 15);
		this.eyeVertical = ((this.eyeData >> 25) & 31);

		this.eyebrowType = (this.eyebrowData & 31);
		this.eyebrowColor = ((this.eyebrowData >> 5) & 7);
		this.eyebrowSize = ((this.eyebrowData >> 8) & 15);
		this.eyebrowStretch = ((this.eyebrowData >> 12) & 7);
		this.eyebrowRotation = ((this.eyebrowData >> 16) & 15);
		this.eyebrowHorizontal = ((this.eyebrowData >> 21) & 15);
		this.eyebrowVertical = ((this.eyebrowData >> 25) & 31);

		this.noseType = (this.noseData & 31);
		this.noseSize = ((this.noseData >> 5) & 15);
		this.noseVertical = ((this.noseData >> 9) & 31);


		this.mouthType = (this.mouthData & 63);
		this.mouthColor = ((this.mouthData >> 6) & 7);
		this.mouthSize = ((this.mouthData >> 9) & 15);
		this.mouthStretch = ((this.mouthData >> 13) & 7);

		this.mouthVertical = (this.mouthData2 & 31);
		this.facialHairMustache = ((this.mouthData2 >> 5) & 7);

		this.facialHairType = (this.facialHairData & 7);
		this.facialHairColor = ((this.facialHairData >> 3) & 7);
		this.facialHairSize = ((this.facialHairData >> 6) & 15);
		this.facialHairVertical = ((this.facialHairData >> 10) & 31);

		this.glassesType = (this.glassesData & 15);
		this.glassesColor = (this.glassesData >> 4) & 7;
		this.glassesSize = (this.glassesData >> 7) & 15;
		this.glassesVertical = (this.glassesData >> 11) & 15;

		this.moleEnable = (this.moleData >> 15);
		this.moleSize = ((this.moleData >> 1) & 15);
		this.moleHorizontal = ((this.moleData >> 5) & 31);
		this.moleVertical = ((this.moleData >> 10) & 31);
	}

	toStudioMii() {
		/*
			Can also disable randomization with:
			let miiStudioData = Buffer.alloc(0x2F);
			let next = 256;
			and removing "randomizer" and the "miiStudioData.writeUInt8(randomizer);" call
		*/
		const miiStudioData = Buffer.alloc(0x2F);
		const randomizer = Math.floor(256 * Math.random());
		let next = randomizer;
		let pos = 1;

		function encodeMiiPart(partValue) {
			const encoded = (7 + (partValue ^ next)) % 256;
			next = encoded;

			miiStudioData.writeUInt8(encoded, pos);
			pos++;
		}

		miiStudioData.writeUInt8(randomizer);

		if (this.facialHairColor === 0) {
			encodeMiiPart(8);
		} else {
			encodeMiiPart(this.facialHairColor);
		}

		encodeMiiPart(this.facialHairType);
		encodeMiiPart(this.build);
		encodeMiiPart(this.eyeStretch);
		encodeMiiPart(this.eyeColor + 8);
		encodeMiiPart(this.eyeRotation);
		encodeMiiPart(this.eyeSize);
		encodeMiiPart(this.eyeType);
		encodeMiiPart(this.eyeHorizontal);
		encodeMiiPart(this.eyeVertical);
		encodeMiiPart(this.eyebrowStretch);

		if (this.eyebrowColor === 0) {
			encodeMiiPart(8);
		} else {
			encodeMiiPart(this.eyebrowColor);
		}

		encodeMiiPart(this.eyebrowRotation);
		encodeMiiPart(this.eyebrowSize);
		encodeMiiPart(this.eyebrowType);
		encodeMiiPart(this.eyebrowHorizontal);
		encodeMiiPart(this.eyebrowVertical);
		encodeMiiPart(this.faceColor);
		encodeMiiPart(this.faceMakeup);
		encodeMiiPart(this.faceType);
		encodeMiiPart(this.faceWrinkles);
		encodeMiiPart(this.favoriteColor);
		encodeMiiPart(this.gender);

		if (this.glassesColor == 0) {
			encodeMiiPart(8);
		} else if (this.glassesColor < 6) {
			encodeMiiPart(this.glassesColor + 13);
		} else {
			encodeMiiPart(0);
		}

		encodeMiiPart(this.glassesSize);
		encodeMiiPart(this.glassesType);
		encodeMiiPart(this.glassesVertical);

		if (this.hairColor == 0) {
			encodeMiiPart(8);
		} else {
			encodeMiiPart(this.hairColor);
		}

		encodeMiiPart(this.hairFlip ? 1 : 0);
		encodeMiiPart(this.hairType);
		encodeMiiPart(this.height);
		encodeMiiPart(this.moleSize);
		encodeMiiPart(this.moleEnable);
		encodeMiiPart(this.moleHorizontal);
		encodeMiiPart(this.moleVertical);
		encodeMiiPart(this.mouthStretch);

		if (this.mouthColor < 4) {
			encodeMiiPart(this.mouthColor + 19);
		} else {
			encodeMiiPart(0);
		}

		encodeMiiPart(this.mouthSize);
		encodeMiiPart(this.mouthType);
		encodeMiiPart(this.mouthVertical);
		encodeMiiPart(this.facialHairSize);
		encodeMiiPart(this.facialHairMustache);
		encodeMiiPart(this.facialHairVertical);
		encodeMiiPart(this.noseSize);
		encodeMiiPart(this.noseType);
		encodeMiiPart(this.noseVertical);

		return miiStudioData;
	}
}

module.exports = Mii;