import DatasetService from '@/services/DatasetService'
import SunCalc from 'suncalc'
import gradientBackground from './gradient-background'

const _sendPostRequest = (state, commit, webCmd, isCommit = false) => {
  if (typeof webCmd === 'undefined') {
    return
  }
  commit('SET_SERVER_PENDING_CHANGES', !isCommit)
  commit(
    'SET_NUMBER_PENDING_POST_REQUESTS',
    state.server.numPendingPostRequests + 1
  )
  return webCmd()
    .then(() => {
      commit(
        'SET_NUMBER_PENDING_POST_REQUESTS',
        state.server.numPendingPostRequests - 1
      )
    })
    .catch(err => {
      console.log('POST failed: ' + err)
      commit(
        'SET_NUMBER_PENDING_POST_REQUESTS',
        state.server.numPendingPostRequests - 1
      )
    })
}

export default {
  loadMetadata({ commit, state, dispatch, getters }) {
    function generateSunData() {
      function addDay(date) {
        // Increment date by one day.
        var result = new Date(date)
        result.setDate(result.getDate() + 1)
        return result
      }

      // Fill sunArray with objects containing dawn and dusk times
      const sunArray = []

      var currentDate = state.timespan.start
      const endDate = state.timespan.end
      while (currentDate <= endDate) {
        const times = SunCalc.getTimes(
          currentDate,
          state.location.latitude,
          state.location.longitude
        )

        // Chceck if sun is over the horizon during its lowest point. That means it doesn't set and it's perpetual day.
        const sunUpNadir =
          SunCalc.getPosition(
            times.nadir,
            state.location.latitude,
            state.location.longitude
          ).altitude >= 0

        const sunUpZenith =
          SunCalc.getPosition(
            times.solarNoon,
            state.location.latitude,
            state.location.longitude
          ).altitude >= 0

        // Store all times in an object and push it into the array.
        sunArray.push({
          sunUpNadir,
          sunUpZenith,
          nadir: times.nadir,
          astronomicalDawn: times.nightEnd,
          nauticalDawn: times.nauticalDawn,
          civilDawn: times.dawn,
          sunrise: times.sunrise,
          sunset: times.sunset,
          civilDusk: times.dusk,
          nauticalDusk: times.nauticalDusk,
          astronomicalDusk: times.night
        })
        currentDate = addDay(currentDate)
      }

      return sunArray
    }

    return DatasetService.getMetadata()
      .then(response => {
        const data = response.data
        const sticks = data.sticks
        // Add GUI status for each stick: 'minimized', 'compact', 'full'
        for (const ID in sticks) {
          const stick = sticks[ID]
          stick.statusGUI = 'minimized'
        }
        commit('ADD_STICKS', sticks)
        dispatch('setFilter', {
          filterKind: 'maxSnowHeight',
          value: getters.maxStickLengthCm
        })

        // Add GUI status for each camera: 'minimized', 'full'
        for (const ID in data.cameras) {
          data.cameras[ID].statusGUI = 'full'
        }
        commit('ADD_CAMERAS', data.cameras)
        commit('SET_TIMESPAN', {
          start: new Date(data.start),
          end: new Date(data.end)
        })
        commit('SET_MAX_SNOW_HEIGHT', data.maxSnowHeight)
        commit('SET_LOCATION', data.location)

        const sunArray = generateSunData()
        commit('SET_SUN_DATA', sunArray)
        const sunImage = gradientBackground.generateSunImage(
          sunArray,
          data.start,
          data.end,
          state.sunColors
        )
        commit('SET_SUN_IMAGE', Object.freeze(sunImage))
      })
      .catch(err => {
        console.error('There was a problem loading metadata.')
        throw err
      })
  },
  loadDataByCamera({ commit, dispatch }, cameraID) {
    return DatasetService.getDataByCamera(cameraID).then(response => {
      const data = response.data
      if ('id' in data) {
        delete data.id
      }
      commit('ADD_DATA_BY_CAMERA', { data, cameraID })
      dispatch('buildImageChronology', cameraID)
      for (const imageName of Object.keys(data)) {
        for (const stickID of Object.keys(data[imageName].sticks)) {
          dispatch('updateSnowHeightAverage', { cameraID, imageName, stickID })
        }
      }
    })
  },
  buildImageChronology({ commit, state }, cameraID) {
    var imageTimesAndNames = []
    for (const [imageName, imageProps] of Object.entries(state.data[cameraID]))
      imageTimesAndNames.push([Date.parse(imageProps.dateTime), imageName])
    imageTimesAndNames.sort((a, b) => a[0] - b[0])
    const orderedImageNames = []
    const lookup = {}
    for (let idx = 0; idx < imageTimesAndNames.length; ++idx) {
      orderedImageNames.push(imageTimesAndNames[idx][1])
      lookup[imageTimesAndNames[idx][1]] = idx
    }
    const chronology = {
      order: orderedImageNames,
      lookup: lookup
    }
    commit('SET_IMAGE_CHRONOLOGY', { cameraID, chronology })
  },

  updateSnowHeightAverage(
    { commit, state, getters },
    { cameraID, imageName, stickID }
  ) {
    // If the stick is not visible, but there was snow in the picture, don't trust the average and therefore don't measure it.
    if (
      !state.data[cameraID][imageName].sticks[stickID].visible &&
      state.data[cameraID][imageName].weatherConditions === 1
    ) {
      commit('UPDATE_SNOW_HEIGHT_AVERAGE', {
        cameraID,
        imageName,
        stickID,
        average: -1
      })
      return
    }

    const camera = state.data[cameraID]
    const closeImageNames = getters.closeImageNames(
      cameraID,
      imageName,
      state.snowHeightAverageTimeWindow
    )
    let count = 0
    let heightSum = 0
    for (const currentImageName of closeImageNames) {
      const currentImage = camera[currentImageName]
      const stick = currentImage.sticks[stickID]
      if (
        !currentImage.discardedByUser &&
        !stick.discardedByUser &&
        stick.visible
      ) {
        // const deltaTime = Math.abs(
        //   new Date(currentImage.dateTime).getTime() - midTimestamp
        // )
        // const weight = (-1 / timeWindow) * deltaTime + 1
        const weight = 1
        heightSum += stick.snowHeight * weight
        count += 1
      }
    }
    const average = count === 0 ? 0 : heightSum / count
    commit('UPDATE_SNOW_HEIGHT_AVERAGE', {
      cameraID,
      imageName,
      stickID,
      average
    })
  },

  setImageDateTime({ state, commit }, { cameraID, imageName, dateTimeString }) {
    const webCmd = () =>
      DatasetService.setImageDateTime(cameraID, imageName, dateTimeString)
    _sendPostRequest(state, commit, webCmd)
    commit('SET_IMAGE_DATE_TIME', {
      cameraID,
      imageName,
      dateTimeString
    })
  },
  setTimeSpan({ state, commit }, { minDateTime, maxDateTime }) {
    const webCmd = () => DatasetService.setTimeSpan(minDateTime, maxDateTime)
    _sendPostRequest(state, commit, webCmd)
    commit('SET_TIMESPAN', {
      start: new Date(minDateTime),
      end: new Date(maxDateTime)
    })
  },
  setReciprocalImage(
    { state, commit },
    { cameraID, imageName, recipCameraID, closestRecipImageName }
  ) {
    const webCmd = () =>
      DatasetService.setReciprocalImage(
        cameraID,
        imageName,
        recipCameraID,
        closestRecipImageName
      )
    _sendPostRequest(state, commit, webCmd)
    commit('SET_RECIPROCAL_IMAGE', {
      cameraID,
      imageName,
      recipCameraID,
      closestRecipImageName
    })
  },
  setStickSnowHeight(
    { state, commit, dispatch, getters },
    { cameraID, imageName, stickID, snowHeight, sendDataToServer }
  ) {
    const webCmd = () =>
      DatasetService.setStickSnowHeight(
        cameraID,
        imageName,
        stickID,
        snowHeight
      )
    if (sendDataToServer) {
      _sendPostRequest(state, commit, webCmd)
    }
    commit('SET_STICK_SNOW_HEIGHT', {
      cameraID,
      imageName,
      stickID,
      snowHeight
    })
    const closeImageNames = getters.closeImageNames(
      cameraID,
      imageName,
      state.snowHeightAverageTimeWindow
    )
    for (const imageName of closeImageNames) {
      dispatch('updateSnowHeightAverage', { cameraID, imageName, stickID })
    }
  },
  setStickTop(
    { state, commit },
    { cameraID, imageName, stickID, point, sendDataToServer }
  ) {
    const webCmd = () =>
      DatasetService.setStickTop(cameraID, imageName, stickID, point)
    if (sendDataToServer) {
      _sendPostRequest(state, commit, webCmd)
    }
    commit('SET_STICK_TOP', {
      cameraID,
      imageName,
      stickID,
      point
    })
  },
  setStickBottom(
    { state, commit },
    { cameraID, imageName, stickID, point, sendDataToServer }
  ) {
    const webCmd = () =>
      DatasetService.setStickBottom(cameraID, imageName, stickID, point)
    if (sendDataToServer) {
      _sendPostRequest(state, commit, webCmd)
    }
    commit('SET_STICK_BOTTOM', {
      cameraID,
      imageName,
      stickID,
      point
    })
  },
  setFlagImageApprovedByUser(
    { state, commit },
    { cameraID, imageName, flagState }
  ) {
    const webCmd = () =>
      DatasetService.setFlagImageApprovedByUser(cameraID, imageName, flagState)
    _sendPostRequest(state, commit, webCmd)
    commit('SET_FLAG_IMAGE_APPROVED_BY_USER', {
      cameraID,
      imageName,
      flagState
    })
  },
  setFlagImageDiscardedByUser(
    { state, commit, getters, dispatch },
    { cameraID, imageName, flagState }
  ) {
    const webCmd = () =>
      DatasetService.setFlagImageDiscardedByUser(cameraID, imageName, flagState)
    _sendPostRequest(state, commit, webCmd)
    commit('SET_FLAG_IMAGE_DISCARDED_BY_USER', {
      cameraID,
      imageName,
      flagState,
      webCmd
    })
    const closeImageNames = getters.closeImageNames(
      cameraID,
      imageName,
      state.snowHeightAverageTimeWindow
    )
    for (const imageName of closeImageNames) {
      for (const stickID of getters.stickIDs) {
        if (getters.cameraByStick(stickID).includes(cameraID)) {
          dispatch('updateSnowHeightAverage', { cameraID, imageName, stickID })
        }
      }
    }
  },
  setFlagImageUsedForTraining(
    { state, commit },
    { cameraID, imageName, flagState }
  ) {
    const webCmd = () =>
      DatasetService.setFlagImageUsedForTraining(cameraID, imageName, flagState)
    _sendPostRequest(state, commit, webCmd)
    commit('SET_FLAG_IMAGE_USED_FOR_TRAINING', {
      cameraID,
      imageName,
      flagState
    })
  },
  setFlagStickCorrectedByUser(
    { state, commit },
    { cameraID, imageName, stickID, flagState }
  ) {
    const webCmd = () =>
      DatasetService.setFlagStickCorrectedByUser(
        cameraID,
        imageName,
        stickID,
        flagState
      )
    _sendPostRequest(state, commit, webCmd)
    commit('SET_FLAG_STICK_CORRECTED_BY_USER', {
      cameraID,
      imageName,
      stickID,
      flagState
    })
  },
  setFlagStickDiscardedByUser(
    { state, commit },
    { cameraID, imageName, stickID, flagState }
  ) {
    const webCmd = () =>
      DatasetService.setFlagStickDiscardedByUser(
        cameraID,
        imageName,
        stickID,
        flagState
      )
    _sendPostRequest(state, commit, webCmd)
    commit('SET_FLAG_STICK_DISCARDED_BY_USER', {
      cameraID,
      imageName,
      stickID,
      flagState
    })
  },
  // setFlagStickCreatedByUser(
  //   { state, commit },
  //   { cameraID, imageName, stickID, flagState }
  // ) {
  //   const webCmd = () =>
  //     DatasetService.setFlagStickCreatedByUser(
  //       cameraID,
  //       imageName,
  //       stickID,
  //       flagState
  //     )
  //   _sendPostRequest(state, commit, webCmd)
  //   commit('SET_FLAG_STICK_CREATED_BY_USER', {
  //     cameraID,
  //     imageName,
  //     stickID,
  //     flagState
  //   })
  // },
  setFlagStickVisible(
    { state, commit, getters, dispatch },
    { cameraID, imageName, stickID, flagState }
  ) {
    const webCmd = () =>
      DatasetService.setFlagStickVisible(
        cameraID,
        imageName,
        stickID,
        flagState
      )
    _sendPostRequest(state, commit, webCmd)
    commit('SET_FLAG_STICK_VISIBLE', {
      cameraID,
      imageName,
      stickID,
      flagState
    })

    const closeImageNames = getters.closeImageNames(
      cameraID,
      imageName,
      state.snowHeightAverageTimeWindow
    )
    for (const imageName of closeImageNames) {
      dispatch('updateSnowHeightAverage', { cameraID, imageName, stickID })
    }
  },
  commitChangesToDisk({ state, commit }) {
    const webCmd = () => DatasetService.commitChangesToDisk()
    _sendPostRequest(state, commit, webCmd)
  },
  setActivePoint({ commit, getters, state }, { stickID, cameraID, imageName }) {
    // Check stickID and cameraID validity.
    if (
      (getters.stickIDs.includes(stickID) || stickID === null) &&
      (getters.cameraByStick(stickID).includes(cameraID) || cameraID === null)
    ) {
      let imageNames = {}
      if (!imageName) {
        imageName = state.chronology[cameraID].order[0]
      }
      imageNames[cameraID] = imageName
      for (const cam in getters.chartData(cameraID)[imageName]
        .reciprocalImageName) {
        imageNames[cam] = getters.chartData(cameraID)[
          imageName
        ].reciprocalImageName[cam]
      }
      commit('SET_ACTIVE_POINT', {
        stickID,
        cameraID,
        imageNames
      })
    } else {
      throw {
        name: 'TypeError',
        message:
          'stickID: StickID is not a valid stick. || cameraID: CameraID is not a valid camera.'
      }
    }
  },
  setShowDaysGradient({ commit }, showDaysGradient) {
    if (typeof showDaysGradient === 'boolean') {
      commit('SET_SHOW_DAYS_GRADIENT', showDaysGradient)
    } else {
      throw {
        name: 'TypeError',
        message: 'setShowDaysGradient: showDaysGradient must be a boolean.'
      }
    }
  },
  setShowImageDialog({ commit }, showImageDialog) {
    if (typeof showImageDialog === 'boolean') {
      commit('SET_SHOW_IMAGE_DIALOG', showImageDialog)
    } else {
      throw {
        name: 'TypeError',
        message: 'setShowImageDialog: showImageDialog must be a boolean.'
      }
    }
  },
  setShowFilters({ commit }, showFilters) {
    if (typeof showFilters === 'boolean') {
      commit('SET_SHOW_FILTERS', showFilters)
    } else {
      throw {
        name: 'TypeError',
        message: 'setShowFilters: showFilters must be a boolean.'
      }
    }
  },
  setShowLegend({ commit }, showLegend) {
    if (typeof showLegend === 'boolean') {
      commit('SET_SHOW_LEGEND', showLegend)
    } else {
      throw {
        name: 'TypeError',
        message: 'setShowLegend: showLegend must be a boolean.'
      }
    }
  },
  setShowHelpDialog({ commit }, showHelpDialog) {
    if (typeof showHelpDialog === 'boolean') {
      commit('SET_SHOW_HELP_DIALOG', showHelpDialog)
    } else {
      throw {
        name: 'TypeError',
        message: 'setShowHelpDialog: showHelpDialog must be a boolean.'
      }
    }
  },
  setZoomState({ commit }, zoomState) {
    commit('SET_ZOOM_STATE', zoomState)
  },
  setStickStatusGUI({ commit }, { statusGUI, stickID }) {
    commit('SET_STICK_STATUS_GUI', { statusGUI, stickID })
  },
  setCameraStatusGUI({ commit }, { statusGUI, cameraID }) {
    commit('SET_CAMERA_STATUS_GUI', { statusGUI, cameraID })
  },
  setFilter({ commit }, { filterKind, value }) {
    commit('SET_FILTER', { filterKind, value })
  },
  checkForPendingChanges({ commit }) {
    DatasetService.multipostEnd().then(() => {
      DatasetService.checkForPendingChanges().then(response => {
        const value = response.data
        commit('SET_PENDING_CHANGES', { value })
      })
    })
  }
  // addStick({ commit, state }, { cameraID, imageName, stickID, stickData }) {
  //   if (
  //     Object.hasOwnProperty.call(
  //       state.data[cameraID][imageName].sticks,
  //       stickID
  //     )
  //   ) {
  //     console.error(
  //       'store.addStick(): ERROR: passed stick already exists => insertion has FAILED.'
  //     )
  //     return
  //   }
  //   if (
  //     !Object.hasOwnProperty.call(stickData, 'top') ||
  //     !Object.hasOwnProperty.call(stickData, 'bottom') ||
  //     !Object.hasOwnProperty.call(stickData, 'snowHeight') ||
  //     !Object.hasOwnProperty.call(stickData, 'visible') ||
  //     !Object.hasOwnProperty.call(stickData, 'correctedByUser') ||
  //     !Object.hasOwnProperty.call(stickData, 'discardedByUser') ||
  //     !Object.hasOwnProperty.call(stickData, 'createdByUser')
  //   ) {
  //     console.error(
  //       'store.addStick(): ERROR: passed stick data are wrong => insertion has FAILED.'
  //     )
  //     return
  //   }
  //   const webCmd = () =>
  //     DatasetService.addStick(cameraID, imageName, stickID, stickData)
  //   commit('ADD_STICK', {
  //     cameraID,
  //     imageName,
  //     stickID,
  //     stickData,
  //     webCmd
  //   })
  // }
}
