import { arrayFill } from "../Source/Cesium.js";
import { Color } from "../Source/Cesium.js";
import { defaultValue } from "../Source/Cesium.js";
import { defined } from "../Source/Cesium.js";
import { JulianDate } from "../Source/Cesium.js";
import { Resource } from "../Source/Cesium.js";
import { Cesium3DTileContentFactory } from "../Source/Cesium.js";
import { Cesium3DTileset } from "../Source/Cesium.js";
import { TileBoundingSphere } from "../Source/Cesium.js";
import pollToPromise from "./pollToPromise.js";

var mockTile = {
  contentBoundingVolume: new TileBoundingSphere(),
  _contentBoundingVolume: new TileBoundingSphere(),
  _header: {
    content: {
      boundingVolume: {
        sphere: [0.0, 0.0, 0.0, 1.0],
      },
    },
  },
};

function Cesium3DTilesTester() {}

function padStringToByteAlignment(string, byteAlignment) {
  var length = string.length;
  var paddedLength = Math.ceil(length / byteAlignment) * byteAlignment; // Round up to the required alignment
  var padding = paddedLength - length;
  var whitespace = "";
  for (var i = 0; i < padding; ++i) {
    whitespace += " ";
  }
  return string + whitespace;
}

var time = new JulianDate(2457522.0);

Cesium3DTilesTester.expectRender = function (scene, tileset, callback) {
  var renderOptions = {
    scene: scene,
    time: time,
  };
  tileset.show = false;
  expect(renderOptions).toRender([0, 0, 0, 255]);
  tileset.show = true;
  expect(renderOptions).toRenderAndCall(function (rgba) {
    expect(rgba).not.toEqual([0, 0, 0, 255]);
    if (defined(callback)) {
      callback(rgba);
    }
  });
};

Cesium3DTilesTester.expectRenderBlank = function (scene, tileset) {
  var renderOptions = {
    scene: scene,
    time: time,
  };
  tileset.show = false;
  expect(renderOptions).toRender([0, 0, 0, 255]);
  tileset.show = true;
  expect(renderOptions).toRender([0, 0, 0, 255]);
};

Cesium3DTilesTester.expectRenderTileset = function (scene, tileset) {
  // Verify render before being picked
  Cesium3DTilesTester.expectRender(scene, tileset);

  // Pick a feature
  expect(scene).toPickAndCall(function (result) {
    expect(result).toBeDefined();

    // Change the color of the picked feature to yellow
    result.color = Color.clone(Color.YELLOW, result.color);

    // Expect the pixel color to be some shade of yellow
    Cesium3DTilesTester.expectRender(scene, tileset, function (rgba) {
      expect(rgba[0]).toBeGreaterThan(0);
      expect(rgba[1]).toBeGreaterThan(0);
      expect(rgba[2]).toEqual(0);
      expect(rgba[3]).toEqual(255);
    });

    // Turn show off and on
    result.show = false;
    Cesium3DTilesTester.expectRenderBlank(scene, tileset);
    result.show = true;
    Cesium3DTilesTester.expectRender(scene, tileset);
  });
};

Cesium3DTilesTester.waitForTilesLoaded = function (scene, tileset) {
  return pollToPromise(function () {
    scene.renderForSpecs();
    return tileset.tilesLoaded;
  }).then(function () {
    scene.renderForSpecs();
    return tileset;
  });
};

Cesium3DTilesTester.waitForReady = function (scene, tileset) {
  return pollToPromise(function () {
    scene.renderForSpecs();
    return tileset.ready;
  }).then(function () {
    return tileset;
  });
};

Cesium3DTilesTester.loadTileset = function (scene, url, options) {
  options = defaultValue(options, {});
  options.url = url;
  options.cullRequestsWhileMoving = defaultValue(
    options.cullRequestsWhileMoving,
    false
  );
  // Load all visible tiles
  var tileset = scene.primitives.add(new Cesium3DTileset(options));

  return Cesium3DTilesTester.waitForTilesLoaded(scene, tileset);
};

Cesium3DTilesTester.loadTileExpectError = function (scene, arrayBuffer, type) {
  var tileset = {};
  var url = Resource.createIfNeeded("");
  expect(function () {
    return Cesium3DTileContentFactory[type](
      tileset,
      mockTile,
      url,
      arrayBuffer,
      0
    );
  }).toThrowRuntimeError();
};

Cesium3DTilesTester.loadTile = function (scene, arrayBuffer, type) {
  var tileset = {
    _statistics: {
      batchTableByteLength: 0,
    },
    root: {},
  };
  var url = Resource.createIfNeeded("");
  var content = Cesium3DTileContentFactory[type](
    tileset,
    mockTile,
    url,
    arrayBuffer,
    0
  );
  content.update(tileset, scene.frameState);
  return content;
};

