import * as Cesium from "cesium";

// Use clipping planes to selectively hide parts of the globe surface.
const viewer = new Cesium.Viewer("cesiumContainer", {
  skyAtmosphere: false,
  shouldAnimate: true,
  terrain: Cesium.Terrain.fromWorldTerrain(),
  scene3DOnly: true,
});
const globe = viewer.scene.globe;

const exampleTypes = ["Cesium Man", "St. Helens", "Grand Canyon Isolated"];
const viewModel = {
  exampleTypes: exampleTypes,
  currentExampleType: exampleTypes[0],
  clippingPlanesEnabled: true,
  edgeStylingEnabled: true,
};
const toolbar = document.getElementById("toolbar");
Cesium.knockout.track(viewModel);
Cesium.knockout.applyBindings(viewModel, toolbar);

// For tracking state when switching exampleTypes
let clippingPlanesEnabled = true;
let edgeStylingEnabled = true;

let tileset;

loadCesiumMan();

function reset() {
  viewer.entities.removeAll();
  viewer.scene.primitives.remove(tileset);
}

function loadCesiumMan() {
  const position = Cesium.Cartesian3.fromRadians(
    -2.0862979473351286,
    0.6586620013036164,
    1400.0,
  );

  const entity = viewer.entities.add({
    position: position,
    box: {
      dimensions: new Cesium.Cartesian3(1400.0, 1400.0, 2800.0),
      material: Cesium.Color.WHITE.withAlpha(0.3),
      outline: true,
      outlineColor: Cesium.Color.WHITE,
    },
  });

  viewer.entities.add({
    position: position,
    model: {
      uri: "../../SampleData/models/CesiumMan/Cesium_Man.glb",
      minimumPixelSize: 128,
      maximumScale: 800,
    },
  });

  globe.depthTestAgainstTerrain = true;
  globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
    modelMatrix: entity.computeModelMatrix(Cesium.JulianDate.now()),
    planes: [
      new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), -700.0),
      new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), -700.0),
      new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), -700.0),
      new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, -1.0, 0.0), -700.0),
    ],
    edgeWidth: edgeStylingEnabled ? 1.0 : 0.0,
    edgeColor: Cesium.Color.WHITE,
    enabled: clippingPlanesEnabled,
  });
  globe.backFaceCulling = true;
  globe.showSkirts = true;

  viewer.trackedEntity = entity;
}

