/**
 * Copyright (c) Nicolas Gallagher.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

const dataUriPattern = /^data:/

export class ImageUriCache {
  static _maximumEntries = 256
  static _entries = {}

  static has(uri: string): boolean {
    const entries = ImageUriCache._entries
    const isDataUri = dataUriPattern.test(uri)
    return isDataUri || Boolean(entries[uri])
  }

  static add(uri: string) {
    const entries = ImageUriCache._entries
    const lastUsedTimestamp = Date.now()
    if (entries[uri]) {
      entries[uri].lastUsedTimestamp = lastUsedTimestamp
      entries[uri].refCount += 1
    } else {
      entries[uri] = {
        lastUsedTimestamp,
        refCount: 1,
      }
    }
  }

  static remove(uri: string) {
    const entries = ImageUriCache._entries
    if (entries[uri]) {
      entries[uri].refCount -= 1
    }
    // Free up entries when the cache is "full"
    ImageUriCache._cleanUpIfNeeded()
  }

  static _cleanUpIfNeeded() {
    const entries = ImageUriCache._entries
    const imageUris = Object.keys(entries)

    if (imageUris.length + 1 > ImageUriCache._maximumEntries) {
      let leastRecentlyUsedKey
      let leastRecentlyUsedEntry

      imageUris.forEach((uri) => {
        const entry = entries[uri]
        if (
          (!leastRecentlyUsedEntry ||
            entry.lastUsedTimestamp < leastRecentlyUsedEntry.lastUsedTimestamp) &&
          entry.refCount === 0
        ) {
          leastRecentlyUsedKey = uri
          leastRecentlyUsedEntry = entry
        }
      })

      if (leastRecentlyUsedKey) {
        delete entries[leastRecentlyUsedKey]
      }
    }
  }
}

let id = 0
const requests = {}

export const ImageLoader = {
  abort(requestId: number) {
    let image = requests[`${requestId}`]
    if (image) {
      image.onerror = null
      image.onload = null
      image = null
      delete requests[`${requestId}`]
    }
  },
  getSize(
    uri: string,
    success: (width: number, height: number) => void,
    failure: () => void
  ) {
    let complete = false
    const interval = setInterval(callback, 16)
    const requestId = ImageLoader.load(uri, callback, errorCallback)

    function callback() {
      const image = requests[`${requestId}`]
      if (image) {
        const { naturalHeight, naturalWidth } = image
        if (naturalHeight && naturalWidth) {
          success(naturalWidth, naturalHeight)
          complete = true
        }
      }
      if (complete) {
        ImageLoader.abort(requestId)
        clearInterval(interval)
      }
    }

    function errorCallback() {
      if (typeof failure === 'function') {
        failure()
      }
      ImageLoader.abort(requestId)
      clearInterval(interval)
    }
  },
  has(uri: string): boolean {
    return ImageUriCache.has(uri)
  },
  load(uri: string, onLoad: Function, onError: Function): number {
    id += 1
    const image = new window.Image()
    // @ts-ignore
    image.onerror = onError
    image.onload = (e) => {
      // avoid blocking the main thread
      const onDecode = () => onLoad({ nativeEvent: e })
      if (typeof image.decode === 'function') {
        // Safari currently throws exceptions when decoding svgs.
        // We want to catch that error and allow the load handler
        // to be forwarded to the onLoad handler in this case
        image.decode().then(onDecode, onDecode)
      } else {
        setTimeout(onDecode, 0)
      }
    }
    image.src = uri
    requests[`${id}`] = image
    return id
  },
  prefetch(uri: string): Promise<void> {
    return new Promise((resolve, reject) => {
      ImageLoader.load(
        uri,
        () => {
          // Add the uri to the cache so it can be immediately displayed when used
          // but also immediately remove it to correctly reflect that it has no active references
          ImageUriCache.add(uri)
          ImageUriCache.remove(uri)
          resolve()
        },
        reject
      )
    })
  },
  queryCache(uris: Array<string>): Promise<{ [uri: string]: 'disk/memory' }> {
    const result = {}
    uris.forEach((u) => {
      if (ImageUriCache.has(u)) {
        result[u] = 'disk/memory'
      }
    })
    return Promise.resolve(result)
  },
}
