const { Op } = require("sequelize");
const database = require("../../database");

// TypeChecker class
class TypeChecker {
	constructor (strictMode = true) {
		this.strict = strictMode;
		
		this.requiredMissed = [];
		this.additionalMissed = [];
		
		this.requiredUnsyntax = [];
		this.additionalUnsyntax = [];
		
		this.unsyntax = false;
	}
	
	async checkRequired (param, Type, paramName) {
		if (param === undefined) {
			this.requiredMissed.push(paramName);
			this.unsyntax = true;
		}
		else {
			const type = new Type(param);
			if (this.strict && !(await type.checkStrict())) {
				this.requiredUnsyntax.push(paramName);
				this.unsyntax = true;
			}
			else if (!this.strict && !(await type.checkNoStrict())) {
				this.requiredUnsyntax.push(paramName);
				this.unsyntax = true;
			}
		}
	}
	
	async checkAdditional (param, Type, paramName) {
		if (param === undefined) {
			this.additionalMissed.push(paramName);
		}
		else {
			const type = new Type(param);
			if (this.strict && !(await type.checkStrict())) {
				this.additionalUnsyntax.push(paramName);
				this.unsyntax = true;
			}
			else if (!this.strict && !(await type.checkNoStrict())) {
				this.additionalUnsyntax.push(paramName);
				this.unsyntax = true;
			}
		}
	}
	
	async calculate () {
		return {
			success  : !this.unsyntax,
			missed   : {
				required   : this.requiredMissed,
				additional : this.additionalMissed
			},
			unsyntax : {
				required   : this.requiredUnsyntax,
				additional : this.additionalUnsyntax
			}
		}
	}
}


// Types

class ParamType {
	constructor (value) {
		this.value = value;
	}

	async checkNoStrict () {
		return true;
	}

	async checkStrict () {
		return true;
	}
}


class StrType extends ParamType {
	async checkStrict () {
		return typeof this.value === 'string';
	}
	
	async checkNoStrict () {
		return typeof this.value === 'string';
	}
}

class IntType extends ParamType {
	async checkNoStrict () {
		return Number.isInteger(+this.value);
	}

	async checkStrict () {
		return Number.isInteger(this.value);
	}
}

// Custom types

class ItemType extends StrType {
	async checkStrict () {
		if (await super.checkStrict()) return ['music', 'author'].includes(this.value);
		return false;
	}
	
	async checkNoStrict () {
		if (await super.checkNoStrict()) return ['music', 'author'].includes(this.value);
		return false;
	}
}

class NameType extends StrType {
	async checkStrict () {
		if (await super.checkStrict()) return this.value.length >= 2;
		return false;
	}
	
	async checkNoStrict () {
		if (await super.checkNoStrict()) return this.value.length >= 2;
		return false;
	}
}

class AuthorType extends IntType {
	async checkStrict () {
		if (await super.checkStrict()) {
			const authorId = this.value;
			return (await database.authors.findAll({ where: { id: authorId }, raw : true })).length !== 0;
		}
		return false;
	}
	
	async checkNoStrict () {
		if (await super.checkNoStrict()) {
			const authorId = +this.value;
			return (await database.authors.findAll({ where: { id: authorId }, raw : true })).length !== 0;
		}
		return false;
	}
}

class AuthorsType extends AuthorType {
	async checkStrict () {
		if (Array.isArray(this.value) && !this.value.map(i => Number.isInteger(i)).includes(false)) {
			// return (await database.authors.findAll({ where: [Op.or]: [
				// { id: 12 },
				// { id: 13 }
			// ], raw : true })).length === this.value;
			return (await database.authors.findAll({ where: { [Op.or]: this.value.map(id => ({ id })) }, raw : true })).length === this.value;
		}
		return false;
	}
	
	async checkNoStrict () {
		if (await(new StrType(this.value)).checkStrict()) {
			const values = this.value.split(',').map(i => +i);
			if (!values.map(i => Number.isInteger(i)).includes(false)) {
				return (await database.authors.findAll({ where: { [Op.or]: values.map(id => ({ id })) }, raw : true })).length === this.value.length;
			}
			return false;
		}
		return false;
	}
}

class MusicType extends IntType {
	async checkStrict () {
		if (await super.checkStrict()) {
			const musicId = this.value;
			return (await database.music.findAll({ where: { id: musicId }, raw : true })).length !== 0;
		}
		return false;
	}
	
	async checkNoStrict () {
		if (await super.checkNoStrict()) {
			const musicId = +this.value;
			return (await database.music.findAll({ where: { id: musicId }, raw : true })).length !== 0;
		}
		return false;
	}
}

class Base64FileType extends StrType {
	async checkStrict () {
		return this.value === null || (await super.checkStrict());
	}
}

module.exports = {
	ParamType,
	StrType, IntType,
	ItemType, NameType, AuthorType, AuthorsType, MusicType, Base64FileType,
	TypeChecker
};
