import vertSingle from '../shaders/vertex/single.js'
import vertDouble from '../shaders/vertex/double.js'
import vertSingleNotTransformed from '../shaders/vertex/singleNotTransformed.js'

import fragSingle from '../shaders/fragment/single.js'
import fragHsPregen from '../shaders/fragment/hillshading/hsPregen.js'
import fragHsAdvMergeAndScaleTiles from '../shaders/fragment/hillshading/hsAdvMergeAndScaleTiles.js'
import fragHsAdvNormals from '../shaders/fragment/hillshading/hsAdvNormals.js'
import fragHsAdvDirectLight from '../shaders/fragment/hillshading/hsAdvDirect.js'
import fragHsAdvSoftShadows from '../shaders/fragment/hillshading/hsAdvSoftShadows.js'
import fragHsAdvAmbientShadows from '../shaders/fragment/hillshading/hsAdvAmbientShadows.js'
import fragHsAdvFinalColorscale from '../shaders/fragment/hillshading/hsAdvFinalColorscale.js'
import fragHsAdvFinalBaselayer from '../shaders/fragment/hillshading/hsAdvFinalBaselayer.js'
import fragHsAdvSmooth from '../shaders/fragment/hillshading/hsAdvSmooth.js'

import {
  DEG2RAD,
  SLOPEFACTOR
} from '../constants'

import * as util from '../util'

const littleEndian = util.machineIsLittleEndian()

/**
 * The resulting Regl DrawCommand is used to draw a single tile. The fragment shader decodes the
 * Float32 value of a pixel and colorizes it with the given color scale (and/or sentinel values).
 * Hillshading is applied with a simple and fast algorithm
 */
export function createDrawTileHsSimpleCommand (regl, commonConfig, fragMacros) {
  return regl({
    ...commonConfig,
    vert: vertSingle,
    frag: util.defineMacros(fragSingle, fragMacros),
    uniforms: {
      ...commonConfig.uniforms,
      scaleLength: regl.prop('scaleLength'),
      sentinelLength: regl.prop('sentinelLength'),
      scaleColormap: regl.prop('scaleColormap'),
      sentinelColormap: regl.prop('sentinelColormap'),
      aboveColor: regl.prop('aboveColor'),
      belowColor: regl.prop('belowColor'),
      texture: (_, { texture }) => texture,
      enableSimpleHillshade: (_, { enableSimpleHillshade }) => enableSimpleHillshade,
      azimuth: (_, { azimuth }) => azimuth,
      altitude: (_, { altitude }) => altitude,
      slopescale: (_, { slopescale }) => slopescale,
      deg2rad: DEG2RAD,
      slopeFactor: SLOPEFACTOR,
      offset: (_, { offset }) => offset,
      textureBounds: (_, { textureBounds }) => {
        return [
          [textureBounds[0].x],
          [textureBounds[0].y],
          [textureBounds[1].x],
          [textureBounds[1].y]
        ]
      },
      textureSize: (_, { textureSize }) => textureSize,
      tileSize: (_, { tileSize }) => tileSize
    },
    attributes: {
      ...commonConfig.attributes,
      texCoord: (_, { textureBounds }) => util.getTexCoordVerticesTriangleStripQuad(textureBounds)
    }
  })
}

/**
 * The resulting Regl DrawCommand is used to draw a single tile. The fragment shader decodes the
 * Float32 value of a pixel and colorizes it with the given color scale (and/or sentinel values).
 * Hillshading is applied from a pre-generated texture
 */
export function createDrawTileHsPregenCommand (regl, commonConfig, fragMacros) {
  return regl({
    ...commonConfig,
    vert: vertDouble,
    frag: util.defineMacros(fragHsPregen, fragMacros),
    uniforms: {
      ...commonConfig.uniforms,
      scaleLength: regl.prop('scaleLength'),
      sentinelLength: regl.prop('sentinelLength'),
      scaleColormap: regl.prop('scaleColormap'),
      sentinelColormap: regl.prop('sentinelColormap'),
      aboveColor: regl.prop('aboveColor'),
      belowColor: regl.prop('belowColor'),
      texture: (_, { texture }) => texture,
      hillshadePregenTexture: (_, { hillshadePregenTexture }) => hillshadePregenTexture
    },
    attributes: {
      ...commonConfig.attributes,
      texCoordA: (_, { textureBounds }) => util.getTexCoordVerticesTriangleStripQuad(textureBounds),
      texCoordB: (_, { textureBoundsHs }) => util.getTexCoordVerticesTriangleStripQuad(textureBoundsHs)
    }
  })
}


/**
 * The resulting Regl DrawCommand is used to get the float values from the 3x3
 * adjacent tiles. The float values can be scaled to adjust the hillshading.
 * It will be saved to a framebuffer and is used as an input to advanced hillshading
 */
