import canvas from 'canvas'
import { scaleUtc } from 'd3-scale'

export default {
  generateSunImage(sunArray, start, end, sunColors) {
    // sunArray:    Array containing objects of timestamps of every notable Sun's position.
    //    start:    Timestamp string, start of the whole dataset.
    //      end:    Timestamp string, end of the whole dataset.
    // sunColors:   Object containing color codes for every phase of the Sun's cycle.
    //              {night, astronomicalTwilight, nauticalTwilight, civilTwilight, day}

    const width = Math.floor((new Date(end) - new Date(start)) / 1000 / 60 / 10) // Time delta in minutes. Every ten minutes is one pixel.

    const height = 1
    const can = canvas.createCanvas(width, height)
    const context = can.getContext('2d')

    const x = scaleUtc()
      .domain([new Date(start), new Date(end)])
      .range([0, width])

    this.drawBackgroundRectangles(context, end, x, sunArray, sunColors)

    return can.toDataURL()
  },

  drawBackgroundRectangle(context, start, end, x, color) {
    // context: Canvas context, used for drawing into canvas.
    // start: Date object, start of the rectangle.
    // end: Date object, end of the rectangle.
    // x: D3 scale for x axis.
    //
    // The width of the rectangle is determined by the time range that is recomputed to pixel width,
    // using the D3 scale function x.

    const rectXStart = Math.floor(x(start))
    const rectXEnd = Math.floor(x(end))
    const rectWidth = rectXEnd - rectXStart + 1 // +1 when rounding errors would render thin lines.

    context.fillStyle = color

    context.fillRect(rectXStart, 0, rectWidth, 1)
  },
  drawBackgroundRectangles(context, end, x, sunArray, sunColors) {
    function isDateValid(d) {
      return d instanceof Date && !isNaN(d)
    }
    // Finite state machine for drawing all rectangles.
    var currentDayNumber = 0
    var state = 0
    var previousState = 0
    var nextNadir
    if (sunArray[0].sunUpNadir) {
      state = 5
    } else if (isDateValid(sunArray[0].astronomicalDawn)) {
      state = 1
    } else if (isDateValid(sunArray[0].nauticalDawn)) {
      state = 2
    } else if (isDateValid(sunArray[0].civilDawn)) {
      state = 3
    } else if (isDateValid(sunArray[0].sunrise)) {
      state = 4
    } else {
      state = 1
    }
    while (currentDayNumber < sunArray.length) {
      if (currentDayNumber < sunArray.length - 1) {
        nextNadir = sunArray[currentDayNumber + 1].nadir
      } else {
        nextNadir = new Date(end)
      }
      switch (state) {
        // Second half of night, from nadir to astronomical dawn.
        case 1:
          if (isDateValid(sunArray[currentDayNumber].astronomicalDawn)) {
            // Astronomical dawn has been found, draw from nadir to ast. dawn.
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].nadir,
              sunArray[currentDayNumber].astronomicalDawn,
              x,
              sunColors.night
            )
            state = 2
          } else if (isDateValid(sunArray[currentDayNumber].nauticalDawn)) {
            state = 2
          } else {
            // Astronomical dawn has not been found, draw perpetual night.
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].nadir,
              nextNadir,
              x,
              sunColors.night
            )
            currentDayNumber += 1
          }
          previousState = 1
          break

        // First astronomical twilight, from astronomical to nautical dawn.
        case 2:
          if (isDateValid(sunArray[currentDayNumber].nauticalDawn)) {
            this.drawBackgroundRectangle(
              context,
              previousState === 1 &&
                isDateValid(sunArray[currentDayNumber].astronomicalDawn)
                ? sunArray[currentDayNumber].astronomicalDawn
                : sunArray[currentDayNumber].nadir,
              sunArray[currentDayNumber].nauticalDawn,
              x,
              sunColors.astronomicalTwilight
            )
            state = 3
          } else if (isDateValid(sunArray[currentDayNumber].civilDawn)) {
            state = 3
          } else {
            this.drawBackgroundRectangle(
              // Nautical dawn hasn't been found, go to next night.
              context,
              sunArray[currentDayNumber].astronomicalDawn,
              sunArray[currentDayNumber].astronomicalDusk,
              x,
              sunColors.astronomicalTwilight
            )
            state = 9
          }
          previousState = 2
          break

        // First nautical twilight, from nautical to civil dawn.
        case 3:
          if (isDateValid(sunArray[currentDayNumber].civilDawn)) {
            this.drawBackgroundRectangle(
              context,
              previousState === 2 &&
                isDateValid(sunArray[currentDayNumber].nauticalDawn)
                ? sunArray[currentDayNumber].nauticalDawn
                : sunArray[currentDayNumber].nadir,
              sunArray[currentDayNumber].civilDawn,
              x,
              sunColors.nauticalTwilight
            )
            state = 4
          } else if (isDateValid(sunArray[currentDayNumber].sunrise)) {
            state = 4
          } else {
            this.drawBackgroundRectangle(
              // Civil dawn hasn't been found, go to nautical dusk.
              context,
              sunArray[currentDayNumber].nauticalDawn,
              sunArray[currentDayNumber].nauticalDusk,
              x,
              sunColors.nauticalTwilight
            )
            state = 8
          }
          previousState = 3
          break

        // First civil twilight, from civil dawn to sunrise.
        case 4:
          if (isDateValid(sunArray[currentDayNumber].sunrise)) {
            this.drawBackgroundRectangle(
              context,
              previousState === 3 &&
                isDateValid(sunArray[currentDayNumber].civilDawn)
                ? sunArray[currentDayNumber].civilDawn
                : sunArray[currentDayNumber].nadir,
              sunArray[currentDayNumber].sunrise,
              x,
              sunColors.civilTwilight
            )
            state = 5
          } else if (sunArray[currentDayNumber].sunUpZenith) {
            state = 5
          } else {
            this.drawBackgroundRectangle(
              // Sunrise hasn't been found, go to civil dusk.
              context,
              sunArray[currentDayNumber].civilDawn,
              sunArray[currentDayNumber].civilDusk,
              x,
              sunColors.civilTwilight
            )
            state = 7
          }
          previousState = 4
          break

        // Day, from sunrise to sunset.
        case 5:
          if (isDateValid(sunArray[currentDayNumber].sunset)) {
            this.drawBackgroundRectangle(
              context,
              previousState === 4 &&
                isDateValid(sunArray[currentDayNumber].sunrise)
                ? sunArray[currentDayNumber].sunrise
                : sunArray[currentDayNumber].nadir,
              sunArray[currentDayNumber].sunset,
              x,
              sunColors.day
            )
            state = 6
          } else {
            this.drawBackgroundRectangle(
              context,
              previousState === 4 &&
                isDateValid(sunArray[currentDayNumber].sunrise)
                ? sunArray[currentDayNumber].sunrise
                : sunArray[currentDayNumber].nadir,
              nextNadir,
              x,
              sunColors.day
            )
            currentDayNumber += 1
          }
          previousState = 5
          break

        // Second civil twilight, from sunset to civil dusk.
        case 6:
          if (isDateValid(sunArray[currentDayNumber].civilDusk)) {
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].sunset,
              sunArray[currentDayNumber].civilDusk,
              x,
              sunColors.civilTwilight
            )
            state = 7
          } else {
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].sunset,
              nextNadir,
              x,
              sunColors.civilTwilight
            )
            currentDayNumber += 1
            state = 4
          }
          previousState = 6
          break

        // Second nautical twilight, from civil to nautical dusk.
        case 7:
          if (isDateValid(sunArray[currentDayNumber].nauticalDusk)) {
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].civilDusk,
              sunArray[currentDayNumber].nauticalDusk,
              x,
              sunColors.nauticalTwilight
            )
            state = 8
          } else {
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].civilDusk,
              nextNadir,
              x,
              sunColors.nauticalTwilight
            )
            currentDayNumber += 1
            state = 3
          }
          previousState = 7
          break

        // Second astronomical twilight, from nautical to astronomical dusk.
        case 8:
          if (isDateValid(sunArray[currentDayNumber].astronomicalDusk)) {
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].nauticalDusk,
              sunArray[currentDayNumber].astronomicalDusk,
              x,
              sunColors.astronomicalTwilight
            )
            state = 9
          } else {
            this.drawBackgroundRectangle(
              context,
              sunArray[currentDayNumber].nauticalDusk,
              nextNadir,
              x,
              sunColors.astronomicalTwilight
            )
            currentDayNumber += 1
            state = 2
          }
          previousState = 8
          break

        // First half of night, from astronomical dusk to next nadir.
        case 9:
          this.drawBackgroundRectangle(
            context,
            sunArray[currentDayNumber].astronomicalDusk,
            nextNadir,
            x,
            sunColors.night
          )
          state = 1
          currentDayNumber += 1
          previousState = 9
          break

        default:
          currentDayNumber = sunArray.length + 1
          break
      }
    }
  }
}
