"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InMemoryDataAccessor = void 0;
const RepresentationMetadata_1 = require("../../http/representation/RepresentationMetadata");
const InternalServerError_1 = require("../../util/errors/InternalServerError");
const NotFoundHttpError_1 = require("../../util/errors/NotFoundHttpError");
const StreamUtil_1 = require("../../util/StreamUtil");
const Vocabularies_1 = require("../../util/Vocabularies");
const ConversionUtil_1 = require("../conversion/ConversionUtil");
class InMemoryDataAccessor {
    identifierStrategy;
    // A dummy container where every entry corresponds to a root container
    store;
    constructor(identifierStrategy) {
        this.identifierStrategy = identifierStrategy;
        this.store = { entries: {} };
    }
    async canHandle() {
        // All data is supported since streams never get read, only copied
    }
    async getData(identifier) {
        const entry = this.getEntry(identifier);
        if (!this.isDataEntry(entry)) {
            throw new NotFoundHttpError_1.NotFoundHttpError();
        }
        return (0, StreamUtil_1.guardedStreamFrom)(entry.data);
    }
    async getMetadata(identifier) {
        const entry = this.getEntry(identifier);
        return new RepresentationMetadata_1.RepresentationMetadata(entry.metadata);
    }
    async *getChildren(identifier) {
        const entry = this.getEntry(identifier);
        if (!this.isDataEntry(entry)) {
            const childNames = Object.keys(entry.entries);
            yield* childNames.map((name) => new RepresentationMetadata_1.RepresentationMetadata({ path: name }));
        }
    }
    async writeDocument(identifier, data, metadata) {
        const parent = this.getParentEntry(identifier);
        // Drain original stream and create copy
        const dataArray = await (0, StreamUtil_1.arrayifyStream)(data);
        // Only add the size for binary streams, which are all streams that do not have an internal type.
        if (metadata.contentType && !(0, ConversionUtil_1.isInternalContentType)(metadata.contentType)) {
            const size = dataArray.reduce((total, chunk) => total + chunk.length, 0);
            metadata.set(Vocabularies_1.POSIX.terms.size, `${size}`);
        }
        parent.entries[identifier.path] = {
            data: dataArray,
            metadata,
        };
    }
    async writeContainer(identifier, metadata) {
        try {
            // Overwrite existing metadata but keep children if container already exists
            const entry = this.getEntry(identifier);
            entry.metadata = metadata;
        }
        catch (error) {
            // Create new entry if it didn't exist yet
            if (NotFoundHttpError_1.NotFoundHttpError.isInstance(error)) {
                const parent = this.getParentEntry(identifier);
                parent.entries[identifier.path] = {
                    entries: {},
                    metadata,
                };
            }
            else {
                throw error;
            }
        }
    }
    async writeMetadata(identifier, metadata) {
        const entry = this.getEntry(identifier);
        entry.metadata = metadata;
    }
    async deleteResource(identifier) {
        const parent = this.getParentEntry(identifier);
        if (!parent.entries[identifier.path]) {
            throw new NotFoundHttpError_1.NotFoundHttpError();
        }
        delete parent.entries[identifier.path];
    }
    isDataEntry(entry) {
        return Boolean(entry.data);
    }
    /**
     * Generates an array of identifiers corresponding to the nested containers until the given identifier is reached.
     * This does not verify if these identifiers actually exist.
     */
    getHierarchy(identifier) {
        if (this.identifierStrategy.isRootContainer(identifier)) {
            return [identifier];
        }
        const hierarchy = this.getHierarchy(this.identifierStrategy.getParentContainer(identifier));
        hierarchy.push(identifier);
        return hierarchy;
    }
    /**
     * Returns the ContainerEntry corresponding to the parent container of the given identifier.
     * Will throw 404 if the parent does not exist.
     */
    getParentEntry(identifier) {
        // Casting is fine here as the parent should never be used as a real container
        let parent = this.store;
        if (this.identifierStrategy.isRootContainer(identifier)) {
            return parent;
        }
        const hierarchy = this.getHierarchy(this.identifierStrategy.getParentContainer(identifier));
        for (const entry of hierarchy) {
            parent = parent.entries[entry.path];
            if (!parent) {
                throw new NotFoundHttpError_1.NotFoundHttpError();
            }
            if (this.isDataEntry(parent)) {
                throw new InternalServerError_1.InternalServerError('Invalid path.');
            }
        }
        return parent;
    }
    /**
     * Returns the CacheEntry corresponding the given identifier.
     * Will throw 404 if the resource does not exist.
     */
    getEntry(identifier) {
        const parent = this.getParentEntry(identifier);
        const entry = parent.entries[identifier.path];
        if (!entry) {
            throw new NotFoundHttpError_1.NotFoundHttpError();
        }
        return entry;
    }
}
exports.InMemoryDataAccessor = InMemoryDataAccessor;
//# sourceMappingURL=InMemoryDataAccessor.js.map