export interface ServerResponse extends events.EventEmitter, stream.Writable {
        // Extended base methods
        write(buffer: Buffer): boolean;
        write(buffer: Buffer, cb?: Function): boolean;
        write(str: string, cb?: Function): boolean;
        write(str: string, encoding?: string, cb?: Function): boolean;
        write(str: string, encoding?: string, fd?: string): boolean;

        writeContinue(): void;
        writeHead(statusCode: number, reasonPhrase?: string, headers?: any): void;
        writeHead(statusCode: number, headers?: any): void;
        statusCode: number;
        statusMessage: string;
        headersSent: boolean;
        setHeader(name: string, value: string | string[]): void;
        sendDate: boolean;
        getHeader(name: string): string;
        removeHeader(name: string): void;
        write(chunk: any, encoding?: string): any;
        addTrailers(headers: any): void;

        // Extended base methods
        end(): void;
        end(buffer: Buffer, cb?: Function): void;
        end(str: string, cb?: Function): void;
        end(str: string, encoding?: string, cb?: Function): void;
        end(data?: any, encoding?: string): void;
    }


export interface ClientRequest extends events.EventEmitter, stream.Writable {
        // Extended base methods
        write(buffer: Buffer): boolean;
        write(buffer: Buffer, cb?: Function): boolean;
        write(str: string, cb?: Function): boolean;
        write(str: string, encoding?: string, cb?: Function): boolean;
        write(str: string, encoding?: string, fd?: string): boolean;

        write(chunk: any, encoding?: string): void;
        abort(): void;
        setTimeout(timeout: number, callback?: Function): void;
        setNoDelay(noDelay?: boolean): void;
        setSocketKeepAlive(enable?: boolean, initialDelay?: number): void;

        setHeader(name: string, value: string | string[]): void;
        getHeader(name: string): string;
        removeHeader(name: string): void;
        addTrailers(headers: any): void;

        // Extended base methods
        end(): void;
        end(buffer: Buffer, cb?: Function): void;
        end(str: string, cb?: Function): void;
        end(str: string, encoding?: string, cb?: Function): void;
        end(data?: any, encoding?: string): void;
    }


export interface IncomingMessage extends events.EventEmitter, stream.Readable {
        httpVersion: string;
        headers: any;
        rawHeaders: string[];
        trailers: any;
        rawTrailers: any;
        setTimeout(msecs: number, callback: Function): NodeJS.Timer;
        /**
         * Only valid for request obtained from http.Server.
         */
        method?: string;
        /**
         * Only valid for request obtained from http.Server.
         */
        url?: string;
        /**
         * Only valid for response obtained from http.ClientRequest.
         */
        statusCode?: number;
        /**
         * Only valid for response obtained from http.ClientRequest.
         */
        statusMessage?: string;
        socket: net.Socket;
    }


/**
     * @deprecated Use IncomingMessage
     */
export interface ClientResponse extends IncomingMessage { }


export interface AgentOptions {
        /**
         * Keep sockets around in a pool to be used by other requests in the future. Default = false
         */
        keepAlive?: boolean;
        /**
         * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000.
         * Only relevant if keepAlive is set to true.
         */
        keepAliveMsecs?: number;
        /**
         * Maximum number of sockets to allow per host. Default for Node 0.10 is 5, default for Node 0.12 is Infinity
         */
        maxSockets?: number;
        /**
         * Maximum number of sockets to leave open in a free state. Only relevant if keepAlive is set to true. Default = 256.
         */
        maxFreeSockets?: number;
    }


export interface ClusterSettings {
        exec?: string;
        args?: string[];
        silent?: boolean;
    }


export interface Address {
        address: string;
        port: number;
        addressType: string;
    }


export interface ZlibOptions { chunkSize?: number; windowBits?: number; level?: number; memLevel?: number; strategy?: number; dictionary?: any; }


export interface Gzip extends stream.Transform { }


export interface Gunzip extends stream.Transform { }


export interface Deflate extends stream.Transform { }


export interface Inflate extends stream.Transform { }


export interface DeflateRaw extends stream.Transform { }


export interface InflateRaw extends stream.Transform { }


export interface Unzip extends stream.Transform { }


export interface CpuInfo {
        model: string;
        speed: number;
        times: {
            user: number;
            nice: number;
            sys: number;
            idle: number;
            irq: number;
        };
    }


export interface NetworkInterfaceInfo {
        address: string;
        netmask: string;
        family: string;
        mac: string;
        internal: boolean;
    }


export interface ServerOptions {
        pfx?: any;
        key?: any;
        passphrase?: string;
        cert?: any;
        ca?: any;
        crl?: any;
        ciphers?: string;
        honorCipherOrder?: boolean;
        requestCert?: boolean;
        rejectUnauthorized?: boolean;
        NPNProtocols?: any;
        SNICallback?: (servername: string) => any;
    }


export interface RequestOptions extends http.RequestOptions{
        pfx?: any;
        key?: any;
        passphrase?: string;
        cert?: any;
        ca?: any;
        ciphers?: string;
        rejectUnauthorized?: boolean;
        secureProtocol?: string;
    }


export interface Agent {
        maxSockets: number;
        sockets: any;
        requests: any;
    }


export interface Server extends tls.Server { }


interface ucs2 {
        decode(string: string): number[];
        encode(codePoints: number[]): string;
    }


export interface ReplOptions {
        prompt?: string;
        input?: NodeJS.ReadableStream;
        output?: NodeJS.WritableStream;
        terminal?: boolean;
        eval?: Function;
        useColors?: boolean;
        useGlobal?: boolean;
        ignoreUndefined?: boolean;
        writer?: Function;
    }


export interface Key {
        sequence?: string;
        name?: string;
        ctrl?: boolean;
        meta?: boolean;
        shift?: boolean;
    }


export interface ReadLine extends events.EventEmitter {
        setPrompt(prompt: string): void;
        prompt(preserveCursor?: boolean): void;
        question(query: string, callback: (answer: string) => void): void;
        pause(): ReadLine;
        resume(): ReadLine;
        close(): void;
        write(data: string|Buffer, key?: Key): void;
    }


export interface Completer {
        (line: string): CompleterResult;
        (line: string, callback: (err: any, result: CompleterResult) => void): any;
    }


export interface CompleterResult {
        completions: string[];
        line: string;
    }


export interface ReadLineOptions {
        input: NodeJS.ReadableStream;
        output?: NodeJS.WritableStream;
        completer?: Completer;
        terminal?: boolean;
        historySize?: number;
    }


export interface Context { }


export interface ScriptOptions {
        filename?: string;
        lineOffset?: number;
        columnOffset?: number;
        displayErrors?: boolean;
        timeout?: number;
        cachedData?: Buffer;
        produceCachedData?: boolean;
    }


export interface RunningScriptOptions {
        filename?: string;
        lineOffset?: number;
        columnOffset?: number;
        displayErrors?: boolean;
        timeout?: number;
    }


export interface ChildProcess extends events.EventEmitter {
        stdin:  stream.Writable;
        stdout: stream.Readable;
        stderr: stream.Readable;
        stdio: [stream.Writable, stream.Readable, stream.Readable];
        pid: number;
        kill(signal?: string): void;
        send(message: any, sendHandle?: any): void;
        disconnect(): void;
        unref(): void;
    }


export interface SpawnOptions {
        cwd?: string;
        env?: any;
        stdio?: any;
        detached?: boolean;
        uid?: number;
        gid?: number;
        shell?: boolean | string;
    }


export interface ExecOptions {
        cwd?: string;
        env?: any;
        shell?: string;
        timeout?: number;
        maxBuffer?: number;
        killSignal?: string;
        uid?: number;
        gid?: number;
    }


export interface ExecOptionsWithStringEncoding extends ExecOptions {
        encoding: BufferEncoding;
    }


export interface ExecOptionsWithBufferEncoding extends ExecOptions {
        encoding: string; // specify `null`.
    }


export interface ExecFileOptions {
        cwd?: string;
        env?: any;
        timeout?: number;
        maxBuffer?: number;
        killSignal?: string;
        uid?: number;
        gid?: number;
    }


export interface ExecFileOptionsWithStringEncoding extends ExecFileOptions {
        encoding: BufferEncoding;
    }


export interface ExecFileOptionsWithBufferEncoding extends ExecFileOptions {
        encoding: string; // specify `null`.
    }


export interface ForkOptions {
        cwd?: string;
        env?: any;
        execPath?: string;
        execArgv?: string[];
        silent?: boolean;
        uid?: number;
        gid?: number;
    }


export interface SpawnSyncOptions {
        cwd?: string;
        input?: string | Buffer;
        stdio?: any;
        env?: any;
        uid?: number;
        gid?: number;
        timeout?: number;
        killSignal?: string;
        maxBuffer?: number;
        encoding?: string;
        shell?: boolean | string;
    }


export interface SpawnSyncOptionsWithStringEncoding extends SpawnSyncOptions {
        encoding: BufferEncoding;
    }


export interface SpawnSyncOptionsWithBufferEncoding extends SpawnSyncOptions {
        encoding: string; // specify `null`.
    }


export interface SpawnSyncReturns<T> {
        pid: number;
        output: string[];
        stdout: T;
        stderr: T;
        status: number;
        signal: string;
        error: Error;
    }


export interface ExecSyncOptions {
        cwd?: string;
        input?: string | Buffer;
        stdio?: any;
        env?: any;
        shell?: string;
        uid?: number;
        gid?: number;
        timeout?: number;
        killSignal?: string;
        maxBuffer?: number;
        encoding?: string;
    }


export interface ExecSyncOptionsWithStringEncoding extends ExecSyncOptions {
        encoding: BufferEncoding;
    }


export interface ExecSyncOptionsWithBufferEncoding extends ExecSyncOptions {
        encoding: string; // specify `null`.
    }


export interface ExecFileSyncOptions {
        cwd?: string;
        input?: string | Buffer;
        stdio?: any;
        env?: any;
        uid?: number;
        gid?: number;
        timeout?: number;
        killSignal?: string;
        maxBuffer?: number;
        encoding?: string;
    }


export interface ExecFileSyncOptionsWithStringEncoding extends ExecFileSyncOptions {
        encoding: BufferEncoding;
    }


export interface ExecFileSyncOptionsWithBufferEncoding extends ExecFileSyncOptions {
        encoding: string; // specify `null`.
    }


export interface Url {
        href?: string;
        protocol?: string;
        auth?: string;
        hostname?: string;
        port?: string;
        host?: string;
        pathname?: string;
        search?: string;
        query?: any; // string | Object
        slashes?: boolean;
        hash?: string;
        path?: string;
    }


export interface Socket extends stream.Duplex {
        // Extended base methods
        write(buffer: Buffer): boolean;
        write(buffer: Buffer, cb?: Function): boolean;
        write(str: string, cb?: Function): boolean;
        write(str: string, encoding?: string, cb?: Function): boolean;
        write(str: string, encoding?: string, fd?: string): boolean;

