import { Logger } from '../..';
import { getGLTypeFromSource, getGlTypeSizeBytes, isAttributeInSource } from './webgl-util';
/**
 * Helper around creating vertex attributes in a given [[VertexBuffer]], this is useful for describing
 * the memory layout for your vertices inside a particular buffer
 *
 * Note: This helper assumes interleaved attributes in one [[VertexBuffer]], not many.
 *
 * Working with `gl.vertexAttribPointer` can be tricky, and this attempts to double check you
 */
export class VertexLayout {
    get vertexBuffer() {
        return this._vertexBuffer;
    }
    get attributes() {
        return this._attributes;
    }
    constructor(options) {
        this._logger = Logger.getInstance();
        this._suppressWarnings = false;
        this._layout = [];
        this._attributes = [];
        this._vertexTotalSizeBytes = 0;
        this._initialized = false;
        const { gl, shader, vertexBuffer, attributes, suppressWarnings } = options;
        this._gl = gl;
        this._vertexBuffer = vertexBuffer;
        this._attributes = attributes;
        this._shader = shader;
        this._suppressWarnings = suppressWarnings;
        if (shader) {
            this.initialize();
        }
    }
    /**
     * Total number of bytes that the vertex will take up
     */
    get totalVertexSizeBytes() {
        return this._vertexTotalSizeBytes;
    }
    set shader(shader) {
        if (shader && this._shader !== shader) {
            this._shader = shader;
            this.initialize();
        }
    }
    get shader() {
        return this._shader;
    }
    /**
     * Layouts need shader locations and must be bound to a shader
     */
    initialize() {
        if (this._initialized) {
            return;
        }
        if (!this._shader) {
            return;
        }
        if (!this._shader.compiled) {
            throw Error('Shader not compiled, shader must be compiled before defining a vertex layout');
        }
        this._vertexTotalSizeBytes = 0;
        this._layout.length = 0;
        const shaderAttributes = this._shader.attributes;
        for (const attribute of this._attributes) {
            const attrib = shaderAttributes[attribute[0]];
            if (!attrib) {
                if (!isAttributeInSource(this._shader.vertexSource, attribute[0])) {
                    throw Error(`The attribute named: ${attribute[0]} size ${attribute[1]}` +
                        ` not found in the shader source code:\n ${this._shader.vertexSource}`);
                }
                if (!this._suppressWarnings) {
                    this._logger.warn(`The attribute named: ${attribute[0]} size ${attribute[1]}` +
                        ` not found in the compiled shader. This is possibly a bug:\n` +
                        ` 1. Not a bug, but should remove unused code - attribute "${attribute[0]}" is unused in` +
                        ` vertex/fragment and has been automatically removed by glsl compiler.\n` +
                        ` 2. Definitely a bug, attribute "${attribute[0]}" in layout has been mistyped or is missing` +
                        ` in shader, check vertex/fragment source.`);
                }
                const glType = getGLTypeFromSource(this._gl, this._shader.vertexSource, attribute[0]);
                // Unused attribute placeholder
                this._layout.push({
                    name: attribute[0],
                    glType,
                    size: attribute[1],
                    location: -1,
                    normalized: false
                });
            }
            if (attrib) {
                if (attrib.size !== attribute[1]) {
                    throw Error(`VertexLayout size definition for attribute: [${attribute[0]}, ${attribute[1]}],` +
                        ` doesn\'t match shader source size ${attrib.size}:\n ${this._shader.vertexSource}`);
                }
                this._layout.push(attrib);
            }
        }
        // calc size
        let componentsPerVertex = 0;
        for (const vertAttribute of this._layout) {
            const typeSize = getGlTypeSizeBytes(this._gl, vertAttribute.glType);
            this._vertexTotalSizeBytes += typeSize * vertAttribute.size;
            componentsPerVertex += vertAttribute.size;
        }
        if (this._vertexBuffer.bufferData.length % componentsPerVertex !== 0) {
            this._logger.warn(`The vertex component size (${componentsPerVertex})  does NOT divide evenly into the specified vertex buffer` +
                ` (${this._vertexBuffer.bufferData.length})`);
        }
        // create VAO
        const gl = this._gl;
        this._vao = gl.createVertexArray();
        gl.bindVertexArray(this._vao);
        this._vertexBuffer.bind();
        let offset = 0;
        for (const vert of this._layout) {
            if (vert.location !== -1) {
                // skip unused attributes
                gl.vertexAttribPointer(vert.location, vert.size, vert.glType, vert.normalized, this.totalVertexSizeBytes, offset);
                gl.enableVertexAttribArray(vert.location);
            }
            offset += getGlTypeSizeBytes(gl, vert.glType) * vert.size;
        }
        gl.bindVertexArray(null);
        this._vertexBuffer.unbind();
        this._initialized = true;
    }
    /**
     * Bind this layout with it's associated vertex buffer
     * @param uploadBuffer Optionally indicate you wish to upload the buffer to the GPU associated with this layout
     */
    use(uploadBuffer = false, count) {
        if (!this._shader) {
            throw Error('No shader is associated with this vertex layout, a shader must be set');
        }
        const gl = this._gl;
        if (!this._shader.isCurrentlyBound()) {
            throw Error('Shader associated with this vertex layout is not active! Call shader.use() before layout.use()');
        }
        this._vertexBuffer.bind();
        if (uploadBuffer) {
            this._vertexBuffer.upload(count);
        }
        gl.bindVertexArray(this._vao);
    }
}
//# sourceMappingURL=vertex-layout.js.map