export function createHsAdvMergeAndScaleTiles (regl) {
  return regl({
    vert: vertSingleNotTransformed,
    frag: fragHsAdvMergeAndScaleTiles,
    uniforms: {
      littleEndian: littleEndian,
      nodataValue: regl.prop('nodataValue'),
      texture: regl.prop('texture'),
      floatScale: regl.prop('floatScale')
    },
    attributes: {
      // 18 triangles = 9 tiles
      position: [
        [-1, 1], [-1 / 3, 1], [-1, 1 / 3], [-1 / 3, 1 / 3], [-1 / 3, 1], [-1, 1 / 3],
        [-1, 1 / 3], [-1 / 3, 1 / 3], [-1, -1 / 3], [-1 / 3, -1 / 3], [-1 / 3, 1 / 3], [-1, -1 / 3],
        [-1, -1 / 3], [-1 / 3, -1 / 3], [-1, -1], [-1 / 3, -1], [-1 / 3, -1 / 3], [-1, -1],
        [-1 / 3, 1], [1 / 3, 1], [-1 / 3, 1 / 3], [1 / 3, 1 / 3], [1 / 3, 1], [-1 / 3, 1 / 3],
        [-1 / 3, 1 / 3], [1 / 3, 1 / 3], [-1 / 3, -1 / 3], [1 / 3, -1 / 3], [1 / 3, 1 / 3], [-1 / 3, -1 / 3],
        [-1 / 3, -1 / 3], [1 / 3, -1 / 3], [-1 / 3, -1], [1 / 3, -1], [1 / 3, -1 / 3], [-1 / 3, -1],
        [1 / 3, 1], [1, 1], [1 / 3, 1 / 3], [1, 1 / 3], [1, 1], [1 / 3, 1 / 3],
        [1 / 3, 1 / 3], [1, 1 / 3], [1 / 3, -1 / 3], [1, -1 / 3], [1, 1 / 3], [1 / 3, -1 / 3],
        [1 / 3, -1 / 3], [1, -1 / 3], [1 / 3, -1], [1, -1], [1, -1 / 3], [1 / 3, -1]
      ],
      texCoord: regl.prop('texCoord')
    },
    depth: { enable: false },
    primitive: 'triangles',
    count: 54,
    viewport: (_, { canvasSize: [width, height] }) => ({ width, height }),
    framebuffer: regl.prop('fbo')
  })
}


/**
 * The resulting Regl DrawCommand is for using a convolution kernel to smooth the input data.
 * Currently hard-coded the kernel and positions in the shader to reduce number of uniforms.
 * TODO: Merge with contours function
 */
export function createHsAdvSmoothCommand (regl, commonConfig) {
  return regl({
    ...commonConfig,
    vert: vertSingleNotTransformed,
    frag: fragHsAdvSmooth,
    uniforms: {
      ...commonConfig.uniforms,
      tInput: regl.prop('tInput'),
      textureSize: regl.prop('textureSize'),
      kernelSize: regl.prop('kernelSize')
    },
    attributes: {
      position: [[-1, 1], [1, 1], [-1, -1], [1, -1]],
      texCoord: [[0, 1], [1, 1], [0, 0], [1, 0]]
    },
    framebuffer: regl.prop('fbo')
  })
}


/**
 * The resulting Regl DrawCommand is used to calculate the normals.
 * It is used as an input to advanced hillshading
 */
export function createHsAdvCalcNormals (
  regl,
  commonConfig
) {
  return regl({
    ...commonConfig,
    vert: vertSingleNotTransformed,
    frag: fragHsAdvNormals,
    uniforms: {
      ...commonConfig.uniforms,
      tInput: regl.prop('tInput'),
      pixelScale: regl.prop('pixelScale'),
      onePixel: regl.prop('onePixel')
    },
    attributes: {
      position: [[-1, 1], [1, 1], [-1, -1], [1, -1]],
      texCoord: [[0, 1], [1, 1], [0, 0], [1, 0]]
    },
    framebuffer: regl.prop('fbo')
  })
}

/**
 * The resulting Regl DrawCommand is used to show hillshading without shadows.
 * Not currently used
 */
export function createHsAdvDirectLightning (
  regl,
  commonConfig
) {
  return regl({
    ...commonConfig,
    vert: vertSingleNotTransformed,
    frag: fragHsAdvDirectLight,
    uniforms: {
      ...commonConfig.uniforms,
      scaleLength: regl.prop('scaleLength'),
      sentinelLength: regl.prop('sentinelLength'),
      scaleColormap: regl.prop('scaleColormap'),
      sentinelColormap: regl.prop('sentinelColormap'),
      tInput: regl.prop('tInput'),
      tNormal: regl.prop('tNormal'),
      floatScale: regl.prop('floatScale'),
      sunDirection: regl.prop('sunDirection'),
      aboveColor: regl.prop('aboveColor'),
      belowColor: regl.prop('belowColor')
    },
    attributes: {
      position: [[-1, 1], [1, 1], [-1, -1], [1, -1]],
      texCoord: [[0, 1], [1, 1], [0, 0], [1, 0]]
    }
  })
}

