import type {ColorMapper, ContinuousColorMapper} from "@bokehjs/models/mappers"
import {LinearColorMapper} from "@bokehjs/models/mappers"
import {range, linspace} from "@bokehjs/core/util/array"

export declare type ColorBarOptions = {
  ticksNum?: number
  ticksSize?: number
  fontFamily?: string
  fontSize?: string
  height?: string
}

export class VTKColorBar {
  public canvas: HTMLCanvasElement
  private ctx: CanvasRenderingContext2D

  constructor(
    private parent: HTMLElement,
    private mapper: ColorMapper,
    private options: ColorBarOptions = {},
  ) {
    if (!options.ticksNum) {
      options.ticksNum = 5
    }
    if (!options.fontFamily) {
      options.fontFamily = "Arial"
    }
    if (!options.fontSize) {
      options.fontSize = "12px"
    }
    if (!options.ticksSize) {
      options.ticksSize = 2
    }
    this.canvas = document.createElement("canvas")
    this.canvas.style.width = "100%"
    this.parent.appendChild(this.canvas)
    this.ctx = this.canvas.getContext("2d")!
    this.ctx.font = `${this.options.fontSize} ${this.options.fontFamily}`
    this.ctx.lineWidth = options.ticksSize
    if (!options.height) {
      options.height = `${(this.font_height+1) * 4}px` //title/ticks/colorbar
    }
    this.canvas.style.height = options.height
    this.draw_colorbar()
  }

  get values(): number[] {
    const {min, max} = (this.mapper as ContinuousColorMapper).metrics
    return linspace(min, max, this.options.ticksNum)
  }

  get ticks(): string[] {
    return this.values.map((v) => v.toExponential(3))
  }

  get title(): string {
    return this.mapper.name ?? "scalars"
  }

  get font_height(): number {
    let font_height = 0
    this.values.forEach((val) => {
      const {
        actualBoundingBoxAscent,
        actualBoundingBoxDescent,
      } = this.ctx.measureText(`${val}`)
      const height = actualBoundingBoxAscent + actualBoundingBoxDescent
      if (font_height < height) {
        font_height = height
      }
    })
    return font_height
  }

  draw_colorbar() {
    this.canvas.width = this.canvas.clientWidth
    this.canvas.height = this.canvas.clientHeight
    const {palette} = this.mapper
    this.ctx.font = `${this.options.fontSize} ${this.options.fontFamily}`
    const font_height = this.font_height

    this.ctx.save()
    //colorbar
    const image = document.createElement("canvas")
    const h = 1
    const w = palette.length
    image.width = w
    image.height = h
    const image_ctx = image.getContext("2d")!
    const image_data = image_ctx.getImageData(0, 0, w, h)
    const cmap = new LinearColorMapper({palette}).rgba_mapper
    const buf8 = cmap.v_compute(range(0, palette.length))
    image_data.data.set(buf8)
    image_ctx.putImageData(image_data, 0, 0)
    this.ctx.drawImage(
      image,
      0,
      2 * (this.font_height + 1) + 1,
      this.canvas.width,
      this.canvas.height,
    )
    this.ctx.restore()
    this.ctx.save()
    //title
    this.ctx.textAlign = "center"
    this.ctx.fillText(this.title, this.canvas.width/2, font_height+1)
    this.ctx.restore()
    this.ctx.save()
    //ticks
    const tick_x_positions = linspace(0, this.canvas.width, 5)
    tick_x_positions.forEach((xpos, idx) => {
      let xpos_tick = xpos
      if (idx == 0) {
        xpos_tick = xpos + Math.ceil(this.ctx.lineWidth / 2)
        this.ctx.textAlign = "left"
      } else if (idx == tick_x_positions.length - 1) {
        xpos_tick = xpos - Math.ceil(this.ctx.lineWidth / 2)
        this.ctx.textAlign = "right"
      } else {
        this.ctx.textAlign = "center"
      }
      this.ctx.moveTo(xpos_tick, 2*(font_height+1))
      this.ctx.lineTo(xpos_tick, 2*(font_height+1)+5)
      this.ctx.stroke()
      this.ctx.fillText(`${this.ticks[idx]}`, xpos, 2*(font_height+1))
    })
    this.ctx.restore()
  }
}