        connect(port: number, host?: string, connectionListener?: Function): void;
        connect(path: string, connectionListener?: Function): void;
        bufferSize: number;
        setEncoding(encoding?: string): void;
        write(data: any, encoding?: string, callback?: Function): void;
        destroy(): void;
        pause(): void;
        resume(): void;
        setTimeout(timeout: number, callback?: Function): void;
        setNoDelay(noDelay?: boolean): void;
        setKeepAlive(enable?: boolean, initialDelay?: number): void;
        address(): { port: number; family: string; address: string; };
        unref(): void;
        ref(): void;

        remoteAddress: string;
        remoteFamily: string;
        remotePort: number;
        localAddress: string;
        localPort: number;
        bytesRead: number;
        bytesWritten: number;

        // Extended base methods
        end(): void;
        end(buffer: Buffer, cb?: Function): void;
        end(str: string, cb?: Function): void;
        end(str: string, encoding?: string, cb?: Function): void;
        end(data?: any, encoding?: string): void;
    }


export interface ListenOptions {
        port?: number;
        host?: string;
        backlog?: number;
        path?: string;
        exclusive?: boolean;
    }


export interface Server extends Socket {
        listen(port: number, hostname?: string, backlog?: number, listeningListener?: Function): Server;
        listen(port: number, hostname?: string, listeningListener?: Function): Server;
        listen(port: number, backlog?: number, listeningListener?: Function): Server;
        listen(port: number, listeningListener?: Function): Server;
        listen(path: string, backlog?: number, listeningListener?: Function): Server;
        listen(path: string, listeningListener?: Function): Server;
        listen(handle: any, backlog?: number, listeningListener?: Function): Server;
        listen(handle: any, listeningListener?: Function): Server;
        listen(options: ListenOptions, listeningListener?: Function): Server;
        close(callback?: Function): Server;
        address(): { port: number; family: string; address: string; };
        getConnections(cb: (error: Error, count: number) => void): void;
        ref(): Server;
        unref(): Server;
        maxConnections: number;
        connections: number;
    }


interface RemoteInfo {
        address: string;
        port: number;
        size: number;
    }


interface AddressInfo {
        address: string;
        family: string;
        port: number;
    }


interface Socket extends events.EventEmitter {
        send(buf: Buffer, offset: number, length: number, port: number, address: string, callback?: (error: Error, bytes: number) => void): void;
        bind(port: number, address?: string, callback?: () => void): void;
        close(): void;
        address(): AddressInfo;
        setBroadcast(flag: boolean): void;
        setMulticastTTL(ttl: number): void;
        setMulticastLoopback(flag: boolean): void;
        addMembership(multicastAddress: string, multicastInterface?: string): void;
        dropMembership(multicastAddress: string, multicastInterface?: string): void;
    }


interface Stats {
        isFile(): boolean;
        isDirectory(): boolean;
        isBlockDevice(): boolean;
        isCharacterDevice(): boolean;
        isSymbolicLink(): boolean;
        isFIFO(): boolean;
        isSocket(): boolean;
        dev: number;
        ino: number;
        mode: number;
        nlink: number;
        uid: number;
        gid: number;
        rdev: number;
        size: number;
        blksize: number;
        blocks: number;
        atime: Date;
        mtime: Date;
        ctime: Date;
        birthtime: Date;
    }


interface FSWatcher extends events.EventEmitter {
        close(): void;
    }


export interface ReadStream extends stream.Readable {
        close(): void;
    }


export interface WriteStream extends stream.Writable {
        close(): void;
        bytesWritten: number;
    }


/**
     * A parsed path object generated by path.parse() or consumed by path.format().
     */
export interface ParsedPath {
        /**
         * The root of the path such as '/' or 'c:\'
         */
        root: string;
        /**
         * The full directory path such as '/home/user/dir' or 'c:\path\dir'
         */
        dir: string;
        /**
         * The file name including extension (if any) such as 'index.html'
         */
        base: string;
        /**
         * The file extension (if any) such as '.html'
         */
        ext: string;
        /**
         * The file name without extension (if any) such as 'index'
         */
        name: string;
    }


export interface NodeStringDecoder {
        write(buffer: Buffer): string;
        detectIncompleteChar(buffer: Buffer): number;
    }


export interface TlsOptions {
        host?: string;
        port?: number;
        pfx?: any;   //string or buffer
        key?: any;   //string or buffer
        passphrase?: string;
        cert?: any;
        ca?: any;    //string or buffer
        crl?: any;   //string or string array
        ciphers?: string;
        honorCipherOrder?: any;
        requestCert?: boolean;
        rejectUnauthorized?: boolean;
        NPNProtocols?: any;  //array or Buffer;
        SNICallback?: (servername: string) => any;
    }


export interface ConnectionOptions {
        host?: string;
        port?: number;
        socket?: net.Socket;
        pfx?: any;   //string | Buffer
        key?: any;   //string | Buffer
        passphrase?: string;
        cert?: any;  //string | Buffer
        ca?: any;    //Array of string | Buffer
        rejectUnauthorized?: boolean;
        NPNProtocols?: any;  //Array of string | Buffer
        servername?: string;
    }


export interface Server extends net.Server {
        close(): Server;
        address(): { port: number; family: string; address: string; };
        addContext(hostName: string, credentials: {
            key: string;
            cert: string;
            ca: string;
        }): void;
        maxConnections: number;
        connections: number;
    }


export interface ClearTextStream extends stream.Duplex {
        authorized: boolean;
        authorizationError: Error;
        getPeerCertificate(): any;
        getCipher: {
            name: string;
            version: string;
        };
        address: {
            port: number;
            family: string;
            address: string;
        };
        remoteAddress: string;
        remotePort: number;
    }


export interface SecurePair {
        encrypted: any;
        cleartext: any;
    }


export interface SecureContextOptions {
        pfx?: any;   //string | buffer
        key?: any;   //string | buffer
        passphrase?: string;
        cert?: any;  // string | buffer
        ca?: any;    // string | buffer
        crl?: any;   // string | string[]
        ciphers?: string;
        honorCipherOrder?: boolean;
    }


export interface SecureContext {
        context: any;
    }


export interface CredentialDetails {
        pfx: string;
        key: string;
        passphrase: string;
        cert: string;
        ca: any;    //string | string array
        crl: any;   //string | string array
        ciphers: string;
    }


export interface Credentials { context?: any; }


export interface Hash {
        update(data: any, input_encoding?: string): Hash;
        digest(encoding: 'buffer'): Buffer;
        digest(encoding: string): any;
        digest(): Buffer;
    }


export interface Hmac extends NodeJS.ReadWriteStream {
        update(data: any, input_encoding?: string): Hmac;
        digest(encoding: 'buffer'): Buffer;
        digest(encoding: string): any;
        digest(): Buffer;
    }


export interface Cipher extends NodeJS.ReadWriteStream {
        update(data: Buffer): Buffer;
        update(data: string, input_encoding: "utf8"|"ascii"|"binary"): Buffer;
        update(data: Buffer, input_encoding: any, output_encoding: "binary"|"base64"|"hex"): string;
        update(data: string, input_encoding: "utf8"|"ascii"|"binary", output_encoding: "binary"|"base64"|"hex"): string;
        final(): Buffer;
        final(output_encoding: string): string;
        setAutoPadding(auto_padding: boolean): void;
        getAuthTag(): Buffer;
    }


export interface Decipher extends NodeJS.ReadWriteStream {
        update(data: Buffer): Buffer;
        update(data: string, input_encoding: "binary"|"base64"|"hex"): Buffer;
        update(data: Buffer, input_encoding: any, output_encoding: "utf8"|"ascii"|"binary"): string;
        update(data: string, input_encoding: "binary"|"base64"|"hex", output_encoding: "utf8"|"ascii"|"binary"): string;
        final(): Buffer;
        final(output_encoding: string): string;
        setAutoPadding(auto_padding: boolean): void;
        setAuthTag(tag: Buffer): void;
    }


export interface Signer extends NodeJS.WritableStream {
        update(data: any): void;
        sign(private_key: string, output_format: string): string;
    }


export interface Verify extends NodeJS.WritableStream {
        update(data: any): void;
        verify(object: string, signature: string, signature_format?: string): boolean;
    }


export interface DiffieHellman {
        generateKeys(encoding?: string): string;
        computeSecret(other_public_key: string, input_encoding?: string, output_encoding?: string): string;
        getPrime(encoding?: string): string;
        getGenerator(encoding: string): string;
        getPublicKey(encoding?: string): string;
        getPrivateKey(encoding?: string): string;
        setPublicKey(public_key: string, encoding?: string): void;
        setPrivateKey(public_key: string, encoding?: string): void;
    }


export interface RsaPublicKey {
        key: string;
        padding?: any;
    }


export interface RsaPrivateKey {
        key: string;
        passphrase?: string,
        padding?: any;
    }


export interface ReadableOptions {
        highWaterMark?: number;
        encoding?: string;
        objectMode?: boolean;
    }


export interface WritableOptions {
        highWaterMark?: number;
        decodeStrings?: boolean;
        objectMode?: boolean;
    }


export interface DuplexOptions extends ReadableOptions, WritableOptions {
        allowHalfOpen?: boolean;
    }


export interface TransformOptions extends ReadableOptions, WritableOptions {}


export interface InspectOptions {
        showHidden?: boolean;
        depth?: number;
        colors?: boolean;
        customInspect?: boolean;
    }


export interface ReadStream extends net.Socket {
        isRaw: boolean;
        setRawMode(mode: boolean): void;
        isTTY: boolean;
    }


export interface WriteStream extends net.Socket {
        columns: number;
        rows: number;
        isTTY: boolean;
    }


// Buffer class
type BufferEncoding = "ascii" | "utf8" | "utf16le" | "ucs2" | "binary" | "hex";


static listenerCount(emitter: EventEmitter, event: string): number;


addListener(event: string, listener: Function): this;


on(event: string, listener: Function): this;


once(event: string, listener: Function): this;


removeListener(event: string, listener: Function): this;


removeAllListeners(event?: string): this;


setMaxListeners(n: number): this;


getMaxListeners(): number;


listeners(event: string): Function[];


emit(event: string, ...args: any[]): boolean;


listenerCount(type: string): number;


/**
         * Destroy any sockets that are currently in use by the agent.
         * It is usually not necessary to do this. However, if you are using an agent with KeepAlive enabled,
         * then it is best to explicitly shut down the agent when you know that it will no longer be used. Otherwise,
         * sockets may hang open for quite a long time before the server terminates them.
         */
destroy(): void;


send(message: any, sendHandle?: any): void;


kill(signal?: string): void;


destroy(signal?: string): void;


disconnect(): void;


isConnected(): boolean;


isDead(): boolean;


runInContext(contextifiedSandbox: Context, options?: RunningScriptOptions): any;


runInNewContext(sandbox?: Context, options?: RunningScriptOptions): any;


runInThisContext(options?: RunningScriptOptions): any;


pipe<T extends NodeJS.WritableStream>(destination: T, options?: { end?: boolean; }): T;


_read(size: number): void;


read(size?: number): any;


setEncoding(encoding: string): void;


pause(): void;


resume(): void;


unpipe<T extends NodeJS.WritableStream>(destination?: T): void;


unshift(chunk: any): void;


wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream;