async function loadStHelens() {
  // Create clipping planes for polygon around area to be clipped.
  const points = [
    new Cesium.Cartesian3(
      -2358434.3501556474,
      -3743554.5012105294,
      4581080.771684084,
    ),
    new Cesium.Cartesian3(
      -2357886.4482675144,
      -3744467.562778789,
      4581020.9199767085,
    ),
    new Cesium.Cartesian3(
      -2357299.84353055,
      -3744954.0879047974,
      4581080.992360969,
    ),
    new Cesium.Cartesian3(
      -2356412.05169956,
      -3745385.3013702347,
      4580893.4737207815,
    ),
    new Cesium.Cartesian3(
      -2355472.889436636,
      -3745256.5725702164,
      4581252.3128526565,
    ),
    new Cesium.Cartesian3(
      -2354385.7458722834,
      -3744319.3823686405,
      4582372.770031389,
    ),
    new Cesium.Cartesian3(
      -2353758.788158616,
      -3743051.0128084184,
      4583356.453176038,
    ),
    new Cesium.Cartesian3(
      -2353663.8128999653,
      -3741847.9126874236,
      4584079.428665509,
    ),
    new Cesium.Cartesian3(
      -2354213.667592133,
      -3740784.50946316,
      4584502.428203525,
    ),
    new Cesium.Cartesian3(
      -2355596.239450013,
      -3739901.0226732804,
      4584515.9652557485,
    ),
    new Cesium.Cartesian3(
      -2356942.4170108805,
      -3740342.454698685,
      4583686.690694482,
    ),
    new Cesium.Cartesian3(
      -2357529.554838029,
      -3740766.995076834,
      4583145.055348843,
    ),
    new Cesium.Cartesian3(
      -2358106.017822064,
      -3741439.438418052,
      4582452.293605261,
    ),
    new Cesium.Cartesian3(
      -2358539.5426236596,
      -3742680.720902901,
      4581692.0260975715,
    ),
  ];

  const pointsLength = points.length;

  // Create center points for each clipping plane
  const clippingPlanes = [];
  for (let i = 0; i < pointsLength; ++i) {
    const nextIndex = (i + 1) % pointsLength;
    let midpoint = Cesium.Cartesian3.add(
      points[i],
      points[nextIndex],
      new Cesium.Cartesian3(),
    );
    midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);

    const up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
    let right = Cesium.Cartesian3.subtract(
      points[nextIndex],
      midpoint,
      new Cesium.Cartesian3(),
    );
    right = Cesium.Cartesian3.normalize(right, right);

    let normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
    normal = Cesium.Cartesian3.normalize(normal, normal);

    // Compute distance by pretending the plane is at the origin
    const originCenteredPlane = new Cesium.Plane(normal, 0.0);
    const distance = Cesium.Plane.getPointDistance(
      originCenteredPlane,
      midpoint,
    );

    clippingPlanes.push(new Cesium.ClippingPlane(normal, distance));
  }
  globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
    planes: clippingPlanes,
    edgeWidth: edgeStylingEnabled ? 1.0 : 0.0,
    edgeColor: Cesium.Color.WHITE,
    enabled: clippingPlanesEnabled,
  });
  globe.backFaceCulling = true;
  globe.showSkirts = true;

  try {
    // Load tileset
    tileset = await Cesium.Cesium3DTileset.fromIonAssetId(5713);
    if (viewModel.currentExampleType !== exampleTypes[1]) {
      // Scenario has changed. Discard the result.
      return;
    }
    // Adjust height so tileset is in terrain
    const cartographic = Cesium.Cartographic.fromCartesian(
      tileset.boundingSphere.center,
    );
    const surface = Cesium.Cartesian3.fromRadians(
      cartographic.longitude,
      cartographic.latitude,
      0.0,
    );
    const offset = Cesium.Cartesian3.fromRadians(
      cartographic.longitude,
      cartographic.latitude,
      -20.0,
    );
    const translation = Cesium.Cartesian3.subtract(
      offset,
      surface,
      new Cesium.Cartesian3(),
    );
    tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);

    tileset.style = new Cesium.Cesium3DTileStyle({
      color: "rgb(207, 255, 207)",
    });

    viewer.scene.primitives.add(tileset);

    const boundingSphere = tileset.boundingSphere;

    const radius = boundingSphere.radius;
    viewer.camera.viewBoundingSphere(
      boundingSphere,
      new Cesium.HeadingPitchRange(0.5, -0.2, radius * 4.0),
    );
    viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
  } catch (error) {
    console.log(`Error loading tileset: ${error}`);
  }
}

function loadGrandCanyon() {
  // Pick a position at the Grand Canyon
  const position = Cesium.Cartographic.toCartesian(
    new Cesium.Cartographic.fromDegrees(-113.2665534, 36.0939345, 100),
  );
  const distance = 3000.0;
  const boundingSphere = new Cesium.BoundingSphere(position, distance);

  globe.clippingPlanes = new Cesium.ClippingPlaneCollection({
    modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(position),
    planes: [
      new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), distance),
      new Cesium.ClippingPlane(new Cesium.Cartesian3(-1.0, 0.0, 0.0), distance),
      new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), distance),
      new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, -1.0, 0.0), distance),
    ],
    unionClippingRegions: true,
    edgeWidth: edgeStylingEnabled ? 1.0 : 0.0,
    edgeColor: Cesium.Color.WHITE,
    enabled: clippingPlanesEnabled,
  });
  globe.backFaceCulling = false;
  globe.showSkirts = false;

  viewer.camera.viewBoundingSphere(
    boundingSphere,
    new Cesium.HeadingPitchRange(0.5, -0.5, boundingSphere.radius * 5.0),
  );
  viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
}

Cesium.knockout
  .getObservable(viewModel, "clippingPlanesEnabled")
  .subscribe(function (value) {
    globe.clippingPlanes.enabled = value;
    clippingPlanesEnabled = value;
  });

Cesium.knockout
  .getObservable(viewModel, "edgeStylingEnabled")
  .subscribe(function (value) {
    edgeStylingEnabled = value;
    globe.clippingPlanes.edgeWidth = edgeStylingEnabled ? 1.0 : 0.0;
  });

Cesium.knockout
  .getObservable(viewModel, "currentExampleType")
  .subscribe(function (newValue) {
    reset();
    if (newValue === exampleTypes[0]) {
      loadCesiumMan();
    } else if (newValue === exampleTypes[1]) {
      loadStHelens();
    } else if (newValue === exampleTypes[2]) {
      loadGrandCanyon();
    }
  });