// Use counter to prevent models from sharing the same cache key,
// this fixes tests that load a model with the same invalid url
var counter = 0;
Cesium3DTilesTester.rejectsReadyPromiseOnError = function (
  scene,
  arrayBuffer,
  type
) {
  var tileset = {
    basePath: counter++,
    _statistics: {
      batchTableByteLength: 0,
    },
  };
  var url = Resource.createIfNeeded("");
  var content = Cesium3DTileContentFactory[type](
    tileset,
    mockTile,
    url,
    arrayBuffer,
    0
  );
  content.update(tileset, scene.frameState);

  return content.readyPromise
    .then(function (content) {
      fail("should not resolve");
    })
    .otherwise(function (error) {
      expect(error).toBeDefined();
    });
};

Cesium3DTilesTester.resolvesReadyPromise = function (scene, url, options) {
  return Cesium3DTilesTester.loadTileset(scene, url, options).then(function (
    tileset
  ) {
    var content = tileset.root.content;
    return content.readyPromise.then(function (content) {
      expect(content).toBeDefined();
    });
  });
};

Cesium3DTilesTester.tileDestroys = function (scene, url, options) {
  return Cesium3DTilesTester.loadTileset(scene, url, options).then(function (
    tileset
  ) {
    var content = tileset.root.content;
    expect(content.isDestroyed()).toEqual(false);
    scene.primitives.remove(tileset);
    expect(content.isDestroyed()).toEqual(true);
  });
};

Cesium3DTilesTester.generateBatchedTileBuffer = function (options) {
  // Procedurally generate the tile array buffer for testing purposes
  options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  var magic = defaultValue(options.magic, [98, 51, 100, 109]);
  var version = defaultValue(options.version, 1);
  var featuresLength = defaultValue(options.featuresLength, 1);
  var featureTableJson = {
    BATCH_LENGTH: featuresLength,
  };
  var featureTableJsonString = JSON.stringify(featureTableJson);
  var featureTableJsonByteLength = featureTableJsonString.length;

  var headerByteLength = 28;
  var byteLength = headerByteLength + featureTableJsonByteLength;
  var buffer = new ArrayBuffer(byteLength);
  var view = new DataView(buffer);
  view.setUint8(0, magic[0]);
  view.setUint8(1, magic[1]);
  view.setUint8(2, magic[2]);
  view.setUint8(3, magic[3]);
  view.setUint32(4, version, true); // version
  view.setUint32(8, byteLength, true); // byteLength
  view.setUint32(12, featureTableJsonByteLength, true); // featureTableJsonByteLength
  view.setUint32(16, 0, true); // featureTableBinaryByteLength
  view.setUint32(20, 0, true); // batchTableJsonByteLength
  view.setUint32(24, 0, true); // batchTableBinaryByteLength

  var i;
  var byteOffset = headerByteLength;
  for (i = 0; i < featureTableJsonByteLength; i++) {
    view.setUint8(byteOffset, featureTableJsonString.charCodeAt(i));
    byteOffset++;
  }

  return buffer;
};

Cesium3DTilesTester.generateInstancedTileBuffer = function (options) {
  // Procedurally generate the tile array buffer for testing purposes
  options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  var magic = defaultValue(options.magic, [105, 51, 100, 109]);
  var version = defaultValue(options.version, 1);

  var gltfFormat = defaultValue(options.gltfFormat, 1);
  var gltfUri = defaultValue(options.gltfUri, "");
  var gltfUriByteLength = gltfUri.length;

  var featuresLength = defaultValue(options.featuresLength, 1);
  var featureTableJson = {
    INSTANCES_LENGTH: featuresLength,
    POSITION: arrayFill(new Array(featuresLength * 3), 0),
  };
  var featureTableJsonString = JSON.stringify(featureTableJson);
  var featureTableJsonByteLength = featureTableJsonString.length;

  var headerByteLength = 32;
  var uriByteLength = gltfUri.length;
  var byteLength =
    headerByteLength + featureTableJsonByteLength + uriByteLength;
  var buffer = new ArrayBuffer(byteLength);
  var view = new DataView(buffer);
  view.setUint8(0, magic[0]);
  view.setUint8(1, magic[1]);
  view.setUint8(2, magic[2]);
  view.setUint8(3, magic[3]);
  view.setUint32(4, version, true); // version
  view.setUint32(8, byteLength, true); // byteLength
  view.setUint32(12, featureTableJsonByteLength, true); // featureTableJsonByteLength
  view.setUint32(16, 0, true); // featureTableBinaryByteLength
  view.setUint32(20, 0, true); // batchTableJsonByteLength
  view.setUint32(24, 0, true); // batchTableBinaryByteLength
  view.setUint32(28, gltfFormat, true); // gltfFormat

  var i;
  var byteOffset = headerByteLength;
  for (i = 0; i < featureTableJsonByteLength; i++) {
    view.setUint8(byteOffset, featureTableJsonString.charCodeAt(i));
    byteOffset++;
  }
  for (i = 0; i < gltfUriByteLength; i++) {
    view.setUint8(byteOffset, gltfUri.charCodeAt(i));
    byteOffset++;
  }
  return buffer;
};