push(chunk: any, encoding?: string): boolean;


_write(chunk: any, encoding: string, callback: Function): void;


write(chunk: any, cb?: Function): boolean;


write(chunk: any, encoding?: string, cb?: Function): boolean;


end(): void;


end(chunk: any, cb?: Function): void;


end(chunk: any, encoding?: string, cb?: Function): void;


_transform(chunk: any, encoding: string, callback: Function): void;


_flush(callback: Function): void;


run(fn: Function): void;


add(emitter: events.EventEmitter): void;


remove(emitter: events.EventEmitter): void;


bind(cb: (err: Error, data: any) => any): any;


intercept(cb: (data: any) => any): any;


export function debounce(func: Function, wait: number) {
    let timer: NodeJS.Timeout;
    return function (...args: any[]) {
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            func.apply(undefined, args);
        }, wait);
    }
}


() => {
            func.apply(undefined, args);
        }


async (
  connection: Connection,
  tempCache: MetaState,
) => {
  const updateTemp = makeSetter(tempCache);

  const loadMetadata = () =>
    pullMetadataByCreators(connection, tempCache, updateTemp);
  const loadEditions = () =>
    pullEditions(connection, updateTemp, tempCache, tempCache.metadata);

  console.log('-------->Loading all metadata for store.');

  await loadMetadata();
  await loadEditions();

  await postProcessMetadata(tempCache);
  console.log('-------->Metadata processing complete.');
  return tempCache;
}


() =>
    pullMetadataByCreators(connection, tempCache, updateTemp)


() =>
    pullEditions(connection, updateTemp, tempCache, tempCache.metadata)


async (
  connection: Connection,
  userTokenAccounts: TokenAccount[],
  tempCache: MetaState,
) => {
  const updateTemp = makeSetter(tempCache);

  console.log('--------->Pulling metadata for user.');
  let currBatch: string[] = [];
  let batches = [];
  const editions = [];
  for (let i = 0; i < userTokenAccounts.length; i++) {
    if (userTokenAccounts[i].info.amount.toNumber() == 1) {
      if (2 + currBatch.length > MULTIPLE_ACCOUNT_BATCH_SIZE) {
        batches.push(currBatch);
        currBatch = [];
      } else {
        const edition = await getEdition(
          userTokenAccounts[i].info.mint.toBase58(),
        );
        const newAdd = [
          await getMetadata(userTokenAccounts[i].info.mint.toBase58()),
          edition,
        ];
        editions.push(edition);
        currBatch = currBatch.concat(newAdd);
      }
    }
  }

  if (currBatch.length > 0 && currBatch.length <= MULTIPLE_ACCOUNT_BATCH_SIZE) {
    batches.push(currBatch);
  }

  console.log(
    '------> From token accounts for user',
    'produced',
    batches.length,
    'batches of accounts to pull',
  );
  for (let i = 0; i < batches.length; i++) {
    const accounts = await getMultipleAccounts(
      connection,
      batches[i],
      'single',
    );
    if (accounts) {
      console.log(
        '------->Pulled batch',
        i,
        'with',
        batches[i].length,
        'accounts, processing....',
      );
      for (let j = 0; j < accounts.keys.length; j++) {
        const pubkey = accounts.keys[j];
        await processMetaData(
          {
            pubkey,
            account: accounts.array[j],
          },
          updateTemp,
        );
      }
    } else {
      console.log('------->Failed to pull batch', i, 'skipping');
    }
  }

  console.log('------> Pulling master editions for user');
  currBatch = [];
  batches = [];
  for (let i = 0; i < editions.length; i++) {
    if (1 + currBatch.length > MULTIPLE_ACCOUNT_BATCH_SIZE) {
      batches.push(currBatch);
      currBatch = [];
    } else if (tempCache.editions[editions[i]]) {
      currBatch.push(tempCache.editions[editions[i]].info.parent);
    }
  }

  if (currBatch.length > 0 && currBatch.length <= MULTIPLE_ACCOUNT_BATCH_SIZE) {
    batches.push(currBatch);
  }

  console.log(
    '------> From token accounts for user',
    'produced',
    batches.length,
    'batches of accounts to pull',
  );
  for (let i = 0; i < batches.length; i++) {
    const accounts = await getMultipleAccounts(
      connection,
      batches[i],
      'single',
    );
    if (accounts) {
      console.log(
        '------->Pulled batch',
        i,
        'with',
        batches[i].length,
        'accounts, processing....',
      );
      for (let j = 0; j < accounts.keys.length; j++) {
        const pubkey = accounts.keys[j];
        await processMetaData(
          {
            pubkey,
            account: accounts.array[j],
          },
          updateTemp,
        );
      }
    } else {
      console.log('------->Failed to pull batch', i, 'skipping');
    }
  }

  await postProcessMetadata(tempCache);

  console.log('-------->User metadata processing complete.');

  return tempCache;
}


async (
  connection: Connection,
  tempCache: MetaState,
) => {
  const updateTemp = makeSetter(tempCache);

  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateTemp);
      }
    };
  getProgramAccounts(connection, METAPLEX_ID, {
    filters: [
      {
        dataSize: MAX_PAYOUT_TICKET_SIZE,
      },
    ],
  }).then(forEach(processMetaplexAccounts));

  return tempCache;
}


(fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateTemp);
      }
    }


async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateTemp);
      }
    }


async (
  connection: Connection,
  state: MetaState,
  walletKey?: PublicKey | null,
): Promise<MetaState> => {
  const updateTemp = makeSetter(state);
  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts.flat()) {
        await fn(account, updateTemp);
      }
    };

  const store = programIds().store;
  if (store) {
    await getPackSets({ connection, storeId: store }).then(
      forEach(processPackSets),
    );
  }

  // Fetch packs' cards
  const fetchCardsPromises = Object.keys(state.packs).map(packSetKey =>
    getCardsByPackSet({ connection, packSetKey }),
  );
  await Promise.all(fetchCardsPromises).then(cards =>
    cards.forEach(forEach(processPackCards)),
  );

  const packKeys = Object.keys(state.packs);
  // Fetch vouchers
  const fetchVouchersPromises = packKeys.map(packSetKey =>
    getVouchersByPackSet({
      connection,
      packSetKey,
    }),
  );
  await Promise.all(fetchVouchersPromises).then(vouchers =>
    vouchers.forEach(forEach(processPackVouchers)),
  );

  // Fetch proving process if user connected wallet
  if (walletKey) {
    const fetchProvingProcessPromises = packKeys.map(packSetKey =>
      getProvingProcessByPackSetAndWallet({
        connection,
        packSetKey,
        walletKey,
      }),
    );
    await Promise.all(fetchProvingProcessPromises).then(provingProcess =>
      provingProcess.forEach(forEach(processProvingProcess)),
    );
  }

  const metadataKeys = Object.values(state.packCards).map(
    ({ info }) => info.metadata,
  );
  const newState = await pullMetadataByKeys(connection, state, metadataKeys);

  await pullEditions(
    connection,
    updateTemp,
    newState,
    metadataKeys.map(m => newState.metadataByMetadata[m]),
  );

  return newState;
}


(fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts.flat()) {
        await fn(account, updateTemp);
      }
    }


async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts.flat()) {
        await fn(account, updateTemp);
      }
    }


packSetKey =>
    getCardsByPackSet({ connection, packSetKey })


cards =>
    cards.forEach(forEach(processPackCards))


packSetKey =>
    getVouchersByPackSet({
      connection,
      packSetKey,
    })


vouchers =>
    vouchers.forEach(forEach(processPackVouchers))


packSetKey =>
      getProvingProcessByPackSetAndWallet({
        connection,
        packSetKey,
        walletKey,
      })


provingProcess =>
      provingProcess.forEach(forEach(processProvingProcess))


({ info }) => info.metadata


m => newState.metadataByMetadata[m]


async ({
  connection,
  state,
  packSetKey,
  walletKey,
}: {
  connection: Connection;
  state: MetaState;
  packSetKey: StringPublicKey;
  walletKey: PublicKey | null;
}): Promise<MetaState> => {
  const updateTemp = makeSetter(state);

  const packSet = await getPackSetByPubkey(connection, packSetKey);
  processPackSets(packSet, updateTemp);

  const packCards = await getCardsByPackSet({
    connection,
    packSetKey,
  });
  packCards.forEach(card => processPackCards(card, updateTemp));

  if (walletKey) {
    const provingProcess = await getProvingProcessByPackSetAndWallet({
      connection,
      packSetKey,
      walletKey,
    });
    provingProcess.forEach(process =>
      processProvingProcess(process, updateTemp),
    );
  }

  const metadataKeys = Object.values(state.packCardsByPackSet[packSetKey]).map(
    ({ info }) => info.metadata,
  );
  const newState = await pullMetadataByKeys(connection, state, metadataKeys);

  await pullEditions(
    connection,
    updateTemp,
    newState,
    metadataKeys.map(m => newState.metadataByMetadata[m]),
  );

  return state;
}


card => processPackCards(card, updateTemp)


process =>
      processProvingProcess(process, updateTemp)


async (
  connection: Connection,
  auction: StringPublicKey,
  tempCache: MetaState,
) => {
  const updateTemp = makeSetter(tempCache);
  let cacheKey;
  try {
    cacheKey = await getAuctionCache(auction);
  } catch (e) {
    console.log(e);
    console.log('Failed to get auction cache key');
    return tempCache;
  }
  const cache = tempCache.auctionCaches[cacheKey]?.info;
  if (!cache) {
    console.log('-----> No auction cache exists for', auction, 'returning');
    return tempCache;
  }
  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateTemp);
      }
    };
  const auctionExtKey = await getAuctionExtended({
    auctionProgramId: AUCTION_ID,
    resource: cache.vault,
  });
  const promises = [
    // pull editions
    pullEditions(
      connection,
      updateTemp,
      tempCache,
      cache.metadata.map(m => tempCache.metadataByMetadata[m]),
    ),
    // pull auction data ext
    connection
      .getAccountInfo(toPublicKey(auctionExtKey))
      .then(a =>
        a
          ? processAuctions({ pubkey: auctionExtKey, account: a }, updateTemp)
          : null,
      ),
    // bidder metadata pull
    getProgramAccounts(connection, AUCTION_ID, {
      filters: [
        {
          memcmp: {
            offset: 32,
            bytes: auction,
          },
        },
      ],
    }).then(forEach(processAuctions)),

    // bidder pot pull
    getProgramAccounts(connection, AUCTION_ID, {
      filters: [
        {
          memcmp: {
            offset: 64,
            bytes: auction,
          },
        },
      ],
    }).then(forEach(processAuctions)),
    // safety deposit pull
    getProgramAccounts(connection, VAULT_ID, {
      filters: [
        {
          memcmp: {
            offset: 1,
            bytes: cache.vault,
          },
        },
      ],
    }).then(forEach(processVaultData)),

    // bid redemptions
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          memcmp: {
            offset: 10,
            bytes: cache.auctionManager,
          },
        },
      ],
    }).then(forEach(processMetaplexAccounts)),
    // bdis where you arent winner
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          memcmp: {
            offset: 2,
            bytes: cache.auctionManager,
          },
        },
      ],
    }).then(forEach(processMetaplexAccounts)),
    // safety deposit configs
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          memcmp: {
            offset: 1,
            bytes: cache.auctionManager,
          },
        },
      ],
    }).then(forEach(processMetaplexAccounts)),
    // prize tracking tickets
    ...cache.metadata
      .map(md =>
        getProgramAccounts(connection, METAPLEX_ID, {
          filters: [
            {
              memcmp: {
                offset: 1,
                bytes: md,
              },
            },
          ],
        }).then(forEach(processMetaplexAccounts)),
      )
      .flat(),
  ];
  await Promise.all(promises);
  console.log('---------->Pulled sub accounts for auction', auction);

  return tempCache;
}


