From ecbd30e160f0ea33d3b24c35890a0589018aa78a Mon Sep 17 00:00:00 2001 From: ITOH <72305210+itohatweb@users.noreply.github.com> Date: Mon, 8 Mar 2021 17:59:26 +0100 Subject: [PATCH] bettrrrr --- as | 0 bot.ts | 19 ++ los.ts | 421 +++++++++++++++++++++++++++++++++ src/util/permissions.ts | 137 ++++++----- tmp.ts | 502 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1022 insertions(+), 57 deletions(-) create mode 100644 as create mode 100644 bot.ts create mode 100644 los.ts create mode 100644 tmp.ts diff --git a/as b/as new file mode 100644 index 000000000..e69de29bb diff --git a/bot.ts b/bot.ts new file mode 100644 index 000000000..1b63204e1 --- /dev/null +++ b/bot.ts @@ -0,0 +1,19 @@ +import { startBot } from "./src/bot.ts"; + +startBot({ + token: "Nzg1MTM3Mjg5MDcyMDgyOTY1.X8zeFA.vP6lAmWqO2wy9-uOyV4__wNMy8o", + intents: ["GUILDS", "GUILD_MESSAGES", "GUILD_EMOJIS"], + eventHandlers: { + ready() { + console.log("Successfully connected to gateway"); + }, + messageCreate(message) { + if (message.content === "ping") { + message.reply("Pong using Discordeno!"); + } + }, + guildEmojisUpdate(g, e, c) { + console.log(e); + }, + }, +}); diff --git a/los.ts b/los.ts new file mode 100644 index 000000000..f0a347636 --- /dev/null +++ b/los.ts @@ -0,0 +1,421 @@ +/** + * Custom implementation of a double ended queue. + */ +export class Denque { + #head = 0; + #tail = 0; + #capacity?: number = undefined; + #capacityMask = 0x3; + #list = new Array(4); + + constructor(array?: T[], options?: IDenqueOptions) { + if (options?.capacity) this.#capacity = options.capacity; + + if (Array.isArray(array)) { + this.fromArray(array); + } + } + + /** + * Returns the item at the specified index from the list. + * 0 is the first element, 1 is the second, and so on... + * Elements at negative values are that many from the end: -1 is one before the end + * (the last element), -2 is two before the end (one before last), etc. + */ + peekAt(index: number) { + let i = index; + // expect a number or return undefined + if (i !== (i | 0)) { + return void 0; + } + let len = this.size(); + if (i >= len || i < -len) return undefined; + if (i < 0) i += len; + i = (this.#head + i) & this.#capacityMask; + return this.#list[i]; + } + + /** + * Alias for peekAt() + */ + get(i: number) { + return this.peekAt(i); + } + + /** + * Returns the first item in the list without removing it. + */ + peek() { + if (this.#head === this.#tail) return undefined; + return this.#list[this.#head]; + } + + /** + * Alias for peek() + */ + peekFront() { + return this.peek(); + } + + /** + * Returns the item that is at the back of the queue without removing it. + * Uses peekAt(-1) + */ + peekBack() { + return this.peekAt(-1); + } + + /** + * Returns the current length of the queue + */ + get length() { + return this.size(); + } + + /** + * Return the number of items on the list, or 0 if empty. + */ + size() { + if (this.#head === this.#tail) return 0; + if (this.#head < this.#tail) return this.#tail - this.#head; + else return this.#capacityMask + 1 - (this.#head - this.#tail); + } + + /** + * Add an item at the beginning of the list. + */ + unshift(item: T) { + if (item === undefined) return this.size(); + let len = this.#list.length; + this.#head = (this.#head - 1 + len) & this.#capacityMask; + this.#list[this.#head] = item; + if (this.#tail === this.#head) this.growArray(); + if (this.#capacity && this.size() > this.#capacity) this.pop(); + if (this.#head < this.#tail) return this.#tail - this.#head; + else return this.#capacityMask + 1 - (this.#head - this.#tail); + } + + /** + * Remove and return the first item on the list, + * Returns undefined if the list is empty. + + */ + shift() { + let head = this.#head; + if (head === this.#tail) return undefined; + let item = this.#list[head]; + this.#list[head] = undefined; + this.#head = (head + 1) & this.#capacityMask; + if ( + head < 2 && this.#tail > 10000 && this.#tail <= this.#list.length >>> 2 + ) { + this.shrinkArray(); + } + return item; + } + + /** + * Add an item to the bottom of the list. + */ + push(item: T) { + if (item === undefined) return this.size(); + let tail = this.#tail; + this.#list[tail] = item; + this.#tail = (tail + 1) & this.#capacityMask; + if (this.#tail === this.#head) { + this.growArray(); + } + if (this.#capacity && this.size() > this.#capacity) { + this.shift(); + } + if (this.#head < this.#tail) return this.#tail - this.#head; + else return this.#capacityMask + 1 - (this.#head - this.#tail); + } + + /** + * Remove and return the last item on the list. + * Returns undefined if the list is empty. + */ + pop() { + let tail = this.#tail; + if (tail === this.#head) return undefined; + let len = this.#list.length; + this.#tail = (tail - 1 + len) & this.#capacityMask; + let item = this.#list[this.#tail]; + this.#list[this.#tail] = undefined; + if (this.#head < 2 && tail > 10000 && tail <= len >>> 2) this.shrinkArray(); + return item; + } + + /** + * Remove and return the item at the specified index from the list. + * Returns undefined if the list is empty. + */ + removeOne(index: number) { + let i = index; + // expect a number or return undefined + if (i !== (i | 0)) { + return void 0; + } + if (this.#head === this.#tail) return void 0; + let size = this.size(); + let len = this.#list.length; + if (i >= size || i < -size) return void 0; + if (i < 0) i += size; + i = (this.#head + i) & this.#capacityMask; + let item = this.#list[i]; + let k; + if (index < size / 2) { + for (k = index; k > 0; k--) { + this.#list[i] = this.#list[(i = (i - 1 + len) & this.#capacityMask)]; + } + this.#list[i] = void 0; + this.#head = (this.#head + 1 + len) & this.#capacityMask; + } else { + for (k = size - 1 - index; k > 0; k--) { + this.#list[i] = this.#list[(i = (i + 1 + len) & this.#capacityMask)]; + } + this.#list[i] = void 0; + this.#tail = (this.#tail - 1 + len) & this.#capacityMask; + } + return item; + } + + /** + * Remove number of items from the specified index from the list. + * Returns array of removed items. + * Returns undefined if the list is empty. + */ + remove(index: number, count: number) { + let i = index; + let removed; + let del_count = count; + // expect a number or return undefined + if (i !== (i | 0)) { + return void 0; + } + if (this.#head === this.#tail) return void 0; + let size = this.size(); + let len = this.#list.length; + if (i >= size || i < -size || count < 1) return void 0; + if (i < 0) i += size; + if (count === 1 || !count) { + removed = new Array(1); + removed[0] = this.removeOne(i); + return removed; + } + if (i === 0 && i + count >= size) { + removed = this.toArray(); + this.clear(); + return removed; + } + if (i + count > size) count = size - i; + let k; + removed = new Array(count); + for (k = 0; k < count; k++) { + removed[k] = this.#list[(this.#head + i + k) & this.#capacityMask]; + } + i = (this.#head + i) & this.#capacityMask; + if (index + count === size) { + this.#tail = (this.#tail - count + len) & this.#capacityMask; + for (k = count; k > 0; k--) { + this.#list[(i = (i + 1 + len) & this.#capacityMask)] = void 0; + } + return removed; + } + if (index === 0) { + this.#head = (this.#head + count + len) & this.#capacityMask; + for (k = count - 1; k > 0; k--) { + this.#list[(i = (i + 1 + len) & this.#capacityMask)] = void 0; + } + return removed; + } + if (i < size / 2) { + this.#head = (this.#head + index + count + len) & this.#capacityMask; + for (k = index; k > 0; k--) { + this.unshift(this.#list[(i = (i - 1 + len) & this.#capacityMask)]); + } + i = (this.#head - 1 + len) & this.#capacityMask; + while (del_count > 0) { + this.#list[(i = (i - 1 + len) & this.#capacityMask)] = void 0; + del_count--; + } + if (index < 0) this.#tail = i; + } else { + this.#tail = i; + i = (i + count + len) & this.#capacityMask; + for (k = size - (count + index); k > 0; k--) { + this.push(this.#list[i++]); + } + i = this.#tail; + while (del_count > 0) { + this.#list[(i = (i + 1 + len) & this.#capacityMask)] = void 0; + del_count--; + } + } + if (this.#head < 2 && this.#tail > 10000 && this.#tail <= len >>> 2) { + this.shrinkArray(); + } + return removed; + } + + /** + * Native splice implementation. + * Remove number of items from the specified index from the list and/or add new elements. + * Returns array of removed items or empty array if count == 0. + * Returns undefined if the list is empty. + */ + splice(index: number, count: number) { + let i = index; + // expect a number or return undefined + if (i !== (i | 0)) { + return void 0; + } + let size = this.size(); + if (i < 0) i += size; + if (i > size) return void 0; + if (arguments.length > 2) { + let k; + let temp; + let removed; + let arg_len = arguments.length; + let len = this.#list.length; + let arguments_index = 2; + if (!size || i < size / 2) { + temp = new Array(i); + for (k = 0; k < i; k++) { + temp[k] = this.#list[(this.#head + k) & this.#capacityMask]; + } + if (count === 0) { + removed = []; + if (i > 0) { + this.#head = (this.#head + i + len) & this.#capacityMask; + } + } else { + removed = this.remove(i, count); + this.#head = (this.#head + i + len) & this.#capacityMask; + } + while (arg_len > arguments_index) { + this.unshift(arguments[--arg_len]); + } + for (k = i; k > 0; k--) { + this.unshift(temp[k - 1]); + } + } else { + temp = new Array(size - (i + count)); + let leng = temp.length; + for (k = 0; k < leng; k++) { + temp[k] = + this.#list[(this.#head + i + count + k) & this.#capacityMask]; + } + if (count === 0) { + removed = []; + if (i != size) { + this.#tail = (this.#head + i + len) & this.#capacityMask; + } + } else { + removed = this.remove(i, count); + this.#tail = (this.#tail - leng + len) & this.#capacityMask; + } + while (arguments_index < arg_len) { + this.push(arguments[arguments_index++]); + } + for (k = 0; k < leng; k++) { + this.push(temp[k]); + } + } + return removed; + } else { + return this.remove(i, count); + } + } + + /** + * Soft clear - does not reset capacity. + */ + clear() { + this.#head = 0; + this.#tail = 0; + } + + /** + * Returns true or false whether the list is empty. + */ + isEmpty() { + return this.#head === this.#tail; + } + + /** + * Returns an array of all queue items. + */ + toArray() { + return this.copyArray(false); + } + + /** + * ------------- + * INTERNALS + * ------------- + */ + + /** + * Fills the queue with items from an array + * For use in the constructor + */ + private fromArray(array: T[]) { + for (var i = 0; i < array.length; i++) this.push(array[i]); + } + + /** + * Clones the array either fully or partially + */ + private copyArray(fullCopy: boolean) { + let newArray = []; + let list = this.#list; + let len = list.length; + let i; + if (fullCopy || this.#head > this.#tail) { + for (i = this.#head; i < len; i++) newArray.push(list[i]); + for (i = 0; i < this.#tail; i++) newArray.push(list[i]); + } else { + for (i = this.#head; i < this.#tail; i++) newArray.push(list[i]); + } + return newArray; + } + + /** + * Grows the internal list array. + */ + private growArray() { + if (this.#head) { + // copy existing data, head to end, then beginning to tail. + this.#list = this.copyArray(true); + this.#head = 0; + } + + // head is at 0 and array is now full, safe to extend + this.#tail = this.#list.length; + + this.#list.length *= 2; + this.#capacityMask = (this.#capacityMask << 1) | 1; + } + + /** + * Shrinks the internal list array. + */ + shrinkArray() { + console.log("list", this.#list.length); + this.#list.length >>>= 1; + console.log("mask", this.#capacityMask); + this.#capacityMask >>>= 1; + } +} + +export interface IDenqueOptions { + capacity?: number; +} + +const f = new Denque(["a", "b", "c", "a", "d"]); +console.log(f.toArray()); +console.log(f.removeOne(1)); +console.log(f.toArray()); diff --git a/src/util/permissions.ts b/src/util/permissions.ts index ebdae7357..da1669485 100644 --- a/src/util/permissions.ts +++ b/src/util/permissions.ts @@ -1,23 +1,48 @@ import { cacheHandlers } from "../api/controllers/cache.ts"; -import { Role } from "../api/structures/mod.ts"; +import { Channel, Guild, Member, Role } from "../api/structures/mod.ts"; import { botID } from "../bot.ts"; import { Errors, Permission, Permissions } from "../types/mod.ts"; +async function getCached(table: "guild", key: string | Guild): Promise; +async function getCached( + table: "channel", + key: string | Channel, +): Promise; +async function getCached( + table: "member", + key: string | Member, +): Promise; +async function getCached( + table: "guild" | "channel" | "member", + key: string | Guild | Channel | Member, +) { + const cached = typeof key === "string" + ? // @ts-ignore - + (await cacheHandlers.get(`${table}s`, key)) + : key; + if (!cached || typeof cached === "string") { + throw new Error(Errors[`${table.toUpperCase}_NOT_FOUND` as Errors]); + } + + return cached; +} + /** Calculates the permissions this member has in the given guild */ export async function calculateBasePermissions( - memberID: string, - guildID: string + guild: string | Guild, + member: string | Member, ) { - const guild = await cacheHandlers.get("guilds", guildID); - if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); - - const member = await cacheHandlers.get("members", memberID); - if (!member) throw new Error(Errors.MEMBER_NOT_FOUND); + console.log(guild, await cacheHandlers.get("guilds", "800080308921696296")); + console.log(guild); + guild = await getCached("guild", guild); + console.log(guild); + member = await getCached("member", member); + console.log(member); let permissions = 0n; // Calculate the role permissions bits, @everyone role is not in memberRoleIDs so we need to pass guildID manualy - permissions |= [...(member.guilds.get(guildID)?.roles || []), guildID] - .map((id) => guild.roles.get(id)?.permissions) + permissions |= [...(member.guilds.get(guild.id)?.roles || []), guild.id] + .map((id) => (guild as Guild).roles.get(id)?.permissions) // Removes any edge case undefined .filter((id) => id) .reduce((bits, perms) => { @@ -26,33 +51,31 @@ export async function calculateBasePermissions( }, 0n); // If the memberID is equal to the guild ownerID he automatically has every permission so we add ADMINISTRATOR permission - if (guild.ownerID === memberID) permissions |= 8n; + if (guild.ownerID === member.id) permissions |= 8n; // Return the members permission bits as a string return permissions.toString(); } /** Calculates the permissions this member has for the given Channel */ export async function calculateChannelOverwrites( - memberID: string, - channelID: string + channel: string | Channel, + member: string | Member, ) { - const channel = await cacheHandlers.get("channels", channelID); - if (!channel) throw new Error(Errors.CHANNEL_NOT_FOUND); + channel = await getCached("channel", channel); // This is a DM channel so return ADMINISTRATOR permission if (!channel.guildID) return "8"; + member = await getCached("member", member); + // Get all the role permissions this member already has let permissions = BigInt( - await calculateBasePermissions(memberID, channel.guildID) + await calculateBasePermissions(channel.guildID, member), ); - const member = await cacheHandlers.get("members", memberID); - if (!member) throw new Error(Errors.MEMBER_NOT_FOUND); - // First calculate @everyone overwrites since these have the lowest priority const overwriteEveryone = channel?.permissionOverwrites.find( - (overwrite) => overwrite.id === channel.guildID + (overwrite) => overwrite.id === (channel as Channel).guildID, ); if (overwriteEveryone) { // First remove denied permissions since denied < allowed @@ -79,7 +102,7 @@ export async function calculateChannelOverwrites( // Third calculate member specific overwrites since these have the highest priority const overwriteMember = overwrites.find( - (overwrite) => overwrite.id === memberID + (overwrite) => overwrite.id === (member as Member).id, ); if (overwriteMember) { permissions &= ~BigInt(overwriteMember.deny); @@ -92,25 +115,25 @@ export async function calculateChannelOverwrites( /** Checks if the given permission bits are matching the given permissions. `ADMINISTRATOR` always returns `true` */ export function validatePermissions( permissionBits: string, - permissions: Permission[] + permissions: Permission[], ) { if (BigInt(permissionBits) & 8n) return true; return permissions.every( (permission) => // Check if permission is in permissionBits - BigInt(permissionBits) & BigInt(Permissions[permission]) + BigInt(permissionBits) & BigInt(Permissions[permission]), ); } /** Checks if the given member has these permissions in the given guild */ export async function hasGuildPermissions( - memberID: string, guildID: string, - permissions: Permission[] + memberID: string, + permissions: Permission[], ) { // First we need the role permission bits this member has - const basePermissions = await calculateBasePermissions(memberID, guildID); + const basePermissions = await calculateBasePermissions(guildID, memberID); // Second use the validatePermissions function to check if the member has every permission return validatePermissions(basePermissions, permissions); } @@ -118,7 +141,7 @@ export async function hasGuildPermissions( /** Checks if the bot has these permissions in the given guild */ export function botHasGuildPermissions( guildID: string, - permissions: Permission[] + permissions: Permission[], ) { // Since Bot is a normal member we can use the hasRolePermissions() function return hasGuildPermissions(botID, guildID, permissions); @@ -126,14 +149,14 @@ export function botHasGuildPermissions( /** Checks if the given member has these permissions for the given channel */ export async function hasChannelPermissions( - memberID: string, channelID: string, - permissions: Permission[] + memberID: string, + permissions: Permission[], ) { // First we need the overwrite bits this member has const channelOverwrites = await calculateChannelOverwrites( memberID, - channelID + channelID, ); // Second use the validatePermissions function to check if the member has every permission return validatePermissions(channelOverwrites, permissions); @@ -142,7 +165,7 @@ export async function hasChannelPermissions( /** Checks if the bot has these permissions f0r the given channel */ export function botHasChannelPermissions( channelID: string, - permissions: Permission[] + permissions: Permission[], ) { // Since Bot is a normal member we can use the hasRolePermissions() function return hasChannelPermissions(botID, channelID, permissions); @@ -151,23 +174,23 @@ export function botHasChannelPermissions( /** Returns the permissions that are not in the given permissionBits */ export function missingPermissions( permissionBits: string, - permissions: Permission[] + permissions: Permission[], ) { if (BigInt(permissionBits) & 8n) return []; return permissions.filter( - (permission) => !(BigInt(permissionBits) & BigInt(Permissions[permission])) + (permission) => !(BigInt(permissionBits) & BigInt(Permissions[permission])), ); } /** Throws an error if this member has not all of the given permissions */ export async function requireGuildPermissions( - memberID: string, guildID: string, - permissions: Permission[] + memberID: string, + permissions: Permission[], ) { // First we need the role permissions bits this member has - const permissionBits = await calculateBasePermissions(memberID, guildID); + const permissionBits = await calculateBasePermissions(guildID, memberID); // Second check if the member is missing any permissions const missing = missingPermissions(permissionBits, permissions); if (missing.length) { @@ -179,7 +202,7 @@ export async function requireGuildPermissions( /** Throws an error if the bot does not have all permissions */ export function requireBotGuildPermissions( guildID: string, - permissions: Permission[] + permissions: Permission[], ) { // Since Bot is a normal member we can use the throwOnMissingGuildPermission() function return requireGuildPermissions(botID, guildID, permissions); @@ -187,9 +210,9 @@ export function requireBotGuildPermissions( /** Throws an error if this member has not all of the given permissions */ export async function requireChannelPermissions( - memberID: string, channelID: string, - permissions: Permission[] + memberID: string, + permissions: Permission[], ) { // First we need the channel overwrite bits this member has const permissionBits = await calculateChannelOverwrites(memberID, channelID); @@ -204,7 +227,7 @@ export async function requireChannelPermissions( /** Throws an error if the bot has not all of the given channel permissions */ export function requireBotChannelPermissions( channelID: string, - permissions: Permission[] + permissions: Permission[], ) { // Since Bot is a normal member we can use the throwOnMissingChannelPermission() function return requireChannelPermissions(botID, channelID, permissions); @@ -226,23 +249,25 @@ export function calculateBits(permissions: Permission[]) { .reduce( // Get the bit value for this permission and assign it to bits (bits, perm) => (bits |= BigInt(Permissions[perm])), - 0n + 0n, ) .toString(); } // TODO: move memberID to first position /** Gets the highest role from the member in this guild */ -export async function highestRole(guildID: string, memberID: string) { - const guild = await cacheHandlers.get("guilds", guildID); - if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); +export async function highestRole( + guild: string | Guild, + member: string | Member, +) { + guild = await getCached("guild", guild); // Get the roles from the member const memberRoles = ( - await cacheHandlers.get("members", memberID) - )?.guilds.get(guildID)?.roles; + await getCached("member", member) + ).guilds.get(guild.id)?.roles; // This member has no roles so the highest one is the @everyone role - if (!memberRoles) return guild.roles.get(guildID) as Role; + if (!memberRoles) return guild.roles.get(guild.id) as Role; let memberHighestRole: Role | undefined; @@ -268,12 +293,11 @@ export async function highestRole(guildID: string, memberID: string) { /** Checks if the first role is higher than the second role */ export async function higherRolePosition( - guildID: string, + guild: string | Guild, roleID: string, - otherRoleID: string + otherRoleID: string, ) { - const guild = await cacheHandlers.get("guilds", guildID); - if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); + guild = await getCached("guild", guild); const role = guild.roles.get(roleID); const otherRole = guild.roles.get(otherRoleID); @@ -289,15 +313,14 @@ export async function higherRolePosition( /** Checks if the member has a higher position than the given role */ export async function isHigherPosition( - guildID: string, + guild: string | Guild, memberID: string, - compareRoleID: string + compareRoleID: string, ) { - const guild = await cacheHandlers.get("guilds", guildID); - if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); + guild = await getCached("guild", guild); if (guild.ownerID === memberID) return true; - const memberHighestRole = await highestRole(guildID, memberID); - return higherRolePosition(guildID, memberHighestRole.id, compareRoleID); + const memberHighestRole = await highestRole(guild.id, memberID); + return higherRolePosition(guild.id, memberHighestRole.id, compareRoleID); } diff --git a/tmp.ts b/tmp.ts new file mode 100644 index 000000000..62aef72c8 --- /dev/null +++ b/tmp.ts @@ -0,0 +1,502 @@ +/** // deno-lint-ignore-file */ +/** Allows easy way to add a prop to a base object when needing to use complicated getters solution. */ +// deno-lint-ignore no-explicit-any +export function createNewProp(value: any): Partial { + return { configurable: true, enumerable: true, writable: true, value }; +} + +interface IDenqueOptions { + capacity?: number; +} + +interface Denque { + /** + * Return the number of items on the list, or 0 if empty. + * @returns {number} + */ + size: number; + /** + * Add an item to the bottom of the list. + * @param item + */ + push(item: T): number; + /** + * Add an item at the beginning of the list. + * @param item + */ + unshift(item: T): number; + /** + * Remove and return the last item on the list. + * Returns undefined if the list is empty. + * @returns {*} + */ + pop(): T | undefined; + removeBack(): T | undefined; + /** + * Remove and return the first item on the list, + * Returns undefined if the list is empty. + * @returns {*} + */ + shift(): T | undefined; + /** + * Returns the item that is at the back of the queue without removing it. + * Uses peekAt(-1) + */ + peekBack: T | undefined; + /** + * Alias for peek() + * @returns {*} + */ + peekFront: T | undefined; + /** + * Returns the item at the specified index from the list. + * 0 is the first element, 1 is the second, and so on... + * Elements at negative values are that many from the end: -1 is one before the end + * (the last element), -2 is two before the end (one before last), etc. + * @param index + * @returns {*} + */ + peekAt(index: number): T | undefined; + /** + * Returns the first item in the list without removing it. + */ + peek: T; + /** + * Alias for peekAt() + * @param i + * @returns {*} + */ + get(index: number): T | undefined; + /** + * Remove number of items from the specified index from the list. + * Returns array of removed items. + * Returns undefined if the list is empty. + * @param index + * @param count + * @returns {array} + */ + remove(index: number, count: number): T[] | any; + /** + * Remove and return the item at the specified index from the list. + * Returns undefined if the list is empty. + * @param index + * @returns {*} + */ + removeOne(index: number): T | undefined; + /** + * Native splice implementation. + * Remove number of items from the specified index from the list and/or add new elements. + * Returns array of removed items or empty array if count == 0. + * Returns undefined if the list is empty. + * + * @param index + * @param count + * @param {...*} [elements] + * @returns {array} + */ + splice(index: number, count: number, ...item: T[]): T[] | undefined; + /** + * Returns true or false whether the list is empty. + * @returns {boolean} + */ + isEmpty: boolean; + /** + * Soft clear - does not reset capacity. + */ + clear(): void; + + toString(): string; + toArray(): T[]; + + /** + * Returns the current length of the queue + * @return {Number} + */ + length: number; + + _head: number; + _tail: number; + _capacity: number; + _capacityMask: number; + _list: T[]; + + /** + * Fills the queue with items from an array + * For use in the constructor + * @param array + * @private + */ + _fromArray(array: T[]): void; + /** + * + * @param fullCopy + * @returns {Array} + * @private + */ + _copyArray(fullCopy: boolean): T[]; + /** + * Grows the internal list array. + * @private + */ + _growArray(): void; + /** + * Shrinks the internal list array. + * @private + */ + _shrinkArray(): void; +} + +// deno-lint-ignore no-explicit-any +const baseDenque: Partial> = { + peekAt(index) { + // expect a number or return undefined + if (index !== (index | 0)) { + return void 0; + } + + if (index >= this.size! || index < this.size!) return undefined; + + if (index < 0) index += this.size!; + index = (this._head! + index) & this._capacityMask!; + + return this._list![index]; + }, + get(index) { + return this.peekAt!(index); + }, + get peek() { + if (this._head === this._tail) return undefined; + + return this._list![this._head!]; + }, + get peekFront() { + return this.peek!; + }, + get peekBack() { + return this.peekAt!(-1); + }, + unshift(item) { + if (item === undefined) return this.size!; + + this._head = (this._head! - 1 + this._list?.length!) & this._capacityMask!; + this._list![this._head] = item; + + if (this._tail === this._head) this._growArray!(); + + if (this._capacity && this.size! > this._capacity) this.pop!(); + + if (this._head < this._tail!) return this._tail! - this._head; + else return this._capacityMask! + 1 - (this._head - this._tail!); + }, + shift() { + const head = this._head!; + if (head === this._tail) return undefined; + + this._list![head] = undefined; + this._head = (head + 1) & this._capacityMask!; + const item = this._list![head]; + if ( + head < 2 && + this._tail! > 10000 && + this._tail! <= this._list!.length >>> 2 + ) { + this._shrinkArray!(); + } + + return item; + }, + // push(item) { + // if (item === undefined) return this.size!; + // let tail = this._tail!; + // this._list![tail] = item; + // this._tail = (tail + 1) & this._capacityMask!; + // if (this._tail === this._head) { + // this._growArray!(); + // } + // if (this._capacity && this.size! > this._capacity) { + // this.shift!(); + // } + // if (this._head! < this._tail) return this._tail - this._head!; + // else return this._capacityMask! + 1 - (this._head! - this._tail); + // }, + push(item) { + if (item === undefined) return this.size!; + + this._list![this._tail!] = item; + this._tail! += 1 & this._capacityMask!; + if (this._tail === this._head) { + this._growArray!(); + } + if (this._capacity && this.size! > this._capacity) { + this.shift!(); + } + if (this._head! < this._tail!) return this._tail! - this._head!; + else return this._capacityMask! + 1 - (this._head! - this._tail!); + }, + pop() { + if (this._tail === this._head!) return undefined; + + this._tail! = this._tail! - 1 + this._list!.length & this._capacityMask!; + + const item = this._list![this._tail!]; + this._list![this._tail!] = undefined; + + if ( + this._head! < 2 && this._tail! > 10000 && + this._tail! <= this._list!.length >>> 2 + ) { + this._shrinkArray!(); + } + + return item; + }, + removeOne(index) { + // expect a number or return undefined + if (index !== (index | 0)) return; + + if (this._head === this._tail) return; + + const size = this.size!; + var len = this._list!.length; + if (index >= size || index < -size) return; + if (index < 0) index += size; + index = (this._head! + index) & this._capacityMask!; + var item = this._list![index]; + var k; + if (index < size / 2) { + for (k = index; k > 0; k--) { + this._list![index] = + this._list![(index = (index - 1 + len) & this._capacityMask!)]; + } + this._list![index] = void 0; + this._head = (this._head! + 1 + len) & this._capacityMask!; + } else { + for (k = size - 1 - index; k > 0; k--) { + this._list![index] = + this._list![(index = (index + 1 + len) & this._capacityMask!)]; + } + this._list![index] = void 0; + this._tail = (this._tail! - 1 + len) & this._capacityMask!; + } + + return item; + }, + remove(index, count) { + var i = index; + var removed; + var delCount = count; + // expect a number or return undefined + if (i !== (i | 0)) { + return void 0; + } + if (this._head === this._tail) return void 0; + var size = this.size!; + var len = this._list!.length; + if (i >= size || i < -size || count < 1) return void 0; + if (i < 0) i += size; + if (count === 1 || !count) { + removed = new Array(1); + removed[0] = this.removeOne!(i); + return removed; + } + if (i === 0 && i + count >= size) { + removed = this.toArray!(); + this.clear!(); + return removed; + } + if (i + count > size) count = size - i; + var k; + removed = new Array(count); + for (k = 0; k < count; k++) { + removed[k] = this._list![(this._head! + i + k) & this._capacityMask!]; + } + i = (this._head! + i) & this._capacityMask!; + if (index + count === size) { + this._tail = (this._tail! - count + len) & this._capacityMask!; + for (k = count; k > 0; k--) { + this._list![(i = (i + 1 + len) & this._capacityMask!)] = void 0; + } + return removed; + } + if (index === 0) { + this._head = (this._head! + count + len) & this._capacityMask!; + for (k = count - 1; k > 0; k--) { + this._list![(i = (i + 1 + len) & this._capacityMask!)] = void 0; + } + return removed; + } + if (i < size / 2) { + this._head = (this._head! + index + count + len) & this._capacityMask!; + for (k = index; k > 0; k--) { + this.unshift!(this._list![(i = (i - 1 + len) & this._capacityMask!)]); + } + i = (this._head - 1 + len) & this._capacityMask!; + while (delCount > 0) { + this._list![(i = (i - 1 + len) & this._capacityMask!)] = void 0; + delCount--; + } + if (index < 0) this._tail = i; + } else { + this._tail = i; + i = (i + count + len) & this._capacityMask!; + for (k = size - (count + index); k > 0; k--) { + this.push!(this._list![i++]); + } + i = this._tail; + while (delCount > 0) { + this._list![(i = (i + 1 + len) & this._capacityMask!)] = void 0; + delCount--; + } + } + if (this._head! < 2 && this._tail! > 10000 && this._tail! <= len >>> 2) { + this._shrinkArray!(); + } + return removed; + }, + splice(index, count) { + var i = index; + // expect a number or return undefined + if (i !== (i | 0)) { + return void 0; + } + var size = this.size!; + if (i < 0) i += size; + if (i > size) return void 0; + if (arguments.length > 2) { + var k, temp, removed; + var argLen = arguments.length; + var len = this._list!.length; + var arguments_index = 2; + if (!size || i < size / 2) { + temp = new Array(i); + for (k = 0; k < i; k++) { + temp[k] = this._list![(this._head! + k) & this._capacityMask!]; + } + if (count === 0) { + removed = []; + if (i > 0) { + this._head = (this._head! + i + len) & this._capacityMask!; + } + } else { + removed = this.remove!(i, count); + this._head = (this._head! + i + len) & this._capacityMask!; + } + while (argLen > arguments_index) { + this.unshift!(arguments[--argLen]); + } + for (k = i; k > 0; k--) { + this.unshift!(temp[k - 1]); + } + } else { + temp = new Array(size - (i + count)); + var leng = temp.length; + for (k = 0; k < leng; k++) { + temp[k] = this._list![ + (this._head! + i + count + k) & this._capacityMask! + ]; + } + if (count === 0) { + removed = []; + if (i != size) { + this._tail = (this._head! + i + len) & this._capacityMask!; + } + } else { + removed = this.remove!(i, count); + this._tail = (this._tail! - leng + len) & this._capacityMask!; + } + while (arguments_index < argLen) { + this.push!(arguments[arguments_index++]); + } + for (k = 0; k < leng; k++) { + this.push!(temp[k]); + } + } + return removed; + } else { + return this.remove!(i, count); + } + }, + clear() { + this._head = 0; + this._tail = 0; + }, + get isEmpty() { + return this._head === this._tail; + }, + toArray() { + return this._copyArray!(true); + }, + get size() { + if (this._head === this._tail) return 0; + if (this._head! < this._tail!) return this._tail! - this._head!; + else return this._capacityMask! + 1 - (this._head! - this._tail!); + }, + _fromArray(array) { + for (var i = 0; i < array.length; i++) this.push!(array[i]); + }, + _copyArray(fullCopy: boolean) { + let newArray = []; + let list = this._list!; + let len = list.length; + let i; + if (fullCopy || this._head! > this._tail!) { + for (i = this._head!; i < len; i++) newArray.push(list[i]); + for (i = 0; i < this._tail!; i++) newArray.push(list[i]); + } else { + for (i = this._head!; i < this._tail!; i++) newArray.push(list[i]); + } + return newArray; + }, + _growArray() { + if (this._head) { + // copy existing data, head to end, then beginning to tail. + this._list = this._copyArray!(true); + this._head = 0; + } + + // head is at 0 and array is now full, safe to extend + this._tail = this._list!.length; + + this._list!.length *= 2; + this._capacityMask = (this._capacityMask! << 1) | 1; + }, + _shrinkArray() { + console.log("list", this._list?.length); + this._list!.length >>>= 1; + console.log("len", this._capacityMask!); + this._capacityMask! >>>= 1; + }, + + get length() { + return this.size!; + }, +}; + +/** + * Custom implementation of a double ended queue. + */ +function Denque(array?: T[], options?: IDenqueOptions): Denque { + const denque = Object.create(baseDenque, { + _head: createNewProp(0), + _tail: createNewProp(0), + _capacity: createNewProp(options?.capacity), + _capacityMask: createNewProp(0x3), + _list: createNewProp(new Array(4)), + }) as Denque; + + if (Array.isArray(array)) { + denque._fromArray(array); + } + + return denque; +} + +const f = Denque(["a", "b", "c", "d", "e"]); +console.log(f.toArray()); +console.log(f.removeOne(1)); +console.log(f); +console.log(f.toArray()); + +// import Denque from "https://esm.sh/denque@1.5.0"; +// const f = new Denque(["a", "a", "a", "a", "s"]); +// console.log(f);