import AttributeCompression from "../Core/AttributeCompression.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import CesiumMath from "../Core/Math.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";

var scratchCenter = new Cartesian3();
var scratchEllipsoid = new Ellipsoid();
var scratchRectangle = new Rectangle();
var scratchScalars = {
  min: undefined,
  max: undefined,
  indexBytesPerElement: undefined,
};

function unpackBuffer(buffer) {
  var packedBuffer = new Float64Array(buffer);

  var offset = 0;
  scratchScalars.indexBytesPerElement = packedBuffer[offset++];

  scratchScalars.min = packedBuffer[offset++];
  scratchScalars.max = packedBuffer[offset++];

  Cartesian3.unpack(packedBuffer, offset, scratchCenter);
  offset += Cartesian3.packedLength;

  Ellipsoid.unpack(packedBuffer, offset, scratchEllipsoid);
  offset += Ellipsoid.packedLength;

  Rectangle.unpack(packedBuffer, offset, scratchRectangle);
}

function packedBatchedIndicesLength(batchedIndices) {
  var length = batchedIndices.length;
  var count = 0;
  for (var i = 0; i < length; ++i) {
    count += Color.packedLength + 3 + batchedIndices[i].batchIds.length;
  }
  return count;
}

function packBuffer(indexDatatype, boundingVolumes, batchedIndices) {
  var numBVs = boundingVolumes.length;
  var length =
    1 +
    1 +
    numBVs * OrientedBoundingBox.packedLength +
    1 +
    packedBatchedIndicesLength(batchedIndices);

  var packedBuffer = new Float64Array(length);

  var offset = 0;
  packedBuffer[offset++] = indexDatatype;
  packedBuffer[offset++] = numBVs;

  for (var i = 0; i < numBVs; ++i) {
    OrientedBoundingBox.pack(boundingVolumes[i], packedBuffer, offset);
    offset += OrientedBoundingBox.packedLength;
  }

  var indicesLength = batchedIndices.length;
  packedBuffer[offset++] = indicesLength;

  for (var j = 0; j < indicesLength; ++j) {
    var batchedIndex = batchedIndices[j];

    Color.pack(batchedIndex.color, packedBuffer, offset);
    offset += Color.packedLength;

    packedBuffer[offset++] = batchedIndex.offset;
    packedBuffer[offset++] = batchedIndex.count;

    var batchIds = batchedIndex.batchIds;
    var batchIdsLength = batchIds.length;
    packedBuffer[offset++] = batchIdsLength;

    for (var k = 0; k < batchIdsLength; ++k) {
      packedBuffer[offset++] = batchIds[k];
    }
  }

  return packedBuffer;
}

var maxShort = 32767;

var scratchEncodedPosition = new Cartesian3();
var scratchNormal = new Cartesian3();
var scratchScaledNormal = new Cartesian3();
var scratchMinHeightPosition = new Cartesian3();
var scratchMaxHeightPosition = new Cartesian3();
var scratchBVCartographic = new Cartographic();
var scratchBVRectangle = new Rectangle();