m => tempCache.metadataByMetadata[m]


a =>
        a
          ? processAuctions({ pubkey: auctionExtKey, account: a }, updateTemp)
          : null


md =>
        getProgramAccounts(connection, METAPLEX_ID, {
          filters: [
            {
              memcmp: {
                offset: 1,
                bytes: md,
              },
            },
          ],
        }).then(forEach(processMetaplexAccounts))


async (
  connection: Connection,
): Promise<ParsedAccount<StoreIndexer>[]> => {
  let i = 0;

  let pageKey = await getStoreIndexer(i);
  let account = await connection.getAccountInfo(new PublicKey(pageKey));
  const pages: ParsedAccount<StoreIndexer>[] = [];
  while (account) {
    pages.push({
      info: decodeStoreIndexer(account.data),
      pubkey: pageKey,
      account,
    });
    i++;

    pageKey = await getStoreIndexer(i);
    account = await connection.getAccountInfo(new PublicKey(pageKey));
  }
  return pages;
}


async (
  connection: Connection,
  page: number,
  tempCache: MetaState,
  walletKey?: PublicKey | null,
  shouldFetchNftPacks?: boolean,
) => {
  const updateTemp = makeSetter(tempCache);
  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateTemp);
      }
    };
  const pageKey = await getStoreIndexer(page);
  const account = await connection.getAccountInfo(new PublicKey(pageKey));

  if (account) {
    processMetaplexAccounts(
      {
        pubkey: pageKey,
        account,
      },
      updateTemp,
    );

    const newPage = tempCache.storeIndexer.find(s => s.pubkey == pageKey);

    const auctionCaches = await getMultipleAccounts(
      connection,
      newPage?.info.auctionCaches || [],
      'single',
    );

    if (auctionCaches && auctionCaches.keys.length) {
      console.log(
        '-------->Page ',
        page,
        ' found',
        auctionCaches.keys.length,
        ' cached auction data',
      );
      auctionCaches.keys.map((pubkey, i) => {
        processMetaplexAccounts(
          {
            pubkey,
            account: auctionCaches.array[i],
          },
          updateTemp,
        );
      });

      const batches: Array<StringPublicKey[]> = [];

      let currBatch: StringPublicKey[] = [];
      for (let i = 0; i < auctionCaches.keys.length; i++) {
        const cache = tempCache.auctionCaches[auctionCaches.keys[i]];

        const totalNewAccountsToAdd = cache.info.metadata.length + 3;

        if (
          totalNewAccountsToAdd + currBatch.length >
          MULTIPLE_ACCOUNT_BATCH_SIZE
        ) {
          batches.push(currBatch);
          currBatch = [];
        } else {
          const newAdd = [
            ...cache.info.metadata,
            cache.info.auction,
            cache.info.auctionManager,
            cache.info.vault,
          ];
          currBatch = currBatch.concat(newAdd);
        }
      }

      if (
        currBatch.length > 0 &&
        currBatch.length <= MULTIPLE_ACCOUNT_BATCH_SIZE
      ) {
        batches.push(currBatch);
      }

      console.log(
        '------> From account caches for page',
        page,
        'produced',
        batches.length,
        'batches of accounts to pull',
      );
      for (let i = 0; i < batches.length; i++) {
        const accounts = await getMultipleAccounts(
          connection,
          batches[i],
          'single',
        );
        if (accounts) {
          console.log(
            '------->Pulled batch',
            i,
            'with',
            batches[i].length,
            'accounts, processing....',
          );
          for (let i = 0; i < accounts.keys.length; i++) {
            const pubkey = accounts.keys[i];
            await processMetaplexAccounts(
              {
                pubkey,
                account: accounts.array[i],
              },
              updateTemp,
            );
            await processVaultData(
              {
                pubkey,
                account: accounts.array[i],
              },
              updateTemp,
            );
            await processMetaData(
              {
                pubkey,
                account: accounts.array[i],
              },
              updateTemp,
            );
            await processAuctions(
              {
                pubkey,
                account: accounts.array[i],
              },
              updateTemp,
            );
          }
        } else {
          console.log('------->Failed to pull batch', i, 'skipping');
        }
      }

      for (let i = 0; i < auctionCaches.keys.length; i++) {
        const auctionCache = tempCache.auctionCaches[auctionCaches.keys[i]];

        const metadata = auctionCache.info.metadata.map(
          s => tempCache.metadataByMetadata[s],
        );
        tempCache.metadataByAuction[auctionCache.info.auction] = metadata;
      }
    }

    if (shouldFetchNftPacks) {
      await pullPacks(connection, tempCache, walletKey);
    }

    if (page == 0) {
      console.log('-------->Page 0, pulling creators and store');
      await getProgramAccounts(connection, METAPLEX_ID, {
        filters: [
          {
            dataSize: MAX_WHITELISTED_CREATOR_SIZE,
          },
        ],
      }).then(forEach(processMetaplexAccounts));

      const store = programIds().store;
      if (store) {
        const storeAcc = await connection.getAccountInfo(store);
        if (storeAcc) {
          await processMetaplexAccounts(
            { pubkey: store.toBase58(), account: storeAcc },
            updateTemp,
          );
        }
      }
    }

    await postProcessMetadata(tempCache);
  }

  return tempCache;
}


s => s.pubkey == pageKey


(pubkey, i) => {
        processMetaplexAccounts(
          {
            pubkey,
            account: auctionCaches.array[i],
          },
          updateTemp,
        );
      }


s => tempCache.metadataByMetadata[s]


