import { decodeStreamMetadata } from "../metadata/tile/streamMetadataDecoder";
import { decodeSignedInt32Stream, decodeLengthStreamToOffsetBuffer, decodeUnsignedConstInt32Stream, decodeUnsignedInt32Stream, getVectorType, } from "./integerStreamDecoder";
import { VectorType } from "../vector/vectorType";
import { PhysicalStreamType } from "../metadata/tile/physicalStreamType";
import { LengthType } from "../metadata/tile/lengthType";
import { DictionaryType } from "../metadata/tile/dictionaryType";
import { createConstGeometryVector, createMortonEncodedConstGeometryVector, } from "../vector/geometry/constGeometryVector";
import { createFlatGeometryVector, createFlatGeometryVectorMortonEncoded } from "../vector/geometry/flatGeometryVector";
import { OffsetType } from "../metadata/tile/offsetType";
import { createConstGpuVector } from "../vector/geometry/constGpuVector";
import { createFlatGpuVector } from "../vector/geometry/flatGpuVector";
// TODO: get rid of numFeatures parameter
export function decodeGeometryColumn(tile, numStreams, offset, numFeatures, scalingData) {
    const geometryTypeMetadata = decodeStreamMetadata(tile, offset);
    const geometryTypesVectorType = getVectorType(geometryTypeMetadata, numFeatures, tile, offset);
    let vertexOffsets;
    let vertexBuffer;
    let mortonSettings;
    let indexBuffer;
    if (geometryTypesVectorType === VectorType.CONST) {
        /* All geometries in the column have the same geometry type */
        const geometryType = decodeUnsignedConstInt32Stream(tile, offset, geometryTypeMetadata);
        // Variables for const geometry path (directly decoded as offsets)
        let geometryOffsets;
        let partOffsets;
        let ringOffsets;
        //TODO: use geometryOffsets for that? -> but then tessellated polygons can't be used with normal polygons
        // in one FeatureTable?
        let triangleOffsets;
        for (let i = 0; i < numStreams - 1; i++) {
            const geometryStreamMetadata = decodeStreamMetadata(tile, offset);
            switch (geometryStreamMetadata.physicalStreamType) {
                case PhysicalStreamType.LENGTH:
                    switch (geometryStreamMetadata.logicalStreamType.lengthType) {
                        case LengthType.GEOMETRIES:
                            geometryOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata);
                            break;
                        case LengthType.PARTS:
                            partOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata);
                            break;
                        case LengthType.RINGS:
                            ringOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata);
                            break;
                        case LengthType.TRIANGLES:
                            triangleOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata);
                    }
                    break;
                case PhysicalStreamType.OFFSET: {
                    switch (geometryStreamMetadata.logicalStreamType.offsetType) {
                        case OffsetType.VERTEX:
                            vertexOffsets = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                            break;
                        case OffsetType.INDEX:
                            indexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                            break;
                    }
                    break;
                }
                case PhysicalStreamType.DATA: {
                    if (DictionaryType.VERTEX === geometryStreamMetadata.logicalStreamType.dictionaryType) {
                        vertexBuffer = decodeSignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData);
                    }
                    else {
                        const mortonMetadata = geometryStreamMetadata;
                        mortonSettings = {
                            numBits: mortonMetadata.numBits,
                            coordinateShift: mortonMetadata.coordinateShift,
                        };
                        vertexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData);
                    }
                    break;
                }
            }
        }
        if (indexBuffer) {
            if (geometryOffsets !== undefined || partOffsets !== undefined) {
                /* Case when the indices of a Polygon outline are encoded in the tile */
                const topologyVector = { geometryOffsets, partOffsets, ringOffsets };
                return createConstGpuVector(numFeatures, geometryType, triangleOffsets, indexBuffer, vertexBuffer, topologyVector);
            }
            /* Case when the no Polygon outlines are encoded in the tile */
            return createConstGpuVector(numFeatures, geometryType, triangleOffsets, indexBuffer, vertexBuffer);
        }
        return mortonSettings === undefined
            ? /* Currently only 2D coordinates (Vec2) are implemented in the encoder  */
                createConstGeometryVector(numFeatures, geometryType, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer)
            : createMortonEncodedConstGeometryVector(numFeatures, geometryType, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer, mortonSettings);
    }
    /* Different geometry types are mixed in the geometry column */
    const geometryTypeVector = decodeUnsignedInt32Stream(tile, offset, geometryTypeMetadata);
    // Variables for flat geometry path (decoded as lengths, then converted to offsets)
    let geometryLengths;
    let partLengths;
    let ringLengths;
    //TODO: use geometryOffsets for that? -> but then tessellated polygons can't be used with normal polygons
    // in one FeatureTable?
    let triangleOffsets;
    for (let i = 0; i < numStreams - 1; i++) {
        const geometryStreamMetadata = decodeStreamMetadata(tile, offset);
        switch (geometryStreamMetadata.physicalStreamType) {
            case PhysicalStreamType.LENGTH:
                switch (geometryStreamMetadata.logicalStreamType.lengthType) {
                    case LengthType.GEOMETRIES:
                        geometryLengths = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                        break;
                    case LengthType.PARTS:
                        partLengths = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                        break;
                    case LengthType.RINGS:
                        ringLengths = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                        break;
                    case LengthType.TRIANGLES:
                        triangleOffsets = decodeLengthStreamToOffsetBuffer(tile, offset, geometryStreamMetadata);
                }
                break;
            case PhysicalStreamType.OFFSET:
                switch (geometryStreamMetadata.logicalStreamType.offsetType) {
                    case OffsetType.VERTEX:
                        vertexOffsets = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                        break;
                    case OffsetType.INDEX:
                        indexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata);
                        break;
                }
                break;
            case PhysicalStreamType.DATA:
                if (DictionaryType.VERTEX === geometryStreamMetadata.logicalStreamType.dictionaryType) {
                    vertexBuffer = decodeSignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData);
                }
                else {
                    const mortonMetadata = geometryStreamMetadata;
                    mortonSettings = {
                        numBits: mortonMetadata.numBits,
                        coordinateShift: mortonMetadata.coordinateShift,
                    };
                    vertexBuffer = decodeUnsignedInt32Stream(tile, offset, geometryStreamMetadata, scalingData);
                }
                break;
        }
    }
    // TODO: refactor the following instructions -> decode in one pass for performance reasons
    /* Calculate the offsets from the length buffer for util access */
    let geometryOffsets;
    let partOffsets;
    let ringOffsets;
    if (geometryLengths) {
        geometryOffsets = decodeRootLengthStream(geometryTypeVector, geometryLengths, 2);
        if (partLengths && ringLengths) {
            partOffsets = decodeLevel1LengthStream(geometryTypeVector, geometryOffsets, partLengths, false);
            ringOffsets = decodeLevel2LengthStream(geometryTypeVector, geometryOffsets, partOffsets, ringLengths);
        }
        else if (partLengths) {
            partOffsets = decodeLevel1WithoutRingBufferLengthStream(geometryTypeVector, geometryOffsets, partLengths);
        }
    }
    else if (partLengths && ringLengths) {
        partOffsets = decodeRootLengthStream(geometryTypeVector, partLengths, 1);
        ringOffsets = decodeLevel1LengthStream(geometryTypeVector, partOffsets, ringLengths, true);
    }
    else if (partLengths) {
        partOffsets = decodeRootLengthStream(geometryTypeVector, partLengths, 0);
    }
    if (indexBuffer && !partOffsets) {
        /* Case when the indices of a Polygon outline are not encoded in the data so no
         *  topology data are present in the tile */
        return createFlatGpuVector(geometryTypeVector, triangleOffsets, indexBuffer, vertexBuffer);
    }
    if (indexBuffer) {
        /* Case when the indices of a Polygon outline are encoded in the tile */
        return createFlatGpuVector(geometryTypeVector, triangleOffsets, indexBuffer, vertexBuffer, {
            geometryOffsets,
            partOffsets,
            ringOffsets,
        });
    }
    return mortonSettings === undefined /* Currently only 2D coordinates (Vec2) are implemented in the encoder  */
        ? createFlatGeometryVector(geometryTypeVector, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer)
        : createFlatGeometryVectorMortonEncoded(geometryTypeVector, { geometryOffsets, partOffsets, ringOffsets }, vertexOffsets, vertexBuffer, mortonSettings);
}
/*
 * Handle the parsing of the different topology length buffers separate not generic to reduce the
 * branching and improve the performance
 */
