import REGL from 'regl'
import {
  CLEAR_COLOR,
  MAX_TEXTURE_DIMENSION,
  EARTH_SUN_DISTANCE,
  SUN_RADIUS
} from './constants'

import TextureManager from './texture_manager'
import * as util from './util'
import { vec3 } from 'gl-matrix'
import * as reglCommands from './reglCommands'


export default class Renderer {
  constructor (gloperations, tileSize, nodataValue, scaleInput, sentinelInput, colorscaleMaxLength, sentinelMaxLength, aboveColor, belowColor) {
    const canvas = document.createElement('canvas')
    let maxTextureDimension = MAX_TEXTURE_DIMENSION
    let normalTextureDimension = 1024

    const regl = REGL({
      canvas: canvas,
      // profile: true,
      // extension only used for advanced hillshading
      // TODO: add fallback to rgba if writing to float fails
      optionalExtensions: ['OES_texture_float', 'WEBGL_color_buffer_float'],
      onDone: function (err, regl) {
        if (err) {
          console.error(err)
          return
        } else {
          // cannot use this directly as it seems to cause some issues
          // maxTextureDimension = regl.limits.maxTextureSize;
          if (regl.limits.maxTextureSize > 2048) {
            maxTextureDimension = 2048
          }
          if (regl.limits.maxTextureSize > 4096) {
            maxTextureDimension = 4096
          }
          if (regl.limits.maxTextureSize > 8192) {
            maxTextureDimension = 8192
          }

          // TODO: use lower normal dimension where relevant
          // Currently set to same due to a bug
          normalTextureDimension = maxTextureDimension
        }
        // TODO: Improve software rendering detection
        if (regl.limits.maxFragmentUniforms === 261) {
          console.warn('Software rendering detected. Many features of this plugin will fail.\
          If you have a GPU, check if drivers are installed ok?')
        }
      }
    })

    const commonDrawConfig = reglCommands.getCommonDrawConfiguration(tileSize, nodataValue)
    const fragMacros = {
      SCALE_MAX_LENGTH: colorscaleMaxLength,
      SENTINEL_MAX_LENGTH: sentinelMaxLength
    }

    // Assign object "instance" properties.
    Object.assign(this, {
      gloperations: gloperations,
      canvas: canvas,
      regl: regl,
      tileSize: tileSize,
      nodataValue: nodataValue,
      maxTextureDimension: maxTextureDimension,
      normalTextureDimension: normalTextureDimension,
      scaleInput: scaleInput,
      sentinelInput: sentinelInput,
      scaleColormap: util.createColormapTexture(scaleInput, regl),
      sentinelColormap: util.createColormapTexture(sentinelInput, regl),
      textureManager: new TextureManager(regl, tileSize, normalTextureDimension, false),
      drawTile: reglCommands.createDrawTileCommand(regl, commonDrawConfig, fragMacros),
      aboveColor: util.colorStringToInts(aboveColor || '#00000000').map(i => i / 255.0),
      belowColor: util.colorStringToInts(belowColor || '#00000000').map(i => i / 255.0)
    })
  }

  destroy () {
    if (this.regl) this.regl.destroy()
    this.regl = null
  }

  renderTile ({ coords, pixelData }, _hillshadeOptions, zoom) {
    const {
      regl,
      textureManager,
      tileSize
    } = this
    this.setCanvasSize(tileSize, tileSize)
    // Add image to the texture and retrieve its texture coordinates.
    const textureBounds = textureManager.addTile(coords, pixelData)

    regl.clear({ color: CLEAR_COLOR })

    const zoomdelta = _hillshadeOptions.hsSimpleZoomdelta || 0

    const offset_pixels = Math.max(0.5, 2 ** (zoom + zoomdelta) / 2048)
    const offset_texcoords = offset_pixels / textureManager.texture.width

    if (_hillshadeOptions.hillshadeType === 'none') {
      this.drawTile({
        canvasSize: [tileSize, tileSize],
        canvasCoordinates: [0, 0],
        textureBounds,
        texture: textureManager.texture,
        scaleLength: this.scaleInput.length,
        sentinelLength: this.sentinelInput.length,
        scaleColormap: this.scaleColormap,
        sentinelColormap: this.sentinelColormap,
        enableSimpleHillshade: false,
        aboveColor: this.aboveColor,
        belowColor: this.belowColor
      })
    } else if (_hillshadeOptions.hillshadeType === 'simple') {
      this.drawTileHsSimple({
        scaleLength: this.scaleInput.length,
        sentinelLength: this.sentinelInput.length,
        scaleColormap: this.scaleColormap,
        sentinelColormap: this.sentinelColormap,
        canvasSize: [tileSize, tileSize],
        canvasCoordinates: [0, 0],
        textureBounds: textureBounds,
        texture: textureManager.texture,
        textureSize: textureManager.texture.width,
        tileSize: tileSize,
        offset: offset_texcoords,
        enableSimpleHillshade: true,
        // elevationScale: _hillshadeOptions.hsElevationScale,
        azimuth: _hillshadeOptions.hsSimpleAzimuth,
        altitude: _hillshadeOptions.hsSimpleAltitude,
        slopescale: _hillshadeOptions.hsSimpleSlopescale,
        aboveColor: this.aboveColor,
        belowColor: this.belowColor 
      })
    }

    // Since the tile will fill the whole canvas, the offset is simply [0, 0].
    return [0, 0]
  }

  setCanvasSize (width, height) {
    Object.assign(this.canvas, { width, height })
  }

  generateAmbientDirections (iterations) {
    const ambientDirections = []
    for (let i = 0; i < iterations; i++) {
      const direction = vec3.random(vec3.create(), Math.random())
      ambientDirections.push(direction)
    }
    this.ambientDirections = ambientDirections
  }

  generateSunDirections (iterations, sunRadiusMultiplier) {
    const sunDirections = []
    for (let i = 0; i < iterations; i++) {
      const direction = vec3.normalize(
        vec3.create(),
        vec3.add(
          vec3.create(),
          vec3.scale(
            vec3.create(),
            vec3.normalize(vec3.create(), [1, 1, 1]),
            EARTH_SUN_DISTANCE
          ),
          vec3.random(vec3.create(), SUN_RADIUS * sunRadiusMultiplier)
        )
      )
      sunDirections.push(direction)
    }
    this.sunDirections = sunDirections
  }

  updateColorscale (scaleInput) {
    this.scaleInputPrevious = this.scaleInput
    this.scaleInput = scaleInput
    this.scaleColormapPrevious = this.scaleColormap
    this.scaleColormap = util.createColormapTexture(scaleInput, this.regl)
  }
  updateOuteRangeColor (aboveColor, belowColor) {
    this.aboveColorPrevious = this.aboveColor
    this.aboveColor = util.colorStringToInts(aboveColor || '#00000000').map(i => i / 255.0)
    this.belowColorPrevious = this.belowColor
    this.belowColor = util.colorStringToInts(belowColor || '#00000000').map(i => i / 255.0)
  }
  updateSentinels (sentinelInput) {
    this.sentinelInputPrevious = this.sentinelInput
    this.sentinelInput = sentinelInput
    this.sentinelColormapPrevious = this.sentinelColormap
    this.sentinelColormap = util.createColormapTexture(sentinelInput, this.regl)
  }
  setMaxTextureDimension (newMaxTextureDimension) {
    const {
      textureManager,
      tileSize,
      regl
    } = this

    textureManager.destroy()

    Object.assign(this, {
      maxTextureDimension: newMaxTextureDimension,
      textureManager: new TextureManager(regl, tileSize, newMaxTextureDimension, false)
    })
  }
}