async (connection: Connection) => {
  const tempCache: MetaState = getEmptyMetaState();
  const updateTemp = makeSetter(tempCache);

  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateTemp);
      }
    };

  const pullMetadata = async (metadata: string) => {
    const mdKey = new PublicKey(metadata);
    const md = await connection.getAccountInfo(mdKey);
    const mdObject = decodeMetadata(
      Buffer.from(md?.data || new Uint8Array([])),
    );
    const editionKey = await getEdition(mdObject.mint);
    const editionData = await connection.getAccountInfo(
      new PublicKey(editionKey),
    );
    if (md) {
      //@ts-ignore
      md.owner = md.owner.toBase58();
      processMetaData(
        {
          pubkey: metadata,
          account: md,
        },
        updateTemp,
      );
      if (editionData) {
        //@ts-ignore
        editionData.owner = editionData.owner.toBase58();
        processMetaData(
          {
            pubkey: editionKey,
            account: editionData,
          },
          updateTemp,
        );
      }
    }
  };

  const pullAuction = async (auction: string) => {
    const auctionExtendedKey = await getAuctionExtended({
      auctionProgramId: AUCTION_ID,
      resource: AUCTION_TO_VAULT[auction],
    });

    const auctionData = await getMultipleAccounts(
      connection,
      [auction, auctionExtendedKey],
      'single',
    );

    if (auctionData) {
      auctionData.keys.map((pubkey, i) => {
        processAuctions(
          {
            pubkey,
            account: auctionData.array[i],
          },
          updateTemp,
        );
      });
    }
  };

  const pullAuctionManager = async (auctionManager: string) => {
    const auctionManagerKey = new PublicKey(auctionManager);
    const auctionManagerData = await connection.getAccountInfo(
      auctionManagerKey,
    );
    if (auctionManagerData) {
      //@ts-ignore
      auctionManagerData.owner = auctionManagerData.owner.toBase58();
      processMetaplexAccounts(
        {
          pubkey: auctionManager,
          account: auctionManagerData,
        },
        updateTemp,
      );
    }
  };

  const pullVault = async (vault: string) => {
    const vaultKey = new PublicKey(vault);
    const vaultData = await connection.getAccountInfo(vaultKey);
    if (vaultData) {
      //@ts-ignore
      vaultData.owner = vaultData.owner.toBase58();
      processVaultData(
        {
          pubkey: vault,
          account: vaultData,
        },
        updateTemp,
      );
    }
  };

  const promises = [
    ...WHITELISTED_METADATA.map(md => pullMetadata(md)),
    ...WHITELISTED_AUCTION.map(a => pullAuction(a)),
    ...WHITELISTED_AUCTION_MANAGER.map(a => pullAuctionManager(a)),
    ...WHITELISTED_VAULT.map(a => pullVault(a)),
    // bidder metadata pull
    ...WHITELISTED_AUCTION.map(a =>
      getProgramAccounts(connection, AUCTION_ID, {
        filters: [
          {
            memcmp: {
              offset: 32,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processAuctions)),
    ),
    // bidder pot pull
    ...WHITELISTED_AUCTION.map(a =>
      getProgramAccounts(connection, AUCTION_ID, {
        filters: [
          {
            memcmp: {
              offset: 64,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processAuctions)),
    ),
    // safety deposit pull
    ...WHITELISTED_VAULT.map(v =>
      getProgramAccounts(connection, VAULT_ID, {
        filters: [
          {
            memcmp: {
              offset: 1,
              bytes: v,
            },
          },
        ],
      }).then(forEach(processVaultData)),
    ),
    // bid redemptions
    ...WHITELISTED_AUCTION_MANAGER.map(a =>
      getProgramAccounts(connection, METAPLEX_ID, {
        filters: [
          {
            memcmp: {
              offset: 9,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processMetaplexAccounts)),
    ),
    // safety deposit configs
    ...WHITELISTED_AUCTION_MANAGER.map(a =>
      getProgramAccounts(connection, METAPLEX_ID, {
        filters: [
          {
            memcmp: {
              offset: 1,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processMetaplexAccounts)),
    ),
    // prize tracking tickets
    ...Object.keys(AUCTION_TO_METADATA)
      .map(key =>
        AUCTION_TO_METADATA[key]
          .map(md =>
            getProgramAccounts(connection, METAPLEX_ID, {
              filters: [
                {
                  memcmp: {
                    offset: 1,
                    bytes: md,
                  },
                },
              ],
            }).then(forEach(processMetaplexAccounts)),
          )
          .flat(),
      )
      .flat(),
    // whitelisted creators
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          dataSize: MAX_WHITELISTED_CREATOR_SIZE,
        },
      ],
    }).then(forEach(processMetaplexAccounts)),
  ];

  await Promise.all(promises);

  await postProcessMetadata(tempCache);

  return tempCache;
}


async (metadata: string) => {
    const mdKey = new PublicKey(metadata);
    const md = await connection.getAccountInfo(mdKey);
    const mdObject = decodeMetadata(
      Buffer.from(md?.data || new Uint8Array([])),
    );
    const editionKey = await getEdition(mdObject.mint);
    const editionData = await connection.getAccountInfo(
      new PublicKey(editionKey),
    );
    if (md) {
      //@ts-ignore
      md.owner = md.owner.toBase58();
      processMetaData(
        {
          pubkey: metadata,
          account: md,
        },
        updateTemp,
      );
      if (editionData) {
        //@ts-ignore
        editionData.owner = editionData.owner.toBase58();
        processMetaData(
          {
            pubkey: editionKey,
            account: editionData,
          },
          updateTemp,
        );
      }
    }
  }


async (auction: string) => {
    const auctionExtendedKey = await getAuctionExtended({
      auctionProgramId: AUCTION_ID,
      resource: AUCTION_TO_VAULT[auction],
    });

    const auctionData = await getMultipleAccounts(
      connection,
      [auction, auctionExtendedKey],
      'single',
    );

    if (auctionData) {
      auctionData.keys.map((pubkey, i) => {
        processAuctions(
          {
            pubkey,
            account: auctionData.array[i],
          },
          updateTemp,
        );
      });
    }
  }


(pubkey, i) => {
        processAuctions(
          {
            pubkey,
            account: auctionData.array[i],
          },
          updateTemp,
        );
      }


async (auctionManager: string) => {
    const auctionManagerKey = new PublicKey(auctionManager);
    const auctionManagerData = await connection.getAccountInfo(
      auctionManagerKey,
    );
    if (auctionManagerData) {
      //@ts-ignore
      auctionManagerData.owner = auctionManagerData.owner.toBase58();
      processMetaplexAccounts(
        {
          pubkey: auctionManager,
          account: auctionManagerData,
        },
        updateTemp,
      );
    }
  }


async (vault: string) => {
    const vaultKey = new PublicKey(vault);
    const vaultData = await connection.getAccountInfo(vaultKey);
    if (vaultData) {
      //@ts-ignore
      vaultData.owner = vaultData.owner.toBase58();
      processVaultData(
        {
          pubkey: vault,
          account: vaultData,
        },
        updateTemp,
      );
    }
  }


md => pullMetadata(md)


a => pullAuction(a)


a => pullAuctionManager(a)


a => pullVault(a)


a =>
      getProgramAccounts(connection, AUCTION_ID, {
        filters: [
          {
            memcmp: {
              offset: 32,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processAuctions))


a =>
      getProgramAccounts(connection, AUCTION_ID, {
        filters: [
          {
            memcmp: {
              offset: 64,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processAuctions))


v =>
      getProgramAccounts(connection, VAULT_ID, {
        filters: [
          {
            memcmp: {
              offset: 1,
              bytes: v,
            },
          },
        ],
      }).then(forEach(processVaultData))


a =>
      getProgramAccounts(connection, METAPLEX_ID, {
        filters: [
          {
            memcmp: {
              offset: 9,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processMetaplexAccounts))


a =>
      getProgramAccounts(connection, METAPLEX_ID, {
        filters: [
          {
            memcmp: {
              offset: 1,
              bytes: a,
            },
          },
        ],
      }).then(forEach(processMetaplexAccounts))


key =>
        AUCTION_TO_METADATA[key]
          .map(md =>
            getProgramAccounts(connection, METAPLEX_ID, {
              filters: [
                {
                  memcmp: {
                    offset: 1,
                    bytes: md,
                  },
                },
              ],
            }).then(forEach(processMetaplexAccounts)),
          )
          .flat()


md =>
            getProgramAccounts(connection, METAPLEX_ID, {
              filters: [
                {
                  memcmp: {
                    offset: 1,
                    bytes: md,
                  },
                },
              ],
            }).then(forEach(processMetaplexAccounts))


async (connection: Connection) => {
  const state: MetaState = getEmptyMetaState();
  const updateState = makeSetter(state);
  const forEachAccount = processingAccounts(updateState);

  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateState);
      }
    };

  const loadVaults = () =>
    getProgramAccounts(connection, VAULT_ID).then(
      forEachAccount(processVaultData),
    );
  const loadAuctions = () =>
    getProgramAccounts(connection, AUCTION_ID).then(
      forEachAccount(processAuctions),
    );
  const loadMetaplex = () =>
    getProgramAccounts(connection, METAPLEX_ID).then(
      forEachAccount(processMetaplexAccounts),
    );
  const loadCreators = () =>
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          dataSize: MAX_WHITELISTED_CREATOR_SIZE,
        },
      ],
    }).then(forEach(processMetaplexAccounts));
  const loadMetadata = () =>
    pullMetadataByCreators(connection, state, updateState);
  const loadEditions = () =>
    pullEditions(connection, updateState, state, state.metadata);

  const loading = [
    loadCreators().then(loadMetadata).then(loadEditions),
    loadVaults(),
    loadAuctions(),
    loadMetaplex(),
  ];

  await Promise.all(loading);

  state.metadata = uniqWith(
    state.metadata,
    (a: ParsedAccount<Metadata>, b: ParsedAccount<Metadata>) =>
      a.pubkey === b.pubkey,
  );

  return state;
}


(fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateState);
      }
    }


async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateState);
      }
    }


() =>
    getProgramAccounts(connection, VAULT_ID).then(
      forEachAccount(processVaultData),
    )


() =>
    getProgramAccounts(connection, AUCTION_ID).then(
      forEachAccount(processAuctions),
    )


() =>
    getProgramAccounts(connection, METAPLEX_ID).then(
      forEachAccount(processMetaplexAccounts),
    )


() =>
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          dataSize: MAX_WHITELISTED_CREATOR_SIZE,
        },
      ],
    }).then(forEach(processMetaplexAccounts))


() =>
    pullMetadataByCreators(connection, state, updateState)


() =>
    pullEditions(connection, updateState, state, state.metadata)


(a: ParsedAccount<Metadata>, b: ParsedAccount<Metadata>) =>
      a.pubkey === b.pubkey


async (
  connection: Connection,
  updater: UpdateStateValueFunc,
  state: MetaState,
  metadataArr: ParsedAccount<Metadata>[],
) => {
  console.log('Pulling editions for optimized metadata');

  type MultipleAccounts = UnPromise<ReturnType<typeof getMultipleAccounts>>;
  let setOf100MetadataEditionKeys: string[] = [];
  const editionPromises: Promise<void>[] = [];

  const loadBatch = () => {
    editionPromises.push(
      getMultipleAccounts(
        connection,
        setOf100MetadataEditionKeys,
        'recent',
      ).then(processEditions),
    );
    setOf100MetadataEditionKeys = [];
  };

  const processEditions = (returnedAccounts: MultipleAccounts) => {
    for (let j = 0; j < returnedAccounts.array.length; j++) {
      processMetaData(
        {
          pubkey: returnedAccounts.keys[j],
          account: returnedAccounts.array[j],
        },
        updater,
      );
    }
  };

  for (const metadata of metadataArr) {
    // let editionKey: StringPublicKey;
    // TODO the nonce builder isnt working here, figure out why
    //if (metadata.info.editionNonce === null) {
    const editionKey = await getEdition(metadata.info.mint);
    /*} else {
      editionKey = (
        await PublicKey.createProgramAddress(
          [
            Buffer.from(METADATA_PREFIX),
            toPublicKey(METADATA_PROGRAM_ID).toBuffer(),
            toPublicKey(metadata.info.mint).toBuffer(),
            new Uint8Array([metadata.info.editionNonce || 0]),
          ],
          toPublicKey(METADATA_PROGRAM_ID),
        )
      ).toBase58();
    }*/

    setOf100MetadataEditionKeys.push(editionKey);

    if (setOf100MetadataEditionKeys.length >= 100) {
      loadBatch();
    }
  }

  if (setOf100MetadataEditionKeys.length >= 0) {
    loadBatch();
  }

  await Promise.all(editionPromises);

  console.log(
    'Edition size',
    Object.keys(state.editions).length,
    Object.keys(state.masterEditions).length,
  );
}


() => {
    editionPromises.push(
      getMultipleAccounts(
        connection,
        setOf100MetadataEditionKeys,
        'recent',
      ).then(processEditions),
    );
    setOf100MetadataEditionKeys = [];
  }


(returnedAccounts: MultipleAccounts) => {
    for (let j = 0; j < returnedAccounts.array.length; j++) {
      processMetaData(
        {
          pubkey: returnedAccounts.keys[j],
          account: returnedAccounts.array[j],
        },
        updater,
      );
    }
  }


(
  connection: Connection,
  state: MetaState,
  updater: UpdateStateValueFunc,
): Promise<any> => {
  console.log('pulling optimized nfts');

  const whitelistedCreators = Object.values(state.whitelistedCreatorsByCreator);

  const setter: UpdateStateValueFunc = async (prop, key, value) => {
    if (prop === 'metadataByMint') {
      await initMetadata(value, state.whitelistedCreatorsByCreator, updater);
    } else {
      updater(prop, key, value);
    }
  };
  const forEachAccount = processingAccounts(setter);

  const additionalPromises: Promise<void>[] = [];
  for (const creator of whitelistedCreators) {
    for (let i = 0; i < MAX_CREATOR_LIMIT; i++) {
      const promise = getProgramAccounts(connection, METADATA_PROGRAM_ID, {
        filters: [
          {
            memcmp: {
              offset:
                1 + // key
                32 + // update auth
                32 + // mint
                4 + // name string length
                MAX_NAME_LENGTH + // name
                4 + // uri string length
                MAX_URI_LENGTH + // uri
                4 + // symbol string length
                MAX_SYMBOL_LENGTH + // symbol
                2 + // seller fee basis points
                1 + // whether or not there is a creators vec
                4 + // creators vec length
                i * MAX_CREATOR_LEN,
              bytes: creator.info.address,
            },
          },
        ],
      }).then(forEachAccount(processMetaData));
      additionalPromises.push(promise);
    }
  }

  return Promise.all(additionalPromises);
}


async (prop, key, value) => {
    if (prop === 'metadataByMint') {
      await initMetadata(value, state.whitelistedCreatorsByCreator, updater);
    } else {
      updater(prop, key, value);
    }
  }


async (
  connection: Connection,
  state: MetaState,
  metadataKeys: StringPublicKey[],
): Promise<MetaState> => {
  const updateState = makeSetter(state);

  let setOf100MetadataEditionKeys: string[] = [];
  const metadataPromises: Promise<void>[] = [];

  const loadBatch = () => {
    metadataPromises.push(
      getMultipleAccounts(
        connection,
        setOf100MetadataEditionKeys,
        'recent',
      ).then(({ keys, array }) => {
        keys.forEach((key, index) =>
          processMetaData({ pubkey: key, account: array[index] }, updateState),
        );
      }),
    );
    setOf100MetadataEditionKeys = [];
  };

  for (const metadata of metadataKeys) {
    setOf100MetadataEditionKeys.push(metadata);

    if (setOf100MetadataEditionKeys.length >= 100) {
      loadBatch();
    }
  }

  if (setOf100MetadataEditionKeys.length >= 0) {
    loadBatch();
  }

  await Promise.all(metadataPromises);
  return state;
}