function decodeRootLengthStream(geometryTypes, rootLengthStream, bufferId) {
    const rootBufferOffsets = new Uint32Array(geometryTypes.length + 1);
    let previousOffset = 0;
    rootBufferOffsets[0] = previousOffset;
    let rootLengthCounter = 0;
    for (let i = 0; i < geometryTypes.length; i++) {
        /* Test if the geometry has and entry in the root buffer
         * BufferId: 2 GeometryOffsets -> MultiPolygon, MultiLineString, MultiPoint
         * BufferId: 1 PartOffsets -> Polygon
         * BufferId: 0 PartOffsets, RingOffsets -> LineString
         * */
        previousOffset = rootBufferOffsets[i + 1] =
            previousOffset + (geometryTypes[i] > bufferId ? rootLengthStream[rootLengthCounter++] : 1);
    }
    return rootBufferOffsets;
}
function decodeLevel1LengthStream(geometryTypes, rootOffsetBuffer, level1LengthBuffer, isLineStringPresent) {
    const level1BufferOffsets = new Uint32Array(rootOffsetBuffer[rootOffsetBuffer.length - 1] + 1);
    let previousOffset = 0;
    level1BufferOffsets[0] = previousOffset;
    let level1BufferCounter = 1;
    let level1LengthBufferCounter = 0;
    for (let i = 0; i < geometryTypes.length; i++) {
        const geometryType = geometryTypes[i];
        const numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i];
        if (geometryType === 5 ||
            geometryType === 2 ||
            (isLineStringPresent && (geometryType === 4 || geometryType === 1))) {
            /* For MultiPolygon, Polygon and in some cases for MultiLineString and LineString
             * a value in the level1LengthBuffer exists */
            for (let j = 0; j < numGeometries; j++) {
                previousOffset = level1BufferOffsets[level1BufferCounter++] =
                    previousOffset + level1LengthBuffer[level1LengthBufferCounter++];
            }
        }
        else {
            /* For MultiPoint and Point and in some cases for MultiLineString and LineString no value in the
             * level1LengthBuffer exists */
            for (let j = 0; j < numGeometries; j++) {
                level1BufferOffsets[level1BufferCounter++] = ++previousOffset;
            }
        }
    }
    return level1BufferOffsets;
}
/*
 * Case where no ring buffer exists so no MultiPolygon or Polygon geometry is part of the buffer
 */
