import { TextureLoader, DataTexture, RedFormat, FloatType } from 'three';
const textureLoader = new TextureLoader();
const SIZE_TEXTURE_TILE = 256;
function checkResponse(response) {
  if (!response.ok) {
    const error = new Error(`Error loading ${response.url}: status ${response.status}`);
    error.response = response;
    throw error;
  }
}
const arrayBuffer = function (url) {
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  return fetch(url, options).then(response => {
    checkResponse(response);
    return response.arrayBuffer();
  });
};
function getTextureFloat(buffer) {
  const texture = new DataTexture(buffer, SIZE_TEXTURE_TILE, SIZE_TEXTURE_TILE, RedFormat, FloatType);
  texture.internalFormat = 'R32F';
  texture.needsUpdate = true;
  return texture;
}

/**
 * Utilitary to fetch resources from a server using the [fetch API](
 * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch).
 *
 * @module Fetcher
 */
export default {
  /**
   * Wrapper over fetch to get some text.
   *
   * @param {string} url - The URL of the resources to fetch.
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   *
   * @return {Promise<string>} Promise containing the text.
   */
  text(url) {
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    return fetch(url, options).then(response => {
      checkResponse(response);
      return response.text();
    });
  },
  /**
   * Little wrapper over fetch to get some JSON.
   *
   * @param {string} url - The URL of the resources to fetch.
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   *
   * @return {Promise<Object>} Promise containing the JSON object.
   */
  json(url) {
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    return fetch(url, options).then(response => {
      checkResponse(response);
      return response.json();
    });
  },
  /**
   * Wrapper over fetch to get some XML.
   *
   * @param {string} url - The URL of the resources to fetch.
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   *
   * @return {Promise<Document>} Promise containing the XML Document.
   */
  xml(url) {
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    return fetch(url, options).then(response => {
      checkResponse(response);
      return response.text();
    }).then(text => new window.DOMParser().parseFromString(text, 'text/xml'));
  },
  /**
   * Wrapper around [THREE.TextureLoader](https://threejs.org/docs/#api/en/loaders/TextureLoader).
   *
   * @param {string} url - The URL of the resources to fetch.
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   * Note that THREE.js docs mentions `withCredentials`, but it is not
   * actually used in [THREE.TextureLoader](https://threejs.org/docs/#api/en/loaders/TextureLoader).
   *
   * @return {Promise<THREE.Texture>} Promise containing the
   * [THREE.Texture](https://threejs.org/docs/api/en/textures/Texture.html).
   */
  texture(url) {
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    let res;
    let rej;
    textureLoader.crossOrigin = options.crossOrigin;
    const promise = new Promise((resolve, reject) => {
      res = resolve;
      rej = reject;
    });
    textureLoader.load(url, res, () => {}, rej);
    return promise;
  },
  /**
   * Wrapper over fetch to get some ArrayBuffer.
   *
   * @param {string} url - The URL of the resources to fetch.
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   *
   * @return {Promise<ArrayBuffer>} Promise containing the ArrayBuffer.
   */
  arrayBuffer,
  /**
   * Wrapper over fetch to get some
   * [THREE.DataTexture](https://threejs.org/docs/#api/en/textures/DataTexture).
   *
   * @param {string} url - The URL of the resources to fetch.
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   *
   * @return {Promise<THREE.DataTexture>} Promise containing the DataTexture.
   */
  textureFloat(url) {
    let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    return arrayBuffer(url, options).then(buffer => {
      const floatArray = new Float32Array(buffer);
      const texture = getTextureFloat(floatArray);
      return texture;
    });
  },
  /**
   * Wrapper over fetch to get a bunch of files sharing the same name, but
   * different extensions.
   *
   * @param {string} baseUrl - The shared URL of the resources to fetch.
   * @param {Object} extensions - An object containing arrays. The keys of
   * each of this array are available fetch type, such as `text`, `json` or
   * even `arrayBuffer`. The arrays contains the extensions to append after
   * the `baseUrl` (see example below).
   * @param {Object} options - Fetch options (passed directly to `fetch()`),
   * see [the syntax for more information](
   * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Syntax).
   *
   * @return {Promise[]} An array of promises, containing all the files,
   * organized by their extensions (see the example below).
   *
   * @example
   * itowns.Fetcher.multiple('http://geo.server/shapefile', {
   *     // will fetch:
   *     // - http://geo.server/shapefile.shp
   *     // - http://geo.server/shapefile.dbf
   *     // - http://geo.server/shapefile.shx
   *     // - http://geo.server/shapefile.prj
   *     arrayBuffer: ['shp', 'dbf', 'shx'],
   *     text: ['prj'],
   * }).then(function _(result) {
   *     // result looks like:
   *     result = {
   *         shp: ArrayBuffer
   *         dbf: ArrayBuffer
   *         shx: ArrayBuffer
   *         prj: string
   *     };
   * });
   */
  multiple(baseUrl, extensions) {
    let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    const promises = [];
    let url;
    for (const fetchType in extensions) {
      if (!this[fetchType]) {
        throw new Error(`${fetchType} is not a valid Fetcher method.`);
      } else {
        for (const extension of extensions[fetchType]) {
          url = `${baseUrl}.${extension}`;
          promises.push(this[fetchType](url, options).then(result => ({
            type: extension,
            result
          })));
        }
      }
    }
    return Promise.all(promises).then(result => {
      const all = {};
      for (const res of result) {
        all[res.type] = res.result;
      }
      return Promise.resolve(all);
    });
  },
  get() {
    let format = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
    const [type, subtype] = format.split('/');
    switch (type) {
      case 'application':
        switch (subtype) {
          case 'geo+json':
          case 'json':
            return this.json;
          case 'kml':
          case 'gpx':
            return this.xml;
          case 'x-protobuf;type=mapbox-vector':
          case 'gtx':
            return this.arrayBuffer;
          case 'isg':
          case 'gdf':
          default:
            return this.text;
        }
      case 'image':
        switch (subtype) {
          case 'x-bil;bits=32':
            return this.textureFloat;
          default:
            return this.texture;
        }
      default:
        return this.texture;
    }
  }
};