() => {
    metadataPromises.push(
      getMultipleAccounts(
        connection,
        setOf100MetadataEditionKeys,
        'recent',
      ).then(({ keys, array }) => {
        keys.forEach((key, index) =>
          processMetaData({ pubkey: key, account: array[index] }, updateState),
        );
      }),
    );
    setOf100MetadataEditionKeys = [];
  }


({ keys, array }) => {
        keys.forEach((key, index) =>
          processMetaData({ pubkey: key, account: array[index] }, updateState),
        );
      }


(key, index) =>
          processMetaData({ pubkey: key, account: array[index] }, updateState)


(state: MetaState): UpdateStateValueFunc<MetaState> =>
  (prop, key, value) => {
    if (prop === 'store') {
      state[prop] = value;
    } else if (prop === 'metadata') {
      state.metadata.push(value);
    } else if (prop === 'storeIndexer') {
      state.storeIndexer = state.storeIndexer.filter(
        p => p.info.page.toNumber() != value.info.page.toNumber(),
      );
      state.storeIndexer.push(value);
      state.storeIndexer = state.storeIndexer.sort((a, b) =>
        a.info.page.sub(b.info.page).toNumber(),
      );
    } else if (prop === 'packCardsByPackSet') {
      if (!state.packCardsByPackSet[key]) {
        state.packCardsByPackSet[key] = [];
      }

      const alreadyHasInState = state.packCardsByPackSet[key].some(
        ({ pubkey }) => pubkey === value.pubkey,
      );
      if (!alreadyHasInState) {
        state.packCardsByPackSet[key].push(value);
      }
    } else {
      state[prop][key] = value;
    }
    return state;
  }


(prop, key, value) => {
    if (prop === 'store') {
      state[prop] = value;
    } else if (prop === 'metadata') {
      state.metadata.push(value);
    } else if (prop === 'storeIndexer') {
      state.storeIndexer = state.storeIndexer.filter(
        p => p.info.page.toNumber() != value.info.page.toNumber(),
      );
      state.storeIndexer.push(value);
      state.storeIndexer = state.storeIndexer.sort((a, b) =>
        a.info.page.sub(b.info.page).toNumber(),
      );
    } else if (prop === 'packCardsByPackSet') {
      if (!state.packCardsByPackSet[key]) {
        state.packCardsByPackSet[key] = [];
      }

      const alreadyHasInState = state.packCardsByPackSet[key].some(
        ({ pubkey }) => pubkey === value.pubkey,
      );
      if (!alreadyHasInState) {
        state.packCardsByPackSet[key].push(value);
      }
    } else {
      state[prop][key] = value;
    }
    return state;
  }


p => p.info.page.toNumber() != value.info.page.toNumber()


(a, b) =>
        a.info.page.sub(b.info.page).toNumber()


({ pubkey }) => pubkey === value.pubkey


(updater: UpdateStateValueFunc) =>
  (fn: ProcessAccountsFunc) =>
  async (accounts: AccountAndPubkey[]) => {
    await createPipelineExecutor(
      accounts.values(),
      account => fn(account, updater),
      {
        sequence: 10,
        delay: 1,
        jobsCount: 3,
      },
    );
  }


(fn: ProcessAccountsFunc) =>
  async (accounts: AccountAndPubkey[]) => {
    await createPipelineExecutor(
      accounts.values(),
      account => fn(account, updater),
      {
        sequence: 10,
        delay: 1,
        jobsCount: 3,
      },
    );
  }


async (accounts: AccountAndPubkey[]) => {
    await createPipelineExecutor(
      accounts.values(),
      account => fn(account, updater),
      {
        sequence: 10,
        delay: 1,
        jobsCount: 3,
      },
    );
  }


account => fn(account, updater)


async (state: MetaState) => {
  const values = Object.values(state.metadataByMint);

  for (const metadata of values) {
    await metadataByMintUpdater(metadata, state);
  }
}


async (
  metadata: ParsedAccount<Metadata>,
  state: MetaState,
) => {
  const key = metadata.info.mint;
  if (isMetadataPartOfStore(metadata, state.whitelistedCreatorsByCreator)) {
    await metadata.info.init();
    const masterEditionKey = metadata.info?.masterEdition;
    if (masterEditionKey) {
      state.metadataByMasterEdition[masterEditionKey] = metadata;
    }
    if (!state.metadata.some(({ pubkey }) => metadata.pubkey === pubkey)) {
      state.metadata.push(metadata);
    }
    state.metadataByMint[key] = metadata;
  } else {
    delete state.metadataByMint[key];
  }
  return state;
}


({ pubkey }) => metadata.pubkey === pubkey


async (
  metadata: ParsedAccount<Metadata>,
  whitelistedCreators: Record<string, ParsedAccount<WhitelistedCreator>>,
  setter: UpdateStateValueFunc,
) => {
  if (isMetadataPartOfStore(metadata, whitelistedCreators)) {
    await metadata.info.init();
    setter('metadataByMint', metadata.info.mint, metadata);
    setter('metadata', '', metadata);
    const masterEditionKey = metadata.info?.masterEdition;
    if (masterEditionKey) {
      setter('metadataByMasterEdition', masterEditionKey, metadata);
    }
  }
}


type MultipleAccounts = UnPromise<ReturnType<typeof getMultipleAccounts>>;


err => {
			throw err.message;
		}


key => titles[key]


diary => this.check(skills, diaryRequirements[diary])


export default class extends BotCommand {
	public constructor(store: CommandStore, file: string[], directory: string) {
		super(store, file, directory, {
			cooldown: 2,
			aliases: ['d'],
			description:
				'Check which diaries your account has the required stats to complete (BOLD = boostable)',
			usage: '(username:rsn)',
			requiredPermissions: ['EMBED_LINKS']
		});
	}

	async run(msg: KlasaMessage, [username]: [string]) {
		const { skills } = await Hiscores.fetch(username).catch(err => {
			throw err.message;
		});

		const diaryNames = objectKeys(diaryRequirements)
			.map(key => titles[key])
			.join('\n');

		const canComplete = objectKeys(diaryRequirements)
			.map(diary => this.check(skills, diaryRequirements[diary]))
			.join('\n');

		const embed = new MessageEmbed()
			.setColor(11132490)
			.setThumbnail('https://i.imgur.com/wV9zvLM.png')
			.setDescription(username)
			.addField('Diary', diaryNames, true)
			.addField('You can complete:', canComplete, true)
			.setFooter('✶Boostable');

		return msg.send({ embed });
	}

	check(skills: SkillsScore, diary: typeof diaryRequirements[keyof typeof diaryRequirements]) {
		const levelMap = ['Easy', 'Medium', 'Hard', 'Elite'];
		let boostVar = 0;
		const canComplete = [];
		for (let i = 0; i < 4; i++) {
			for (const req of objectKeys(diary)) {
				if (req !== 'construction') {
					const statLevel = skills[req].level;
					const levelRequirement = diary[req].statReq[i];
					if (levelRequirement === 0) continue;
					if (statLevel >= levelRequirement) continue;
					if (statLevel < levelRequirement - diary[req].boost[i]) {
						break;
					} else {
						boostVar = 1;
						continue;
					}
				} else if (boostVar === 1) {
					canComplete.push(`✶${levelMap[i]}`);
				} else {
					canComplete.push(levelMap[i]);
				}
			}
		}
		return canComplete.length ? canComplete.join(', ') : '-';
	}
}


async run(msg: KlasaMessage, [username]: [string]) {
		const { skills } = await Hiscores.fetch(username).catch(err => {
			throw err.message;
		});

		const diaryNames = objectKeys(diaryRequirements)
			.map(key => titles[key])
			.join('\n');

		const canComplete = objectKeys(diaryRequirements)
			.map(diary => this.check(skills, diaryRequirements[diary]))
			.join('\n');

		const embed = new MessageEmbed()
			.setColor(11132490)
			.setThumbnail('https://i.imgur.com/wV9zvLM.png')
			.setDescription(username)
			.addField('Diary', diaryNames, true)
			.addField('You can complete:', canComplete, true)
			.setFooter('✶Boostable');

		return msg.send({ embed });
	}


check(skills: SkillsScore, diary: typeof diaryRequirements[keyof typeof diaryRequirements]) {
		const levelMap = ['Easy', 'Medium', 'Hard', 'Elite'];
		let boostVar = 0;
		const canComplete = [];
		for (let i = 0; i < 4; i++) {
			for (const req of objectKeys(diary)) {
				if (req !== 'construction') {
					const statLevel = skills[req].level;
					const levelRequirement = diary[req].statReq[i];
					if (levelRequirement === 0) continue;
					if (statLevel >= levelRequirement) continue;
					if (statLevel < levelRequirement - diary[req].boost[i]) {
						break;
					} else {
						boostVar = 1;
						continue;
					}
				} else if (boostVar === 1) {
					canComplete.push(`✶${levelMap[i]}`);
				} else {
					canComplete.push(levelMap[i]);
				}
			}
		}
		return canComplete.length ? canComplete.join(', ') : '-';
	}