/**
 * The resulting Regl DrawCommand is used to calculate soft shadows.
 */
export function createHsAdvSoftShadows (
  regl,
  commonConfig
) {
  return regl({
    ...commonConfig,
    vert: vertSingleNotTransformed,
    frag: fragHsAdvSoftShadows,
    uniforms: {
      ...commonConfig.uniforms,
      tInput: regl.prop('tInput'),
      tNormal: regl.prop('tNormal'),
      tSrc: regl.prop('tSrc'),
      softIterations: regl.prop('softIterations'),
      pixelScale: regl.prop('pixelScale'),
      resolution: regl.prop('resolution'),
      sunDirection: regl.prop('sunDirection')
    },
    attributes: {
      position: [[-1, 1], [1, 1], [-1, -1], [1, -1]],
      texCoord: [[1 / 3, 2 / 3], [2 / 3, 2 / 3], [1 / 3, 1 / 3], [2 / 3, 1 / 3]]
    },
    framebuffer: regl.prop('fbo')
  })
}

/**
 * The resulting Regl DrawCommand is used to calculate ambient lighting.
 */
export function createHsAdvAmbientShadows (
  regl,
  commonConfig
) {
  return regl({
    ...commonConfig,
    vert: vertSingleNotTransformed,
    frag: fragHsAdvAmbientShadows,
    uniforms: {
      ...commonConfig.uniforms,
      tInput: regl.prop('tInput'),
      tNormal: regl.prop('tNormal'),
      tSrc: regl.prop('tSrc'),
      ambientIterations: regl.prop('ambientIterations'),
      pixelScale: regl.prop('pixelScale'),
      resolution: regl.prop('resolution'),
      direction: regl.prop('direction')
    },
    attributes: {
      position: [[-1, 1], [1, 1], [-1, -1], [1, -1]],
      texCoord: [[1 / 3, 2 / 3], [2 / 3, 2 / 3], [1 / 3, 1 / 3], [2 / 3, 1 / 3]]
    },
    framebuffer: regl.prop('fbo')
  })
}

/**
 * The resulting Regl DrawCommand is used to combine soft and ambient shading,
 * use the colormap on the input floats and apply the hillshading.
 */
export function createHsAdvFinalColorscale (
  regl,
  commonConfig
) {
  return regl({
    ...commonConfig,
    vert: vertDouble,
    frag: fragHsAdvFinalColorscale,
    uniforms: {
      ...commonConfig.uniforms,
      scaleLength: regl.prop('scaleLength'),
      sentinelLength: regl.prop('sentinelLength'),
      scaleColormap: regl.prop('scaleColormap'),
      sentinelColormap: regl.prop('sentinelColormap'),
      tInput: regl.prop('tInput'),
      tSoftShadow: regl.prop('tSoftShadow'),
      tAmbient: regl.prop('tAmbient'),
      floatScale: regl.prop('floatScale'),
      finalSoftMultiplier: regl.prop('finalSoftMultiplier'),
      finalAmbientMultiplier: regl.prop('finalAmbientMultiplier'),
      aboveColor: regl.prop('aboveColor'),
      belowColor: regl.prop('belowColor')
    },
    attributes: {
      ...commonConfig.attributes,
      texCoordA: [[1 / 3, 2 / 3], [2 / 3, 2 / 3], [1 / 3, 1 / 3], [2 / 3, 1 / 3]],
      texCoordB: [[0, 1], [1, 1], [0, 0], [1, 0]]
    }
  })
}

/**
 * The resulting Regl DrawCommand is used to combine soft and ambient shading,
 * use the baselayer tile and apply the hillshading.
 */
export function createHsAdvFinalBaselayer (
  regl,
  commonConfig
) {
  return regl({
    ...commonConfig,
    vert: vertDouble,
    frag: fragHsAdvFinalBaselayer,
    uniforms: {
      ...commonConfig.uniforms,
      tBase: regl.prop('tBase'),
      tSoftShadow: regl.prop('tSoftShadow'),
      tAmbient: regl.prop('tAmbient'),
      finalSoftMultiplier: regl.prop('finalSoftMultiplier'),
      finalAmbientMultiplier: regl.prop('finalAmbientMultiplier')
    },
    attributes: {
      ...commonConfig.attributes,
      texCoordA: regl.prop('baseTexCoords'),
      texCoordB: [[0, 1], [1, 1], [0, 0], [1, 0]]
    }
  })
}