Cesium3DTilesTester.generatePointCloudTileBuffer = function (options) {
  // Procedurally generate the tile array buffer for testing purposes
  options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  var magic = defaultValue(options.magic, [112, 110, 116, 115]);
  var version = defaultValue(options.version, 1);
  var featureTableJson = options.featureTableJson;
  if (!defined(featureTableJson)) {
    featureTableJson = {
      POINTS_LENGTH: 1,
      POSITIONS: {
        byteOffset: 0,
      },
    };
  }

  var featureTableJsonString = JSON.stringify(featureTableJson);
  featureTableJsonString = padStringToByteAlignment(featureTableJsonString, 4);
  var featureTableJsonByteLength = defaultValue(
    options.featureTableJsonByteLength,
    featureTableJsonString.length
  );

  var featureTableBinary = new ArrayBuffer(12); // Enough space to hold 3 floats
  var featureTableBinaryByteLength = featureTableBinary.byteLength;

  var headerByteLength = 28;
  var byteLength =
    headerByteLength +
    featureTableJsonByteLength +
    featureTableBinaryByteLength;
  var buffer = new ArrayBuffer(byteLength);
  var view = new DataView(buffer);
  view.setUint8(0, magic[0]);
  view.setUint8(1, magic[1]);
  view.setUint8(2, magic[2]);
  view.setUint8(3, magic[3]);
  view.setUint32(4, version, true); // version
  view.setUint32(8, byteLength, true); // byteLength
  view.setUint32(12, featureTableJsonByteLength, true); // featureTableJsonByteLength
  view.setUint32(16, featureTableBinaryByteLength, true); // featureTableBinaryByteLength
  view.setUint32(20, 0, true); // batchTableJsonByteLength
  view.setUint32(24, 0, true); // batchTableBinaryByteLength

  var i;
  var byteOffset = headerByteLength;
  for (i = 0; i < featureTableJsonByteLength; i++) {
    view.setUint8(byteOffset, featureTableJsonString.charCodeAt(i));
    byteOffset++;
  }
  for (i = 0; i < featureTableBinaryByteLength; i++) {
    view.setUint8(byteOffset, featureTableBinary[i]);
    byteOffset++;
  }
  return buffer;
};

Cesium3DTilesTester.generateCompositeTileBuffer = function (options) {
  // Procedurally generate the tile array buffer for testing purposes
  options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  var magic = defaultValue(options.magic, [99, 109, 112, 116]);
  var version = defaultValue(options.version, 1);
  var tiles = defaultValue(options.tiles, []);
  var tilesLength = tiles.length;

  var i;
  var tilesByteLength = 0;
  for (i = 0; i < tilesLength; ++i) {
    tilesByteLength += tiles[i].byteLength;
  }

  var headerByteLength = 16;
  var byteLength = headerByteLength + tilesByteLength;
  var buffer = new ArrayBuffer(byteLength);
  var uint8Array = new Uint8Array(buffer);
  var view = new DataView(buffer);
  view.setUint8(0, magic[0]);
  view.setUint8(1, magic[1]);
  view.setUint8(2, magic[2]);
  view.setUint8(3, magic[3]);
  view.setUint32(4, version, true); // version
  view.setUint32(8, byteLength, true); // byteLength
  view.setUint32(12, tilesLength, true); // tilesLength

  var byteOffset = headerByteLength;
  for (i = 0; i < tilesLength; ++i) {
    var tile = new Uint8Array(tiles[i]);
    uint8Array.set(tile, byteOffset);
    byteOffset += tile.byteLength;
  }

  return buffer;
};