({color, title}) => (
  <Container>
    <Title color={color}>{title}</Title>
  </Container>


interface Props {
  color: string;
  title: string;
}


/*
 * Helper function used by the IndexHtmlWebpackPlugin.
 * Can also be directly used by builder, e. g. in order to generate an index.html
 * after processing several configurations in order to build different sets of
 * bundles for differential serving.
 */
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> {
  const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;

  let { crossOrigin = 'none' } = params;
  if (sri && crossOrigin === 'none') {
    crossOrigin = 'anonymous';
  }

  const stylesheets = new Set<string>();
  const scripts = new Map</** file name */ string, /** isModule */ boolean>();

  // Sort files in the order we want to insert them by entrypoint
  for (const [entrypoint, isModule] of entrypoints) {
    for (const { extension, file, name } of files) {
      if (name !== entrypoint || scripts.has(file) || stylesheets.has(file)) {
        continue;
      }

      switch (extension) {
        case '.js':
          // Also, non entrypoints need to be loaded as no module as they can contain problematic code.
          scripts.set(file, isModule);
          break;
        case '.css':
          stylesheets.add(file);
          break;
      }
    }
  }

  let scriptTags: string[] = [];
  for (const [src, isModule] of scripts) {
    const attrs = [`src="${deployUrl}${src}"`];

    // This is also need for non entry-points as they may contain problematic code.
    if (isModule) {
      attrs.push('type="module"');
    } else {
      attrs.push('defer');
    }

    if (crossOrigin !== 'none') {
      attrs.push(`crossorigin="${crossOrigin}"`);
    }

    if (sri) {
      const content = await loadOutputFile(src);
      attrs.push(generateSriAttributes(content));
    }

    scriptTags.push(`<script ${attrs.join(' ')}></script>`);
  }

  let linkTags: string[] = [];
  for (const src of stylesheets) {
    const attrs = [`rel="stylesheet"`, `href="${deployUrl}${src}"`];

    if (crossOrigin !== 'none') {
      attrs.push(`crossorigin="${crossOrigin}"`);
    }

    if (sri) {
      const content = await loadOutputFile(src);
      attrs.push(generateSriAttributes(content));
    }

    linkTags.push(`<link ${attrs.join(' ')}>`);
  }

  const { rewriter, transformedContent } = await htmlRewritingStream(html);
  const baseTagExists = html.includes('<base');

  rewriter
    .on('startTag', (tag) => {
      switch (tag.tagName) {
        case 'html':
          // Adjust document locale if specified
          if (isString(lang)) {
            updateAttribute(tag, 'lang', lang);
          }
          break;
        case 'head':
          // Base href should be added before any link, meta tags
          if (!baseTagExists && isString(baseHref)) {
            rewriter.emitStartTag(tag);
            rewriter.emitRaw(`<base href="${baseHref}">`);

            return;
          }
          break;
        case 'base':
          // Adjust base href if specified
          if (isString(baseHref)) {
            updateAttribute(tag, 'href', baseHref);
          }
          break;
      }

      rewriter.emitStartTag(tag);
    })
    .on('endTag', (tag) => {
      switch (tag.tagName) {
        case 'head':
          for (const linkTag of linkTags) {
            rewriter.emitRaw(linkTag);
          }

          linkTags = [];
          break;
        case 'body':
          // Add script tags
          for (const scriptTag of scriptTags) {
            rewriter.emitRaw(scriptTag);
          }

          scriptTags = [];
          break;
      }

      rewriter.emitEndTag(tag);
    });

  const content = await transformedContent;

  if (linkTags.length || scriptTags.length) {
    // In case no body/head tags are not present (dotnet partial templates)
    return linkTags.join('') + scriptTags.join('') + content;
  }

  return content;
}


function generateSriAttributes(content: string): string {
  const algo = 'sha384';
  const hash = createHash(algo).update(content, 'utf8').digest('base64');

  return `integrity="${algo}-${hash}"`;
}


function updateAttribute(
  tag: { attrs: { name: string; value: string }[] },
  name: string,
  value: string,
): void {
  const index = tag.attrs.findIndex((a) => a.name === name);
  const newValue = { name, value };

  if (index === -1) {
    tag.attrs.push(newValue);
  } else {
    tag.attrs[index] = newValue;
  }
}


function isString(value: unknown): value is string {
  return typeof value === 'string';
}


(tag) => {
      switch (tag.tagName) {
        case 'html':
          // Adjust document locale if specified
          if (isString(lang)) {
            updateAttribute(tag, 'lang', lang);
          }
          break;
        case 'head':
          // Base href should be added before any link, meta tags
          if (!baseTagExists && isString(baseHref)) {
            rewriter.emitStartTag(tag);
            rewriter.emitRaw(`<base href="${baseHref}">`);

            return;
          }
          break;
        case 'base':
          // Adjust base href if specified
          if (isString(baseHref)) {
            updateAttribute(tag, 'href', baseHref);
          }
          break;
      }

      rewriter.emitStartTag(tag);
    }


(tag) => {
      switch (tag.tagName) {
        case 'head':
          for (const linkTag of linkTags) {
            rewriter.emitRaw(linkTag);
          }

          linkTags = [];
          break;
        case 'body':
          // Add script tags
          for (const scriptTag of scriptTags) {
            rewriter.emitRaw(scriptTag);
          }

          scriptTags = [];
          break;
      }

      rewriter.emitEndTag(tag);
    }


(a) => a.name === name


export interface AugmentIndexHtmlOptions {
  /* Input contents */
  html: string;
  baseHref?: string;
  deployUrl?: string;
  sri: boolean;
  /** crossorigin attribute setting of elements that provide CORS support */
  crossOrigin?: CrossOriginValue;
  /*
   * Files emitted by the build.
   */
  files: FileInfo[];
  /*
   * Function that loads a file used.
   * This allows us to use different routines within the IndexHtmlWebpackPlugin and
   * when used without this plugin.
   */
  loadOutputFile: LoadOutputFileFunctionType;
  /** Used to sort the inseration of files in the HTML file */
  entrypoints: Entrypoint[];
  /** Used to set the document default locale */
  lang?: string;
}


export interface FileInfo {
  file: string;
  name: string;
  extension: string;
}


export type LoadOutputFileFunctionType = (file: string) => Promise<string>;


export type CrossOriginValue = 'none' | 'anonymous' | 'use-credentials';


export type Entrypoint = [name: string, isModule: boolean];


item => new TextRegion(
                item.regionId,
                imageId,
                Region.parseFromPostgresPolygonString(item.region),
                item.label,
                User.newBaseUser(item.uploaderDisplayName, item.uploaderUsername),
                item.labelerDisplayName ? User.newBaseUser(item.labelerDisplayName, item.labelerUsername) : null
            )


item => new TextRegion(
                item.regionId,
                item.imageId,
                Region.parseFromPostgresPolygonString(item.region),
                item.label,
                User.newBaseUser(item.uploaderDisplayName, item.uploaderUsername),
                item.labelerDisplayName ? User.newBaseUser(item.labelerDisplayName, item.labelerUsername) : null
            )


class TextRegionDao {
    private constructor() { }
    private static readonly INSTANCE = new TextRegionDao();
    public static getInstance(): TextRegionDao {
        return this.INSTANCE;
    }

    public async getTextRegion(regionId: string): Promise<TextRegion> {
        try {
            const result = await databaseConnection.oneOrNone(
                `
                    SELECT * FROM public."Regions"
                        WHERE "Regions"."regionId" = $1;
                `,
                [regionId]
            );
            if (!result) {
                return null;
            }
            const label = result.label;
            return new TextRegion(
                result.regionId,
                result.imageId,
                Region.parseFromPostgresPolygonString(result.region),
                label,
                User.newBaseUser(result.uploadedBy, result.uploadedBy),
                result.labeledBy ? User.newBaseUser(result.labeledBy, result.labeledBy) : null
            );
        } catch (e) {
            throw new Error(`[getTextRegion()] Error happened while reading regions from database: ${e}`);
        }
    }

    public async getTextRegionsOfImage(imageId: string): Promise<TextRegion[]> {
        try {
            const regions = await databaseConnection.any(
                `
                    SELECT
                        "Regions"."regionId", "Regions"."region", "Regions"."label",
                        "Uploader".username as "uploaderUsername", "Uploader"."displayName" as "uploaderDisplayName",
                        "Labeler".username as "labelerUsername", "Labeler"."displayName" as "labelerDisplayName",
                        "RegionLabels"."displayName" as "labelDisplayName", "RegionLabels"."color" as "labelColor"
                        FROM public."Regions"
                        INNER JOIN public."Users" AS "Uploader"
                            ON "Regions"."uploadedBy" = "Uploader".username
                        LEFT JOIN public."Users" as "Labeler"
                            ON "Regions"."labeledBy" = "Labeler".username
                        LEFT JOIN public."RegionLabels"
                            ON "Regions"."label" = "RegionLabels"."labelId"
                        WHERE "Regions"."imageId" = $1;
                `,
                [imageId]
            );
            const results: TextRegion[] = regions.map(item => new TextRegion(
                item.regionId,
                imageId,
                Region.parseFromPostgresPolygonString(item.region),
                item.label,
                User.newBaseUser(item.uploaderDisplayName, item.uploaderUsername),
                item.labelerDisplayName ? User.newBaseUser(item.labelerDisplayName, item.labelerUsername) : null
            ));
            return results;
        } catch (e) {
            throw new Error(`[getTextRegionsOfImage()] Error happened while reading regions from database: ${e}`);
        }
    }

    public async getTextRegionsOfImageList(imageIds: string[]): Promise<TextRegion[]> {
        try {
            const regions = await databaseConnection.any(toOrdinal(`
                SELECT
                    "TextRegions"."regionId", "TextRegions"."imageId", "TextRegions"."region", "TextRegions"."label",
                    "Uploader".username as "uploaderUsername", "Uploader"."displayName" as "uploaderDisplayName",
                    "Labeler".username as "labelerUsername", "Labeler"."displayName" as "labelerDisplayName"
                    FROM public."TextRegions"
                    INNER JOIN public."Users" AS "Uploader"
                        ON "TextRegions"."uploadedBy" = "Uploader".username
                    LEFT JOIN public."Users" as "Labeler"
                        ON "TextRegions"."labeledBy" = "Labeler".username
                    WHERE "TextRegions"."imageId" IN (${imageIds.map(() => "?").join(",")});
            `), [...imageIds]);
            const results: TextRegion[] = regions.map(item => new TextRegion(
                item.regionId,
                item.imageId,
                Region.parseFromPostgresPolygonString(item.region),
                item.label,
                User.newBaseUser(item.uploaderDisplayName, item.uploaderUsername),
                item.labelerDisplayName ? User.newBaseUser(item.labelerDisplayName, item.labelerUsername) : null
            ));
            return results;
        } catch (e) {
            throw new Error(`[getTextRegionsOfImageList()] Error happened while reading regions from database: ${e}`);
        }
    }

    public async getNumberOfUnlabeledRegion(imageId: string): Promise<number> {
        try {
            const result = await databaseConnection.one(`
                SELECT COUNT(*) FROM public."Regions" WHERE "Regions"."imageId" = $1 AND "Regions".label IS null;
            `, [imageId]);
            return +result.count;
        } catch (reason) {
            throw new Error(`[getNumberOfUnlabeledRegion()] Error happened while counting unlabeled region: ${reason}`);
        }
    }
}


public static getInstance(): TextRegionDao {
        return this.INSTANCE;
    }


public async getTextRegion(regionId: string): Promise<TextRegion> {
        try {
            const result = await databaseConnection.oneOrNone(
                `
                    SELECT * FROM public."Regions"
                        WHERE "Regions"."regionId" = $1;
                `,
                [regionId]
            );
            if (!result) {
                return null;
            }
            const label = result.label;
            return new TextRegion(
                result.regionId,
                result.imageId,
                Region.parseFromPostgresPolygonString(result.region),
                label,
                User.newBaseUser(result.uploadedBy, result.uploadedBy),
                result.labeledBy ? User.newBaseUser(result.labeledBy, result.labeledBy) : null
            );
        } catch (e) {
            throw new Error(`[getTextRegion()] Error happened while reading regions from database: ${e}`);
        }
    }


public async getTextRegionsOfImage(imageId: string): Promise<TextRegion[]> {
        try {
            const regions = await databaseConnection.any(
                `
                    SELECT
                        "Regions"."regionId", "Regions"."region", "Regions"."label",
                        "Uploader".username as "uploaderUsername", "Uploader"."displayName" as "uploaderDisplayName",
                        "Labeler".username as "labelerUsername", "Labeler"."displayName" as "labelerDisplayName",
                        "RegionLabels"."displayName" as "labelDisplayName", "RegionLabels"."color" as "labelColor"
                        FROM public."Regions"
                        INNER JOIN public."Users" AS "Uploader"
                            ON "Regions"."uploadedBy" = "Uploader".username
                        LEFT JOIN public."Users" as "Labeler"
                            ON "Regions"."labeledBy" = "Labeler".username
                        LEFT JOIN public."RegionLabels"
                            ON "Regions"."label" = "RegionLabels"."labelId"
                        WHERE "Regions"."imageId" = $1;
                `,
                [imageId]
            );
            const results: TextRegion[] = regions.map(item => new TextRegion(
                item.regionId,
                imageId,
                Region.parseFromPostgresPolygonString(item.region),
                item.label,
                User.newBaseUser(item.uploaderDisplayName, item.uploaderUsername),
                item.labelerDisplayName ? User.newBaseUser(item.labelerDisplayName, item.labelerUsername) : null
            ));
            return results;
        } catch (e) {
            throw new Error(`[getTextRegionsOfImage()] Error happened while reading regions from database: ${e}`);
        }
    }


public async getTextRegionsOfImageList(imageIds: string[]): Promise<TextRegion[]> {
        try {
            const regions = await databaseConnection.any(toOrdinal(`
                SELECT
                    "TextRegions"."regionId", "TextRegions"."imageId", "TextRegions"."region", "TextRegions"."label",
                    "Uploader".username as "uploaderUsername", "Uploader"."displayName" as "uploaderDisplayName",
                    "Labeler".username as "labelerUsername", "Labeler"."displayName" as "labelerDisplayName"
                    FROM public."TextRegions"
                    INNER JOIN public."Users" AS "Uploader"
                        ON "TextRegions"."uploadedBy" = "Uploader".username
                    LEFT JOIN public."Users" as "Labeler"
                        ON "TextRegions"."labeledBy" = "Labeler".username
                    WHERE "TextRegions"."imageId" IN (${imageIds.map(() => "?").join(",")});
            `), [...imageIds]);
            const results: TextRegion[] = regions.map(item => new TextRegion(
                item.regionId,
                item.imageId,
                Region.parseFromPostgresPolygonString(item.region),
                item.label,
                User.newBaseUser(item.uploaderDisplayName, item.uploaderUsername),
                item.labelerDisplayName ? User.newBaseUser(item.labelerDisplayName, item.labelerUsername) : null
            ));
            return results;
        } catch (e) {
            throw new Error(`[getTextRegionsOfImageList()] Error happened while reading regions from database: ${e}`);
        }
    }


public async getNumberOfUnlabeledRegion(imageId: string): Promise<number> {
        try {
            const result = await databaseConnection.one(`
                SELECT COUNT(*) FROM public."Regions" WHERE "Regions"."imageId" = $1 AND "Regions".label IS null;
            `, [imageId]);
            return +result.count;
        } catch (reason) {
            throw new Error(`[getNumberOfUnlabeledRegion()] Error happened while counting unlabeled region: ${reason}`);
        }
    }


export interface Heartbeat {
  head: string
  height: number
  nickname: string
  peerId: string
  ip: string
  minerAddress: string
}


(node: Node, parent: ElementContainer, root: ElementContainer) => {
    for (let childNode = node.firstChild, nextNode; childNode; childNode = nextNode) {
        nextNode = childNode.nextSibling;

        if (isTextNode(childNode) && childNode.data.trim().length > 0) {
            parent.textNodes.push(new TextContainer(childNode, parent.styles));
        } else if (isElementNode(childNode)) {
            const container = createContainer(childNode);
            if (container.styles.isVisible()) {
                if (createsRealStackingContext(childNode, container, root)) {
                    container.flags |= FLAGS.CREATES_REAL_STACKING_CONTEXT;
                } else if (createsStackingContext(container.styles)) {
                    container.flags |= FLAGS.CREATES_STACKING_CONTEXT;
                }

                if (LIST_OWNERS.indexOf(childNode.tagName) !== -1) {
                    container.flags |= FLAGS.IS_LIST_OWNER;
                }

                parent.elements.push(container);
                if (!isTextareaElement(childNode) && !isSVGElement(childNode) && !isSelectElement(childNode)) {
                    parseNodeTree(childNode, container, root);
                }
            }
        }
    }
}


(element: Element): ElementContainer => {
    if (isImageElement(element)) {
        return new ImageElementContainer(element);
    }

    if (isCanvasElement(element)) {
        return new CanvasElementContainer(element);
    }

    if (isSVGElement(element)) {
        return new SVGElementContainer(element);
    }

    if (isLIElement(element)) {
        return new LIElementContainer(element);
    }

    if (isOLElement(element)) {
        return new OLElementContainer(element);
    }

    if (isInputElement(element)) {
        return new InputElementContainer(element);
    }

    if (isSelectElement(element)) {
        return new SelectElementContainer(element);
    }

    if (isTextareaElement(element)) {
        return new TextareaElementContainer(element);
    }

    if (isIFrameElement(element)) {
        return new IFrameElementContainer(element);
    }

    return new ElementContainer(element);
}


(element: HTMLElement): ElementContainer => {
    const container = createContainer(element);
    container.flags |= FLAGS.CREATES_REAL_STACKING_CONTEXT;
    parseNodeTree(element, container, container);
    return container;
}


(node: Element, container: ElementContainer, root: ElementContainer): boolean => {
    return (
        container.styles.isPositionedWithZIndex() ||
        container.styles.opacity < 1 ||
        container.styles.isTransformed() ||
        (isBodyElement(node) && root.styles.isTransparent())
    );
}


(styles: CSSParsedDeclaration): boolean => styles.isPositioned() || styles.isFloating()


(node: Node): node is Text => node.nodeType === Node.TEXT_NODE


(node: Node): node is Element => node.nodeType === Node.ELEMENT_NODE


(node: Node): node is HTMLElement =>
    typeof (node as HTMLElement).style !== 'undefined'


(element: Element): element is SVGElement =>
    typeof (element as SVGElement).className === 'object'


(node: Element): node is HTMLLIElement => node.tagName === 'LI'


(node: Element): node is HTMLOListElement => node.tagName === 'OL'


(node: Element): node is HTMLInputElement => node.tagName === 'INPUT'


(node: Element): node is HTMLHtmlElement => node.tagName === 'HTML'


(node: Element): node is SVGSVGElement => node.tagName === 'svg'


(node: Element): node is HTMLBodyElement => node.tagName === 'BODY'


(node: Element): node is HTMLCanvasElement => node.tagName === 'CANVAS'


(node: Element): node is HTMLImageElement => node.tagName === 'IMG'


(node: Element): node is HTMLIFrameElement => node.tagName === 'IFRAME'


(node: Element): node is HTMLStyleElement => node.tagName === 'STYLE'


(node: Element): node is HTMLScriptElement => node.tagName === 'SCRIPT'


(node: Element): node is HTMLTextAreaElement => node.tagName === 'TEXTAREA'


(node: Element): node is HTMLSelectElement => node.tagName === 'SELECT'


@Component({
  selector: 'rob-homepage-carousel',
  templateUrl: './homepage-carousel.component.html',
  styleUrls: ['./homepage-carousel.component.scss']
})
export class HomepageCarouselComponent {}


export class CreateTagsCommand extends $Command<
  CreateTagsCommandInput,
  CreateTagsCommandOutput,
  MediaLiveClientResolvedConfig
> {
  // Start section: command_properties
  // End section: command_properties

  constructor(readonly input: CreateTagsCommandInput) {
    // Start section: command_constructor
    super();
    // End section: command_constructor
  }

  resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: MediaLiveClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<CreateTagsCommandInput, CreateTagsCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      inputFilterSensitiveLog: CreateTagsRequest.filterSensitiveLog,
      outputFilterSensitiveLog: (output: any) => output,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }

  private serialize(input: CreateTagsCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_restJson1CreateTagsCommand(input, context);
  }

  private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<CreateTagsCommandOutput> {
    return deserializeAws_restJson1CreateTagsCommand(output, context);
  }

  // Start section: command_body_extra
  // End section: command_body_extra
}


