define([
  "dojo/_base/declare",
  "dojo/request/xhr",
  "dojo/request",
  "dojo/promise/all",
  "helpers/OffsetVertices",
  "mappers/CartesianToTypedArray",
  "style/NodeStyler"
], function (declare, xhr, request, all, OffsetVertices, CartesianToTypedArray, NodeStyler) {

  return declare(null, {

    constructor: function () {

    },

    map: function (node) {

      return new Promise((resolve, reject) => {

        request(node.url, {
          handleAs: "json",
          headers: { "X-Requested-With": null }
        }).then((data) => {

          let featuresUrl = `${node.url}/${data.featureData[0].href}`;
          let geometryUrl = `${node.url}/${data.geometryData[0].href}`;

          let featuresPromise = request.get(featuresUrl, {
            handleAs: "json",
            headers: { "X-Requested-With": null }
          });

          let geometryPromise = xhr(geometryUrl, {
            handleAs: "arraybuffer",
            headers: { "X-Requested-With": null }
          });

          all([featuresPromise, geometryPromise]).then((payloads) => {
            let instances = this.process(data, payloads);
            resolve(instances);
          });

        });

      });

    },

    process: function (nodeData, payloads) {

      let nodeLevel = nodeData.level;
      let nodeMBS = nodeData.mbs;

      let VERTEX_ELEMENTS = 3;
      let VERTEX_ELEMENTS_PER_TRIANGLE = 9;
      let NORMAL_ELEMENTS_PER_TRIANGLE = 9;

      let features = payloads[0].featureData;
      let vertexAttributes = payloads[0].geometryData[0].params.vertexAttributes;
      let geometryBuffer = payloads[1];
      let instances = [];

      for (let i = 0, len = features.length; i < len; i++) {

        let feature = features[i];
        let faceRange = feature.geometries[0].params.faceRange;
        let featureVertices = new Float32Array(
          geometryBuffer,
          vertexAttributes.position.byteOffset + faceRange[0] * VERTEX_ELEMENTS_PER_TRIANGLE * Float32Array.BYTES_PER_ELEMENT,
          (faceRange[1] - faceRange[0] + 1) * VERTEX_ELEMENTS_PER_TRIANGLE
        );

        minHeight = featureVertices.filter((coordinate, index) => {
          return (index + 1) % 3 == 0;
        }).reduce((a, b) => {
          return Math.min(a, b);
        });

        OffsetVertices.apply(featureVertices, nodeMBS[0], nodeMBS[1], -minHeight)
        let cartesianPositions = Cesium.Cartesian3.fromDegreesArrayHeights(featureVertices);
        let positions = new Float64Array(featureVertices.length);
        CartesianToTypedArray.apply(cartesianPositions, positions);

        let normals = new Float32Array(
          geometryBuffer,
          vertexAttributes.normal.byteOffset + faceRange[0] * NORMAL_ELEMENTS_PER_TRIANGLE * Float32Array.BYTES_PER_ELEMENT,
          (faceRange[1] - faceRange[0] + 1) * NORMAL_ELEMENTS_PER_TRIANGLE
        );

        let geometry = new Cesium.Geometry({
          attributes: {
            position: new Cesium.GeometryAttribute({
              componentDatatype: Cesium.ComponentDatatype.DOUBLE,
              componentsPerAttribute: 3,
              values: positions
            }),
            normal: new Cesium.GeometryAttribute({
              componentDatatype: Cesium.ComponentDatatype.FLOAT,
              componentsPerAttribute: 3,
              values: normals
            })
          },
          primitiveType: Cesium.PrimitiveType.TRIANGLES,
          boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
        });

        let instance = new Cesium.GeometryInstance({
          geometry: geometry,
          attributes: {
            color: Cesium.ColorGeometryInstanceAttribute.fromColor(NodeStyler.getColor(nodeLevel))
          },
          show: new Cesium.ShowGeometryInstanceAttribute(true),
          id: feature.id
        });

        instances.push(instance);

      } // end for

      return instances;

    }

  });

});