Cesium3DTilesTester.generateVectorTileBuffer = function (options) {
  // Procedurally generate the tile array buffer for testing purposes
  options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  var magic = defaultValue(options.magic, [118, 99, 116, 114]);
  var version = defaultValue(options.version, 1);

  var featureTableJsonString;
  var featureTableJsonByteLength = 0;
  var defineFeatureTable = defaultValue(options.defineFeatureTable, true);
  if (defineFeatureTable) {
    var defineRegion = defaultValue(options.defineRegion, true);
    var featureTableJson = {
      REGION: defineRegion ? [-1.0, -1.0, 1.0, 1.0, -1.0, 1.0] : undefined,
      POLYGONS_LENGTH: defaultValue(options.polygonsLength, 0),
      POLYLINES_LENGTH: defaultValue(options.polylinesLength, 0),
      POINTS_LENGTH: defaultValue(options.pointsLength, 0),
      POLYGON_BATCH_IDS: options.polygonBatchIds,
      POLYLINE_BATCH_IDS: options.polylineBatchIds,
      POINT_BATCH_IDS: options.pointBatchIds,
    };
    featureTableJsonString = JSON.stringify(featureTableJson);
    featureTableJsonByteLength = featureTableJsonString.length;
  }

  var headerByteLength = 44;
  var byteLength = headerByteLength + featureTableJsonByteLength;
  var buffer = new ArrayBuffer(byteLength);
  var view = new DataView(buffer);
  view.setUint8(0, magic[0]);
  view.setUint8(1, magic[1]);
  view.setUint8(2, magic[2]);
  view.setUint8(3, magic[3]);
  view.setUint32(4, version, true); // version
  view.setUint32(8, byteLength, true); // byteLength
  view.setUint32(12, featureTableJsonByteLength, true); // featureTableJsonByteLength
  view.setUint32(16, 0, true); // featureTableBinaryByteLength
  view.setUint32(20, 0, true); // batchTableJsonByteLength
  view.setUint32(24, 0, true); // batchTableBinaryByteLength
  view.setUint32(28, 0, true); // indicesByteLength
  view.setUint32(32, 0, true); // polygonPositionByteLength
  view.setUint32(36, 0, true); // polylinePositionByteLength
  view.setUint32(40, 0, true); // pointsPositionByteLength

  var i;
  var byteOffset = headerByteLength;
  for (i = 0; i < featureTableJsonByteLength; i++) {
    view.setUint8(byteOffset, featureTableJsonString.charCodeAt(i));
    byteOffset++;
  }

  return buffer;
};

Cesium3DTilesTester.generateGeometryTileBuffer = function (options) {
  // Procedurally generate the tile array buffer for testing purposes
  options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  var magic = defaultValue(options.magic, [103, 101, 111, 109]);
  var version = defaultValue(options.version, 1);

  var featureTableJsonString;
  var featureTableJsonByteLength = 0;
  var defineFeatureTable = defaultValue(options.defineFeatureTable, true);
  if (defineFeatureTable) {
    var featureTableJson = {
      BOXES_LENGTH: defaultValue(options.boxesLength, 0),
      CYLINDERS_LENGTH: defaultValue(options.cylindersLength, 0),
      ELLIPSOIDS_LENGTH: defaultValue(options.ellipsoidsLength, 0),
      SPHERES_LENGTH: defaultValue(options.spheresLength, 0),
      BOX_BATCH_IDS: options.boxBatchIds,
      CYLINDER_BATCH_IDS: options.cylinderBatchIds,
      ELLIPSOID_BATCH_IDS: options.ellipsoidBatchIds,
      SPHERE_BATCH_IDS: options.sphereBatchIds,
    };
    featureTableJsonString = JSON.stringify(featureTableJson);
    featureTableJsonByteLength = featureTableJsonString.length;
  }

  var headerByteLength = 28;
  var byteLength = headerByteLength + featureTableJsonByteLength;
  var buffer = new ArrayBuffer(byteLength);
  var view = new DataView(buffer);
  view.setUint8(0, magic[0]);
  view.setUint8(1, magic[1]);
  view.setUint8(2, magic[2]);
  view.setUint8(3, magic[3]);
  view.setUint32(4, version, true); // version
  view.setUint32(8, byteLength, true); // byteLength
  view.setUint32(12, featureTableJsonByteLength, true); // featureTableJsonByteLength
  view.setUint32(16, 0, true); // featureTableBinaryByteLength
  view.setUint32(20, 0, true); // batchTableJsonByteLength
  view.setUint32(24, 0, true); // batchTableBinaryByteLength

  var i;
  var byteOffset = headerByteLength;
  for (i = 0; i < featureTableJsonByteLength; i++) {
    view.setUint8(byteOffset, featureTableJsonString.charCodeAt(i));
    byteOffset++;
  }

  return buffer;
};

export default Cesium3DTilesTester;