function createVectorTilePolygons(parameters, transferableObjects) {
  unpackBuffer(parameters.packedBuffer);

  var indices;
  var indexBytesPerElement = scratchScalars.indexBytesPerElement;
  if (indexBytesPerElement === 2) {
    indices = new Uint16Array(parameters.indices);
  } else {
    indices = new Uint32Array(parameters.indices);
  }

  var positions = new Uint16Array(parameters.positions);
  var counts = new Uint32Array(parameters.counts);
  var indexCounts = new Uint32Array(parameters.indexCounts);
  var batchIds = new Uint32Array(parameters.batchIds);
  var batchTableColors = new Uint32Array(parameters.batchTableColors);

  var boundingVolumes = new Array(counts.length);

  var center = scratchCenter;
  var ellipsoid = scratchEllipsoid;
  var rectangle = scratchRectangle;
  var minHeight = scratchScalars.min;
  var maxHeight = scratchScalars.max;

  var minimumHeights = parameters.minimumHeights;
  var maximumHeights = parameters.maximumHeights;
  if (defined(minimumHeights) && defined(maximumHeights)) {
    minimumHeights = new Float32Array(minimumHeights);
    maximumHeights = new Float32Array(maximumHeights);
  }

  var i;
  var j;
  var rgba;

  var positionsLength = positions.length / 2;
  var uBuffer = positions.subarray(0, positionsLength);
  var vBuffer = positions.subarray(positionsLength, 2 * positionsLength);
  AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer);

  var decodedPositions = new Float64Array(positionsLength * 3);
  for (i = 0; i < positionsLength; ++i) {
    var u = uBuffer[i];
    var v = vBuffer[i];

    var x = CesiumMath.lerp(rectangle.west, rectangle.east, u / maxShort);
    var y = CesiumMath.lerp(rectangle.south, rectangle.north, v / maxShort);

    var cart = Cartographic.fromRadians(x, y, 0.0, scratchBVCartographic);
    var decodedPosition = ellipsoid.cartographicToCartesian(
      cart,
      scratchEncodedPosition
    );
    Cartesian3.pack(decodedPosition, decodedPositions, i * 3);
  }

  var countsLength = counts.length;
  var offsets = new Array(countsLength);
  var indexOffsets = new Array(countsLength);
  var currentOffset = 0;
  var currentIndexOffset = 0;
  for (i = 0; i < countsLength; ++i) {
    offsets[i] = currentOffset;
    indexOffsets[i] = currentIndexOffset;

    currentOffset += counts[i];
    currentIndexOffset += indexCounts[i];
  }

  var batchedPositions = new Float32Array(positionsLength * 3 * 2);
  var batchedIds = new Uint16Array(positionsLength * 2);
  var batchedIndexOffsets = new Uint32Array(indexOffsets.length);
  var batchedIndexCounts = new Uint32Array(indexCounts.length);
  var batchedIndices = [];

  var colorToBuffers = {};
  for (i = 0; i < countsLength; ++i) {
    rgba = batchTableColors[i];
    if (!defined(colorToBuffers[rgba])) {
      colorToBuffers[rgba] = {
        positionLength: counts[i],
        indexLength: indexCounts[i],
        offset: 0,
        indexOffset: 0,
        batchIds: [i],
      };
    } else {
      colorToBuffers[rgba].positionLength += counts[i];
      colorToBuffers[rgba].indexLength += indexCounts[i];
      colorToBuffers[rgba].batchIds.push(i);
    }
  }

  // get the offsets and counts for the positions and indices of each primitive
  var buffer;
  var byColorPositionOffset = 0;
  var byColorIndexOffset = 0;
  for (rgba in colorToBuffers) {
    if (colorToBuffers.hasOwnProperty(rgba)) {
      buffer = colorToBuffers[rgba];
      buffer.offset = byColorPositionOffset;
      buffer.indexOffset = byColorIndexOffset;

      var positionLength = buffer.positionLength * 2;
      var indexLength = buffer.indexLength * 2 + buffer.positionLength * 6;

      byColorPositionOffset += positionLength;
      byColorIndexOffset += indexLength;

      buffer.indexLength = indexLength;
    }
  }

  var batchedDrawCalls = [];

  for (rgba in colorToBuffers) {
    if (colorToBuffers.hasOwnProperty(rgba)) {
      buffer = colorToBuffers[rgba];

      batchedDrawCalls.push({
        color: Color.fromRgba(parseInt(rgba)),
        offset: buffer.indexOffset,
        count: buffer.indexLength,
        batchIds: buffer.batchIds,
      });
    }
  }

  for (i = 0; i < countsLength; ++i) {
    rgba = batchTableColors[i];

    buffer = colorToBuffers[rgba];
    var positionOffset = buffer.offset;
    var positionIndex = positionOffset * 3;
    var batchIdIndex = positionOffset;

    var polygonOffset = offsets[i];
    var polygonCount = counts[i];
    var batchId = batchIds[i];

    var polygonMinimumHeight = minHeight;
    var polygonMaximumHeight = maxHeight;
    if (defined(minimumHeights) && defined(maximumHeights)) {
      polygonMinimumHeight = minimumHeights[i];
      polygonMaximumHeight = maximumHeights[i];
    }

    var minLat = Number.POSITIVE_INFINITY;
    var maxLat = Number.NEGATIVE_INFINITY;
    var minLon = Number.POSITIVE_INFINITY;
    var maxLon = Number.NEGATIVE_INFINITY;

    for (j = 0; j < polygonCount; ++j) {
      var position = Cartesian3.unpack(
        decodedPositions,
        polygonOffset * 3 + j * 3,
        scratchEncodedPosition
      );
      ellipsoid.scaleToGeodeticSurface(position, position);

      var carto = ellipsoid.cartesianToCartographic(
        position,
        scratchBVCartographic
      );
      var lat = carto.latitude;
      var lon = carto.longitude;

      minLat = Math.min(lat, minLat);
      maxLat = Math.max(lat, maxLat);
      minLon = Math.min(lon, minLon);
      maxLon = Math.max(lon, maxLon);

      var normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal);
      var scaledNormal = Cartesian3.multiplyByScalar(
        normal,
        polygonMinimumHeight,
        scratchScaledNormal
      );
      var minHeightPosition = Cartesian3.add(
        position,
        scaledNormal,
        scratchMinHeightPosition
      );

      scaledNormal = Cartesian3.multiplyByScalar(
        normal,
        polygonMaximumHeight,
        scaledNormal
      );
      var maxHeightPosition = Cartesian3.add(
        position,
        scaledNormal,
        scratchMaxHeightPosition
      );

      Cartesian3.subtract(maxHeightPosition, center, maxHeightPosition);
      Cartesian3.subtract(minHeightPosition, center, minHeightPosition);

      Cartesian3.pack(maxHeightPosition, batchedPositions, positionIndex);
      Cartesian3.pack(minHeightPosition, batchedPositions, positionIndex + 3);

      batchedIds[batchIdIndex] = batchId;
      batchedIds[batchIdIndex + 1] = batchId;

      positionIndex += 6;
      batchIdIndex += 2;
    }

    rectangle = scratchBVRectangle;
    rectangle.west = minLon;
    rectangle.east = maxLon;
    rectangle.south = minLat;
    rectangle.north = maxLat;

    boundingVolumes[i] = OrientedBoundingBox.fromRectangle(
      rectangle,
      minHeight,
      maxHeight,
      ellipsoid
    );

    var indicesIndex = buffer.indexOffset;

    var indexOffset = indexOffsets[i];
    var indexCount = indexCounts[i];

    batchedIndexOffsets[i] = indicesIndex;

    for (j = 0; j < indexCount; j += 3) {
      var i0 = indices[indexOffset + j] - polygonOffset;
      var i1 = indices[indexOffset + j + 1] - polygonOffset;
      var i2 = indices[indexOffset + j + 2] - polygonOffset;

      // triangle on the top of the extruded polygon
      batchedIndices[indicesIndex++] = i0 * 2 + positionOffset;
      batchedIndices[indicesIndex++] = i1 * 2 + positionOffset;
      batchedIndices[indicesIndex++] = i2 * 2 + positionOffset;

      // triangle on the bottom of the extruded polygon
      batchedIndices[indicesIndex++] = i2 * 2 + 1 + positionOffset;
      batchedIndices[indicesIndex++] = i1 * 2 + 1 + positionOffset;
      batchedIndices[indicesIndex++] = i0 * 2 + 1 + positionOffset;
    }

    // indices for the walls of the extruded polygon
    for (j = 0; j < polygonCount; ++j) {
      var v0 = j;
      var v1 = (j + 1) % polygonCount;

      batchedIndices[indicesIndex++] = v0 * 2 + 1 + positionOffset;
      batchedIndices[indicesIndex++] = v1 * 2 + positionOffset;
      batchedIndices[indicesIndex++] = v0 * 2 + positionOffset;

      batchedIndices[indicesIndex++] = v0 * 2 + 1 + positionOffset;
      batchedIndices[indicesIndex++] = v1 * 2 + 1 + positionOffset;
      batchedIndices[indicesIndex++] = v1 * 2 + positionOffset;
    }

    buffer.offset += polygonCount * 2;
    buffer.indexOffset = indicesIndex;

    batchedIndexCounts[i] = indicesIndex - batchedIndexOffsets[i];
  }

  batchedIndices = IndexDatatype.createTypedArray(
    batchedPositions.length / 3,
    batchedIndices
  );

  var batchedIndicesLength = batchedDrawCalls.length;
  for (var m = 0; m < batchedIndicesLength; ++m) {
    var tempIds = batchedDrawCalls[m].batchIds;
    var count = 0;
    var tempIdsLength = tempIds.length;
    for (var n = 0; n < tempIdsLength; ++n) {
      count += batchedIndexCounts[tempIds[n]];
    }
    batchedDrawCalls[m].count = count;
  }

  var indexDatatype =
    batchedIndices.BYTES_PER_ELEMENT === 2
      ? IndexDatatype.UNSIGNED_SHORT
      : IndexDatatype.UNSIGNED_INT;
  var packedBuffer = packBuffer(
    indexDatatype,
    boundingVolumes,
    batchedDrawCalls
  );

  transferableObjects.push(
    batchedPositions.buffer,
    batchedIndices.buffer,
    batchedIndexOffsets.buffer,
    batchedIndexCounts.buffer,
    batchedIds.buffer,
    packedBuffer.buffer
  );

  return {
    positions: batchedPositions.buffer,
    indices: batchedIndices.buffer,
    indexOffsets: batchedIndexOffsets.buffer,
    indexCounts: batchedIndexCounts.buffer,
    batchIds: batchedIds.buffer,
    packedBuffer: packedBuffer.buffer,
  };
}
export default createTaskProcessorWorker(createVectorTilePolygons);