export type CreateTagsCommandInput = CreateTagsRequest;


export type CreateTagsCommandOutput = __MetadataBearer;


resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: MediaLiveClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<CreateTagsCommandInput, CreateTagsCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      inputFilterSensitiveLog: CreateTagsRequest.filterSensitiveLog,
      outputFilterSensitiveLog: (output: any) => output,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }


private serialize(input: CreateTagsCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_restJson1CreateTagsCommand(input, context);
  }


private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<CreateTagsCommandOutput> {
    return deserializeAws_restJson1CreateTagsCommand(output, context);
  }


() => {
  let context: any = {};

  beforeEach(() => {
    context = { x: 10, s: 'property', o: { a: 1, b: 2 } };
  });

  it('should return an object', () => {
    const result = evaluate('{}', context);

    expect(result).toBeInstanceOf(Object);
  });

  it('should recognize identifier properties', () => {
    const result = evaluate('{a: 1, b:2}', context);

    expect(result).toEqual({ a: 1, b: 2 });
  });

  it('should recognize string properties', () => {
    const result = evaluate('{"- a": 1, "@b":2}', context);

    expect(result).toEqual({ '- a': 1, '@b': 2 });
  });

  it('should recognize number properties', () => {
    const result = evaluate('{1: 1, 2:2, 0xF:16}', context);

    expect(result).toEqual({ 1: 1, 2: 2, 0xf: 16 });
  });

  it('should recognize computed properties', () => {
    const result = evaluate('{[s]:2}', context);

    expect(result).toEqual({ property: 2 });
  });

  it('should allow trailling comma', () => {
    const result = evaluate('{a: 1, b:2,}', context);

    expect(result).toEqual({ a: 1, b: 2 });
  });

  it('should allow spread and clone object', () => {
    const result = evaluate('{...o}', context);

    expect(result).toEqual({ a: 1, b: 2 });
    expect(result).not.toBe(context.o);
  });

  it('should allow spread and overwrite properties', () => {
    const result = evaluate('{...o, a:100}', context);

    expect(result).toEqual({ a: 100, b: 2 });
  });

  it('should allow spread and overwrite properties', () => {
    const result = evaluate('{ a:100, ...o}', context);

    expect(result).toEqual({ a: 1, b: 2 });
  });
}


() => {
    context = { x: 10, s: 'property', o: { a: 1, b: 2 } };
  }


() => {
    const result = evaluate('{}', context);

    expect(result).toBeInstanceOf(Object);
  }


() => {
    const result = evaluate('{a: 1, b:2}', context);

    expect(result).toEqual({ a: 1, b: 2 });
  }


() => {
    const result = evaluate('{"- a": 1, "@b":2}', context);

    expect(result).toEqual({ '- a': 1, '@b': 2 });
  }


() => {
    const result = evaluate('{1: 1, 2:2, 0xF:16}', context);

    expect(result).toEqual({ 1: 1, 2: 2, 0xf: 16 });
  }


() => {
    const result = evaluate('{[s]:2}', context);

    expect(result).toEqual({ property: 2 });
  }


() => {
    const result = evaluate('{a: 1, b:2,}', context);

    expect(result).toEqual({ a: 1, b: 2 });
  }


() => {
    const result = evaluate('{...o}', context);

    expect(result).toEqual({ a: 1, b: 2 });
    expect(result).not.toBe(context.o);
  }


() => {
    const result = evaluate('{...o, a:100}', context);

    expect(result).toEqual({ a: 100, b: 2 });
  }


() => {
    const result = evaluate('{ a:100, ...o}', context);

    expect(result).toEqual({ a: 1, b: 2 });
  }