import addToArray from "./addToArray.js";
import ForEach from "./ForEach.js";
import getAccessorByteStride from "./getAccessorByteStride.js";
import defaultValue from "../../Core/defaultValue.js";
import defined from "../../Core/defined.js";
import WebGLConstants from "../../Core/WebGLConstants.js";

/**
 * Adds default glTF values if they don't exist.
 *
 * @param {Object} gltf A javascript object containing a glTF asset.
 * @returns {Object} The modified glTF.
 *
 * @private
 */
function addDefaults(gltf) {
  ForEach.accessor(gltf, function (accessor) {
    if (defined(accessor.bufferView)) {
      accessor.byteOffset = defaultValue(accessor.byteOffset, 0);
    }
  });

  ForEach.bufferView(gltf, function (bufferView) {
    if (defined(bufferView.buffer)) {
      bufferView.byteOffset = defaultValue(bufferView.byteOffset, 0);
    }
  });

  ForEach.mesh(gltf, function (mesh) {
    ForEach.meshPrimitive(mesh, function (primitive) {
      primitive.mode = defaultValue(primitive.mode, WebGLConstants.TRIANGLES);
      if (!defined(primitive.material)) {
        if (!defined(gltf.materials)) {
          gltf.materials = [];
        }
        const defaultMaterial = {
          name: "default",
        };
        primitive.material = addToArray(gltf.materials, defaultMaterial);
      }
    });
  });

  ForEach.accessorContainingVertexAttributeData(gltf, function (accessorId) {
    const accessor = gltf.accessors[accessorId];
    const bufferViewId = accessor.bufferView;
    accessor.normalized = defaultValue(accessor.normalized, false);
    if (defined(bufferViewId)) {
      const bufferView = gltf.bufferViews[bufferViewId];
      bufferView.byteStride = getAccessorByteStride(gltf, accessor);
      bufferView.target = WebGLConstants.ARRAY_BUFFER;
    }
  });

  ForEach.accessorContainingIndexData(gltf, function (accessorId) {
    const accessor = gltf.accessors[accessorId];
    const bufferViewId = accessor.bufferView;
    if (defined(bufferViewId)) {
      const bufferView = gltf.bufferViews[bufferViewId];
      bufferView.target = WebGLConstants.ELEMENT_ARRAY_BUFFER;
    }
  });

  ForEach.material(gltf, function (material) {
    const extensions = defaultValue(
      material.extensions,
      defaultValue.EMPTY_OBJECT
    );
    const materialsCommon = extensions.KHR_materials_common;
    if (defined(materialsCommon)) {
      const technique = materialsCommon.technique;
      const values = defined(materialsCommon.values)
        ? materialsCommon.values
        : {};
      materialsCommon.values = values;

      values.ambient = defined(values.ambient)
        ? values.ambient
        : [0.0, 0.0, 0.0, 1.0];
      values.emission = defined(values.emission)
        ? values.emission
        : [0.0, 0.0, 0.0, 1.0];

      values.transparency = defaultValue(values.transparency, 1.0);

      if (technique !== "CONSTANT") {
        values.diffuse = defined(values.diffuse)
          ? values.diffuse
          : [0.0, 0.0, 0.0, 1.0];
        if (technique !== "LAMBERT") {
          values.specular = defined(values.specular)
            ? values.specular
            : [0.0, 0.0, 0.0, 1.0];
          values.shininess = defaultValue(values.shininess, 0.0);
        }
      }

      // These actually exist on the extension object, not the values object despite what's shown in the spec
      materialsCommon.transparent = defaultValue(
        materialsCommon.transparent,
        false
      );
      materialsCommon.doubleSided = defaultValue(
        materialsCommon.doubleSided,
        false
      );

      return;
    }

    material.emissiveFactor = defaultValue(
      material.emissiveFactor,
      [0.0, 0.0, 0.0]
    );
    material.alphaMode = defaultValue(material.alphaMode, "OPAQUE");
    material.doubleSided = defaultValue(material.doubleSided, false);

    if (material.alphaMode === "MASK") {
      material.alphaCutoff = defaultValue(material.alphaCutoff, 0.5);
    }

    const techniquesExtension = extensions.KHR_techniques_webgl;
    if (defined(techniquesExtension)) {
      ForEach.materialValue(material, function (materialValue) {
        // Check if material value is a TextureInfo object
        if (defined(materialValue.index)) {
          addTextureDefaults(materialValue);
        }
      });
    }

    addTextureDefaults(material.emissiveTexture);
    addTextureDefaults(material.normalTexture);
    addTextureDefaults(material.occlusionTexture);

    const pbrMetallicRoughness = material.pbrMetallicRoughness;
    if (defined(pbrMetallicRoughness)) {
      pbrMetallicRoughness.baseColorFactor = defaultValue(
        pbrMetallicRoughness.baseColorFactor,
        [1.0, 1.0, 1.0, 1.0]
      );
      pbrMetallicRoughness.metallicFactor = defaultValue(
        pbrMetallicRoughness.metallicFactor,
        1.0
      );
      pbrMetallicRoughness.roughnessFactor = defaultValue(
        pbrMetallicRoughness.roughnessFactor,
        1.0
      );
      addTextureDefaults(pbrMetallicRoughness.baseColorTexture);
      addTextureDefaults(pbrMetallicRoughness.metallicRoughnessTexture);
    }

    const pbrSpecularGlossiness =
      extensions.KHR_materials_pbrSpecularGlossiness;
    if (defined(pbrSpecularGlossiness)) {
      pbrSpecularGlossiness.diffuseFactor = defaultValue(
        pbrSpecularGlossiness.diffuseFactor,
        [1.0, 1.0, 1.0, 1.0]
      );
      pbrSpecularGlossiness.specularFactor = defaultValue(
        pbrSpecularGlossiness.specularFactor,
        [1.0, 1.0, 1.0]
      );
      pbrSpecularGlossiness.glossinessFactor = defaultValue(
        pbrSpecularGlossiness.glossinessFactor,
        1.0
      );
      addTextureDefaults(pbrSpecularGlossiness.specularGlossinessTexture);
    }
  });

  ForEach.animation(gltf, function (animation) {
    ForEach.animationSampler(animation, function (sampler) {
      sampler.interpolation = defaultValue(sampler.interpolation, "LINEAR");
    });
  });

  const animatedNodes = getAnimatedNodes(gltf);
  ForEach.node(gltf, function (node, id) {
    const animated = defined(animatedNodes[id]);
    if (
      animated ||
      defined(node.translation) ||
      defined(node.rotation) ||
      defined(node.scale)
    ) {
      node.translation = defaultValue(node.translation, [0.0, 0.0, 0.0]);
      node.rotation = defaultValue(node.rotation, [0.0, 0.0, 0.0, 1.0]);
      node.scale = defaultValue(node.scale, [1.0, 1.0, 1.0]);
    } else {
      node.matrix = defaultValue(
        node.matrix,
        [
          1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
          0.0, 1.0,
        ]
      );
    }
  });

  ForEach.sampler(gltf, function (sampler) {
    sampler.wrapS = defaultValue(sampler.wrapS, WebGLConstants.REPEAT);
    sampler.wrapT = defaultValue(sampler.wrapT, WebGLConstants.REPEAT);
  });

  if (defined(gltf.scenes) && !defined(gltf.scene)) {
    gltf.scene = 0;
  }

  return gltf;
}

function getAnimatedNodes(gltf) {
  const nodes = {};
  ForEach.animation(gltf, function (animation) {
    ForEach.animationChannel(animation, function (channel) {
      const target = channel.target;
      const nodeId = target.node;
      const path = target.path;
      // Ignore animations that target 'weights'
      if (path === "translation" || path === "rotation" || path === "scale") {
        nodes[nodeId] = true;
      }
    });
  });
  return nodes;
}

function addTextureDefaults(texture) {
  if (defined(texture)) {
    texture.texCoord = defaultValue(texture.texCoord, 0);
  }
}

export default addDefaults;