function decodeLevel1WithoutRingBufferLengthStream(geometryTypes, rootOffsetBuffer, level1LengthBuffer) {
    const level1BufferOffsets = new Uint32Array(rootOffsetBuffer[rootOffsetBuffer.length - 1] + 1);
    let previousOffset = 0;
    level1BufferOffsets[0] = previousOffset;
    let level1OffsetBufferCounter = 1;
    let level1LengthCounter = 0;
    for (let i = 0; i < geometryTypes.length; i++) {
        const geometryType = geometryTypes[i];
        const numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i];
        if (geometryType === 4 || geometryType === 1) {
            /* For MultiLineString and LineString a value in the level1LengthBuffer exists */
            for (let j = 0; j < numGeometries; j++) {
                previousOffset = level1BufferOffsets[level1OffsetBufferCounter++] =
                    previousOffset + level1LengthBuffer[level1LengthCounter++];
            }
        }
        else {
            /* For MultiPoint and Point no value in level1LengthBuffer exists */
            for (let j = 0; j < numGeometries; j++) {
                level1BufferOffsets[level1OffsetBufferCounter++] = ++previousOffset;
            }
        }
    }
    return level1BufferOffsets;
}
function decodeLevel2LengthStream(geometryTypes, rootOffsetBuffer, level1OffsetBuffer, level2LengthBuffer) {
    const level2BufferOffsets = new Uint32Array(level1OffsetBuffer[level1OffsetBuffer.length - 1] + 1);
    let previousOffset = 0;
    level2BufferOffsets[0] = previousOffset;
    let level1OffsetBufferCounter = 1;
    let level2OffsetBufferCounter = 1;
    let level2LengthBufferCounter = 0;
    for (let i = 0; i < geometryTypes.length; i++) {
        const geometryType = geometryTypes[i];
        const numGeometries = rootOffsetBuffer[i + 1] - rootOffsetBuffer[i];
        if (geometryType !== 0 && geometryType !== 3) {
            /* For MultiPolygon, MultiLineString, Polygon and LineString a value in level2LengthBuffer
             * exists */
            for (let j = 0; j < numGeometries; j++) {
                const numParts = level1OffsetBuffer[level1OffsetBufferCounter] - level1OffsetBuffer[level1OffsetBufferCounter - 1];
                level1OffsetBufferCounter++;
                for (let k = 0; k < numParts; k++) {
                    previousOffset = level2BufferOffsets[level2OffsetBufferCounter++] =
                        previousOffset + level2LengthBuffer[level2LengthBufferCounter++];
                }
            }
        }
        else {
            /* For MultiPoint and Point no value in level2LengthBuffer exists */
            for (let j = 0; j < numGeometries; j++) {
                level2BufferOffsets[level2OffsetBufferCounter++] = ++previousOffset;
                level1OffsetBufferCounter++;
            }
        }
    }
    return level2BufferOffsets;
}
//# sourceMappingURL=geometryDecoder.js.map