<template>
<div id="map-container">
  <v-overlay :value="error">
    <v-alert type="error">{{ error }}</v-alert>
  </v-overlay>
  <div id="map">
  </div>
  <div> <!-- This div holds the map overlays -->
    <div class="map-overlay top left expand-on-hover">
      <div class="map-overlay-icon">
        <v-icon>mdi-menu</v-icon>
      </div>
      <div class="map-overlay-inner">
        <h3>Background</h3>
        <form>
          <input id="lyr-osm" type="checkbox" value="osm" v-model="layerSelection"/>
          <label for="lyr-osm">OpenStreetMap</label>

          <input id="lyr-sea" type="checkbox" value="sea" v-model="layerSelection"/>
          <label for="lyr-sea">OpenSeaMap</label>

          <input id="lyr-nau" type="checkbox" value="nau" v-model="layerSelection" disabled/>
          <label for="lyr-nau" title="Scan of Norway's nautical charts – CURRENTLY UNAVAILABLE" disabled>Nautical charts (NO)</label>
        </form>

        <hr class="my-2"/>

        <h3>Layers</h3>

        <div class="lines-points">

          <span>Vessel track</span>
          <label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="navp" v-model="layerSelection"/></label>

          <!--
          <label title="Show lines" disabled><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="navl" v-model="layerSelection"/></label>
          -->

          <div>
            <v-menu bottom offset-y class="pb-1">
              <template v-slot:activator="{ on, attrs }">
                <v-icon style="margin-right: 3px;" small v-bind="attrs" v-on="on" :title="`Show lines.\nCurrently selected period: ${vesselTrackPeriodSettings[vesselTrackPeriod].title}. Click to change`">mdi-vector-line</v-icon>
              </template>
              <v-list nav dense>
                <v-list-item @click="vesselTrackPeriod = 'hour'">
                  <v-list-item-content>
                    <v-list-item-title>Last hour</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item @click="vesselTrackPeriod = 'hour6'">
                  <v-list-item-content>
                    <v-list-item-title>Last 6 hours</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item @click="vesselTrackPeriod = 'hour12'">
                  <v-list-item-content>
                    <v-list-item-title>Last 12 hours</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item @click="vesselTrackPeriod = 'day'">
                  <v-list-item-content>
                    <v-list-item-title>Last 24 hours</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item @click="vesselTrackPeriod = 'week'">
                  <v-list-item-content>
                    <v-list-item-title>Last week</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
              </v-list>
            </v-menu>
          <input type="checkbox" value="navl" v-model="layerSelection"/>
         </div>

          <label><!-- No heatmap available --></label>

          <span>Sail lines</span>
          <label title="Show points" disabled><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="pslp" v-model="layerSelection"/></label>
          <label title="Show lines" disabled><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="psll" v-model="layerSelection"/></label>
          <label><!-- No heatmap available --></label>

          <span>Preplots</span>
          <label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="pplp" v-model="layerSelection"/></label>
          <label title="Show lines"><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="ppll" v-model="layerSelection"/></label>
          <label><!-- No heatmap available --></label>

          <span>Plan</span>
          <!-- We do not show individual points for planned lines. There are the preplots for that.
          <label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="planp" v-model="layerSelection"/></label>
          -->
          <span><!-- Necessary for grid alignment purposes --></span>
          <label title="Show lines"><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="planl" v-model="layerSelection"/></label>
          <label><!-- No heatmap available --></label>

          <span>Raw data</span>
          <label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="seqrp" v-model="layerSelection"/></label>
          <label title="Show lines"><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="seqrl" v-model="layerSelection"/></label>

          <div>
            <v-menu bottom offset-y class="mr-1 pb-1">
              <template v-slot:activator="{ on, attrs }">
                <v-icon small left class="mx-0" v-bind="attrs" v-on="on" :title="`Show deviations.\nCurrently selected mode: ${heatmapTitle}. Click to change`">mdi-dots-grid</v-icon>
              </template>
              <v-list nav dense>
                <v-list-item @click="setHeatmapValue('total_error')">
                  <v-list-item-content>
                    <v-list-item-title>Δ<span style="text-decoration:overline;">ij</span> Total error</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item @click="setHeatmapValue('delta_j')">
                  <v-list-item-content>
                    <v-list-item-title>Δj Inline error</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item @click="setHeatmapValue('delta_i')">
                  <v-list-item-content>
                    <v-list-item-title>Δi Crossline error</v-list-item-title>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item>
                  <v-list-item-content>
                    <v-menu bottom offset-y>
                      <template v-slot:activator="{ on, attrs }">
                        <v-list-item-title v-bind="attrs" v-on="on">
                          Raw vs final <v-icon small right>mdi-chevron-right</v-icon>
                        </v-list-item-title>
                      </template>
                      <v-list nav dense>
                        <v-list-item @click="setHeatmapValue('co_total_error')">
                          <v-list-item-content>
                            <v-list-item-title>Δ<span style="text-decoration:overline;">ij</span> Total error</v-list-item-title>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item @click="setHeatmapValue('co_delta_j')">
                          <v-list-item-content>
                            <v-list-item-title>Δj Inline error</v-list-item-title>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item @click="setHeatmapValue('co_delta_i')">
                          <v-list-item-content>
                            <v-list-item-title>Δi Crossline error</v-list-item-title>
                          </v-list-item-content>
                        </v-list-item>
                      </v-list>
                    </v-menu>
                  </v-list-item-content>
                </v-list-item>
                <v-list-item>
                  <v-list-item-content>
                    <v-menu bottom offset-y>
                      <template v-slot:activator="{ on, attrs }">
                        <v-list-item-title v-bind="attrs" v-on="on">
                          Gun data <v-icon small right>mdi-chevron-right</v-icon>
                        </v-list-item-title>
                      </template>
                      <v-list nav dense>
                        <v-list-item>
                          <v-list-item-content>
                            <v-menu bottom offset-y>
                              <template v-slot:activator="{ on, attrs }">
                                <v-list-item-title v-bind="attrs" v-on="on">
                                  Pressure <v-icon small right>mdi-chevron-right</v-icon>
                                </v-list-item-title>
                              </template>
                              <v-list nav dense>
                                <v-list-item @click="setHeatmapValue('press_μ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Mean (μ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('press_σ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Standard deviation (σ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('press_R')">
                                  <v-list-item-content>
                                    <v-list-item-title>Range (R)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                              </v-list>
                            </v-menu>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item>
                          <v-list-item-content>
                            <v-menu bottom offset-y>
                              <template v-slot:activator="{ on, attrs }">
                                <v-list-item-title v-bind="attrs" v-on="on">
                                  Depths <v-icon small right>mdi-chevron-right</v-icon>
                                </v-list-item-title>
                              </template>
                              <v-list nav dense>
                                <v-list-item @click="setHeatmapValue('depth_μ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Mean (μ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('depth_σ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Standard deviation (σ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('depth_R')">
                                  <v-list-item-content>
                                    <v-list-item-title>Range (R)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                              </v-list>
                            </v-menu>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item>
                          <v-list-item-content>
                            <v-menu bottom offset-y>
                              <template v-slot:activator="{ on, attrs }">
                                <v-list-item-title v-bind="attrs" v-on="on">
                                  Deltas <v-icon small right>mdi-chevron-right</v-icon>
                                </v-list-item-title>
                              </template>
                              <v-list nav dense>
                                <v-list-item @click="setHeatmapValue('delta_μ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Mean (μ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('delta_σ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Standard deviation (σ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('delta_R')">
                                  <v-list-item-content>
                                    <v-list-item-title>Range (R)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                              </v-list>
                            </v-menu>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item>
                          <v-list-item-content>
                            <v-menu bottom offset-y>
                              <template v-slot:activator="{ on, attrs }">
                                <v-list-item-title v-bind="attrs" v-on="on">
                                  Delay <v-icon small right>mdi-chevron-right</v-icon>
                                </v-list-item-title>
                              </template>
                              <v-list nav dense>
                                <v-list-item @click="setHeatmapValue('delay_μ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Mean (μ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('delay_σ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Standard deviation (σ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('delay_R')">
                                  <v-list-item-content>
                                    <v-list-item-title>Range (R)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                              </v-list>
                            </v-menu>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item>
                          <v-list-item-content>
                            <v-menu bottom offset-y>
                              <template v-slot:activator="{ on, attrs }">
                                <v-list-item-title v-bind="attrs" v-on="on">
                                  Fill time <v-icon small right>mdi-chevron-right</v-icon>
                                </v-list-item-title>
                              </template>
                              <v-list nav dense>
                                <v-list-item @click="setHeatmapValue('fill_μ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Mean (μ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('fill_σ')">
                                  <v-list-item-content>
                                    <v-list-item-title>Standard deviation (σ)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('fill_R')">
                                  <v-list-item-content>
                                    <v-list-item-title>Range (R)</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                              </v-list>
                            </v-menu>
                          </v-list-item-content>
                        </v-list-item>
                        <v-list-item>
                          <v-list-item-content>
                            <v-menu bottom offset-y>
                              <template v-slot:activator="{ on, attrs }">
                                <v-list-item-title v-bind="attrs" v-on="on">
                                  Misfires <v-icon small right>mdi-chevron-right</v-icon>
                                </v-list-item-title>
                              </template>
                              <v-list nav dense>
                                <v-list-item @click="setHeatmapValue('no_fire')">
                                  <v-list-item-content>
                                    <v-list-item-title>No fire</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('autofire')">
                                  <v-list-item-content>
                                    <v-list-item-title>Autofire</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                                <v-list-item @click="setHeatmapValue('misfire')">
                                  <v-list-item-content>
                                    <v-list-item-title>No fire / autofire</v-list-item-title>
                                  </v-list-item-content>
                                </v-list-item>
                              </v-list>
                            </v-menu>
                          </v-list-item-content>
                        </v-list-item>
                      </v-list>
                    </v-menu>
                  </v-list-item-content>
                </v-list-item>
              </v-list>
            </v-menu>
         <input type="checkbox" value="seqrh" v-model="layerSelection"/>
         </div>

<!--
          <label title="Show position error"><v-icon small left class="mx-0">mdi-dots-grid</v-icon> <input type="checkbox" value="seqrh" v-model="layerSelection"/></label>
-->

          <span>Final data</span>
          <label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="seqfp" v-model="layerSelection"/></label>
          <label title="Show lines"><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="seqfl" v-model="layerSelection"/></label>
          <label><!-- No heatmap available --></label>

          <span>Events</span>
          <label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="log" v-model="layerSelection"/></label>
          <label><!-- No lines available --></label>
          <label><!-- No heatmap available --></label>

          <template v-if="crosshairsPositions.length">
          <span>Markers</span>
          <label title="Show crosshair markers"><v-icon small left class="mx-0">mdi-crosshairs</v-icon> <input type="checkbox" value="crosshairs" v-model="layerSelection"/></label>
          <label title="Show marker labels" v-if="crosshairsLabels.length"><v-icon small left class="mx-0">mdi-format-text-variant</v-icon> <input type="checkbox" value="labels" v-model="layerSelection"/></label>
          <label v-else><!-- No labels provided --></label>
          <label><!-- No heatmap available --></label>
          </template>

        </div>
<!--
          <input id="lyr-psl" type="checkbox" value="psl" v-model="layerSelection"/>
          <label for="lyr-psl" title="Vessel preplots">Sail lines</label>

          <input id="lyr-ppl" type="checkbox" value="ppl" v-model="layerSelection"/>
          <label for="lyr-ppl" title="Source preplots">Preplot lines</label>

          <input id="lyr-plan" type="checkbox" value="plan" v-model="layerSelection"/>
          <label for="lyr-plan" title="Sequences in the planner">Planned lines</label>

          <input id="lyr-preplots" type="checkbox" value="preplots" v-model="layerSelection"/>
          <label for="lyr-preplots" title="Shotpoint deviation from preplot position">Preplot error</label>

          <input id="lyr-peh" type="checkbox" value="peh" v-model="layerSelection"/>
          <label for="lyr-peh" title="Shotpoint deviation from preplot position">Preplot error (heatmap)</label>

          <input id="lyr-seq" type="checkbox" value="seq" v-model="layerSelection"/>
          <label for="lyr-seq" title="Raw and/or final shot positions">Sequence data</label>

          <input id="lyr-log" type="checkbox" value="log" v-model="layerSelection"/>
          <label for="lyr-log" title="Event positions">Event log data</label>

          <input id="lyr-nav" type="checkbox" value="nav" v-model="layerSelection"/>
          <label for="lyr-nav" title="Vessel track">Navigation trail</label>
-->

        <!-- QC data: This section is meant to show (some) QC results in a graphical way,
             as 3D columns with lengths proportional to the QC values. Not implemented
             at this time. -->
        <!-- BEGIN QC data
        <h3
          class="mt-3"
          title="The selected attribute will be displayed as different length ‘columns’ in 3D view. Use Ctrl or Shift + Left Click to tilt the map."
        >QC data aspect</h3>
        <v-select
          class="mt-1"
          style="max-width: 20ex;"
          dense
          hide-details
          clearable
          :items="[ 'Depth', 'Pressure', 'Delta' ]"
        >
        </v-select>
        END QC data -->

<!--
        <hr class="my-2"/>

        <div title="Not yet implemented">
          <v-btn
            small
            text
            disabled
          >
            <v-icon small left>mdi-cog-outline</v-icon>
            Map settings
          </v-btn>
        </div>
-->
      </div>
    </div>

    <div class="map-overlay top right">
      <!-- TODO Implement search -->
      <!--
      <div>
        <input v-show="searchVisible"
          type="text"
          class="mr-2"
          placeholder="Search for a point or event"
          v-model="searchText"
        />
        <v-icon
          class="my-1"
          title="Find element"
          @click="searchVisible = !searchVisible"
          >mdi-crosshairs</v-icon>
      </div>
      -->
      <!-- TODO Implement filtering -->
      <!--
      <div>
        <input v-show="filterVisible"
          type="text"
          class="mr-2"
          placeholder="Filter by sequence or line"
          v-model="filterText"
        />
        <v-icon
          class="my-1"
          title="Filter items"
          @click="filterVisible = !filterVisible"
          >mdi-filter-outline</v-icon>
      </div>
      -->
      <div>
        <v-icon
          class="my-1 mt-4"
          title="Fit view"
          @click="zoomReset"
          >mdi-magnify-scan</v-icon>
      </div>
      <div>
        <v-icon
          class="my-1"
          title="Zoom in"
          @click="zoomIn"
          >mdi-magnify-plus-outline</v-icon>
      </div>
      <div>
        <v-icon
          class="my-1"
          title="Zoom out"
          @click="zoomOut"
          >mdi-magnify-minus-outline</v-icon>
      </div>
      <div>
        <v-icon
          class="my-1"
          :title="isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'"
          @click="toggleFullscreen"
        >{{ isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen' }}</v-icon>
      </div>
    </div>

    <div class="map-overlay bottom right">
      <div>
        <v-progress-circular v-if="loading || loadingProgress !== null"
          size="42"
          color="primary"
          title="Loading data…"
          :indeterminate="loadingProgress === null"
          :value="loadingProgress"
        >{{ Math.round(loadingProgress) }}%</v-progress-circular>
        <div v-else>
          <v-icon
            class="my-1"
            title="Manual refresh"
            @click="refresh"
            >mdi-cloud-refresh-variant-outline</v-icon>
        </div>
      </div>
    </div>

    <div class="map-overlay bottom left">
      <div>
      </div>
    </div>

  </div>
</div>
</template>

<style scoped>
#map {
  height: 100%;
  background-color: #dae4f7;
}

.theme--dark #map {
  background-color: #111;
}

.theme--dark .map-overlay-inner, .theme--dark .map-overlay-icon {
  background-color: #222;
}

.map-overlay {
  font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
  position: absolute;
  z-index: 1;
}

.top {
  top: 8px;
}

.bottom {
  bottom: 8px;
}

.left {
  left: 8px;
  max-width: 20%;
}

.right {
  right: 8px;
  max-width: 20%;
  text-align: right;
}

.map-overlay-icon {
  background-color: white;
  border: 1px solid black;
  border-radius: 3px;
}

.map-overlay-inner {
  padding: 10px;
  background-color: white;
  border: 1px solid black;
  border-radius: 3px;
}

.map-overlay-inner form {
  display: grid;
  grid-template-columns: 36px 1fr;
}

.map-overlay-inner .lines-points {
  display: grid;
  grid-template-columns: minmax(auto, 1fr) 36px 36px 36px;
  grid-column-gap: 4px;
}

.map-overlay-inner .lines-points :nth-child(4n+1) {
  margin-right: 6px;
}

.map-overlay input[type=text] {
  background-color: white;
  border: 1px solid black;
  border-radius: 4px;
}

.expand-on-hover:hover .map-overlay-icon {
  display: none;
}

.expand-on-hover:not(:hover) .map-overlay-inner {
  display: none;
}

.expand-on-hover:hover .map-overlay-inner {
  display: block;
}

/* BEGIN
 * This is meant to be a possible solution for keeping
 * the layers menu open while selecting from a v-select
 * in the context of displaying QC values. There may be
 * better solutions.
 */

.expand-on-hover:focus-within .map-overlay-icon {
  display: none;
}

.expand-on-hover:focus-within .map-overlay-inner {
  display: block;
}
/*
 * END
 */

</style>

<script>

import MapLayersMixin from './MapLayersMixin';
import MapTooltipsMixin from './MapTooltipsMixin';
import { mapActions, mapGetters, mapState } from 'vuex';
import zoomFitIcon from '@/assets/zoom-fit-best.svg'

import { Deck, FlyToInterpolator } from '@deck.gl/core';
import { IconLayer, TextLayer } from '@deck.gl/layers';
import { DougalBinaryBundle, DougalBinaryChunkSequential, DougalBinaryChunkInterleaved } from '@dougal/binary';

let deck;

/* Decoded SVG:
 * <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path style="fill:#ff0000;fill-opacity:1;stroke:none" d="M 7 3 L 7 4.03125 A 4.5 4.5 0 0 0 3.0332031 8 L 2 8 L 2 9 L 3.03125 9 A 4.5 4.5 0 0 0 7 12.966797 L 7 14 L 8 14 L 8 12.96875 A 4.5 4.5 0 0 0 11.966797 9 L 13 9 L 13 8 L 11.96875 8 A 4.5 4.5 0 0 0 8 4.0332031 L 8 3 L 7 3 z M 7 5.0390625 L 7 8 L 4.0410156 8 A 3.5 3.5 0 0 1 7 5.0390625 z M 8 5.0410156 A 3.5 3.5 0 0 1 10.960938 8 L 8 8 L 8 5.0410156 z M 4.0390625 9 L 7 9 L 7 11.958984 A 3.5 3.5 0 0 1 4.0390625 9 z M 8 9 L 10.958984 9 A 3.5 3.5 0 0 1 8 11.960938 L 8 9 z " />
</svg>
 */
const hashMarkerIconURL = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4KPHBhdGggc3R5bGU9ImZpbGw6I2ZmMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgZD0iTSA3IDMgTCA3IDQuMDMxMjUgQSA0LjUgNC41IDAgMCAwIDMuMDMzMjAzMSA4IEwgMiA4IEwgMiA5IEwgMy4wMzEyNSA5IEEgNC41IDQuNSAwIDAgMCA3IDEyLjk2Njc5NyBMIDcgMTQgTCA4IDE0IEwgOCAxMi45Njg3NSBBIDQuNSA0LjUgMCAwIDAgMTEuOTY2Nzk3IDkgTCAxMyA5IEwgMTMgOCBMIDExLjk2ODc1IDggQSA0LjUgNC41IDAgMCAwIDggNC4wMzMyMDMxIEwgOCAzIEwgNyAzIHogTSA3IDUuMDM5MDYyNSBMIDcgOCBMIDQuMDQxMDE1NiA4IEEgMy41IDMuNSAwIDAgMSA3IDUuMDM5MDYyNSB6IE0gOCA1LjA0MTAxNTYgQSAzLjUgMy41IDAgMCAxIDEwLjk2MDkzOCA4IEwgOCA4IEwgOCA1LjA0MTAxNTYgeiBNIDQuMDM5MDYyNSA5IEwgNyA5IEwgNyAxMS45NTg5ODQgQSAzLjUgMy41IDAgMCAxIDQuMDM5MDYyNSA5IHogTSA4IDkgTCAxMC45NTg5ODQgOSBBIDMuNSAzLjUgMCAwIDEgOCAxMS45NjA5MzggTCA4IDkgeiAiIC8+Cjwvc3ZnPgo=";


export default {
  name: "Map",

  mixins: [
    MapLayersMixin,
    MapTooltipsMixin
  ],

  components: {

  },

  data () {
    return {
      layerSelection: [],
      layersAvailable: {},
      sequenceDataElements: [],
      sequenceBinaryData: { positions: new Float32Array(0), values: [], udv: 2 },
      sequenceBinaryDataFinal: { positions: new Float32Array(0), values: [], udv: 3 },
      sequenceDataTStamp: null,
      loadingProgress: null,
      //loadingRefreshInterval: 6000, // Refresh sequenceBinaryData* every six seconds while loading
      loadingRefreshInterval: 0,
      viewState: {},
      viewStateDefaults: {
        //maxZoom: 18,
        maxPitch: 89
      },

      vesselPosition: null,
      vesselTrackLastRefresh: 0,
      vesselTrackRefreshInterval: 12, // seconds
      vesselTrackIntervalID: null,
      vesselTrackPeriod: "hour",
      vesselTrackPeriodSettings: {
        hour: {
          title: "1 hour",
          offset: 3600 * 1000,
          decimation: 1,
          refreshInterval: 18,
        },
        hour6: {
          title: "6 hours",
          offset: 6 * 3600 * 1000,
          decimation: 1,
          refreshInterval: 18,
        },
        hour12: {
          title: "12 hours",
          offset: 12 * 3600 * 1000,
          decimation: 1,
          refreshInterval: 18,
        },
        day: {
          title: "24 hours",
          offset: 24 * 3600 * 1000,
          decimation: 12,
          refreshInterval: 18,
        },
        week: {
          title: "7 days",
          offset: 7 * 24 * 3600 * 1000,
          decimation: 60,
          refreshInterval: 60,
        },
        week2: {
          title: "14 days",
          offset: 14 * 24 * 3600 * 1000,
          decimation: 60,
          refreshInterval: 90,
        },
        month: {
          title: "30 days",
          offset: 30 * 24 * 3600 * 1000,
          decimation: 90,
          refreshInterval: 120,
        },
        quarter: {
          title: "90 days",
          offset: 90 * 24 * 3600 * 1000,
          decimation: 180,
          refreshInterval: 300,
        },
        year: {
          title: "1 year",
          offset: 365 * 24 * 3600 * 1000,
          decimation: 1200,
          refreshInterval: 1800,
        },
      },
      heatmapValue: "total_error",
      isFullscreen: false,
      crosshairsPositions: [],
      crosshairsLabels: [],

      searchText: "",
      searchVisible: false,
      filterText: "",
      filterVisible: false,

      error: null,
    };
  },

  computed: {

    lineTStamp () {
      return this.$store.state.line.timestamp;
    },

    sequenceTStamp () {
      return this.$store.state.sequence.timestamp;
    },

    sequenceData () {
      return this.sequenceDataElements?.map( el => el.data );
    },

    /*
    sequenceBinaryData() {
      return this.getSequenceBinaryData(2); // Raw + gun data
    },

    sequenceBinaryDataFinal() {
      return this.getSequenceBinaryData(3); // Final + c-o data
    },
    */

    heatmapTitle () {

      let title = this.heatmapValue;

      switch (this.heatmapValue) {
        // Raw vs preplot
        case "total_error":
          title = "Total position error (raw data)";
          break;
        case "delta_i":
          title = "Total crossline position error (raw data)";
          break;
        case "delta_j":
          title = "Total inline position error (raw data)";
          break;

        // Final vs raw (c-o)
        case "co_total_error":
          title = "Total position error (final vs raw)";
          break;
        case "co_delta_i":
          title = "Total crossline position error (final vs raw)";
          break;
        case "co_delta_j":
          title = "Total inline position error (final vs raw)";
          break;

        // Gun deltas
        case "delta_μ":
          title = "Guns mean delta error";
          break;
        case "delta_σ":
          title = "Guns delta standard deviation (1⋅σ)";
          break;
        case "delta_R":
          title = "Guns delta range";
          break;

        // Gun pressure
        case "press_μ":
          title = "Fired array guns mean pressure";
          break;
        case "press_σ":
          title = "Fired array pressure standard deviation (1⋅σ)";
          break;
        case "press_R":
          title = "Fired array pressure range";
          break;

        // Gun depth
        case "depth_μ":
          title = "Guns mean depth";
          break;
        case "depth_σ":
          title = "Gun depth standard deviation (1⋅σ)";
          break;
        case "depth_R":
          title = "Gun depth range";
          break;

        // Gun fill time
        case "fill_μ":
          title = "Guns mean fill time";
          break;
        case "fill_σ":
          title = "Guns fill time standard deviation (1⋅σ)";
          break;
        case "fill_R":
          title = "Guns fill time range";
          break;

        // Gun delay
        case "delay_μ":
          title = "Guns mean firing delay";
          break;
        case "delay_σ":
          title = "Guns firing delay standard deviation (1⋅σ)";
          break;
        case "delay_R":
          title = "Guns firing delay range";
          break;
        default:
      }

      return title;
    },

    ...mapGetters(['user', 'loading', 'serverEvent', 'lineName', 'serverEvent', 'lines', 'plannedSequences', 'sequences', 'labels']),
    ...mapState({projectSchema: state => state.project.projectSchema})
  },

  watch: {
    layerSelection (newVal, oldVal) {
      this.render();
    },

    viewState: {
      handler () {
        this.setViewState();
      },
      deep: true
    },

    vesselTrackPeriod () {
      this.updateVesselIntervalTimer();
    },

    vesselTrackLastRefresh () {
      this.render();
    },

    lines () {
      // Refresh map on change of preplot data
      this.render();
    },

    plannedSequences () {
      // Refresh map on change of planned lines
      this.render();
    },

    sequences () {
      // Refresh map on change of sequence data (raw / final)
      this.sequenceDataTStamp = Date.now();
      this.render();
    },

    labels () {
      // Refresh the events layer on labels change
      if (this.layerSelection.includes("log")) {
        this.render();
      }
    },

    sequenceDataElements () {
      //console.log("seq data changed");
      this.sequenceDataTStamp = Date.now();
      //this.render();
    },

    $route (to, from) {
      if (to.name == "map") {
        this.decodeURLHash();
      }
    },

  },

  methods: {
    initView () {
    },

    render () {
      if (deck) {
        const layers = [];
        for (const name in this.layersAvailable) {
          //console.log("Visible", name, this.layerSelection.includes(name));
          const fn = this.layersAvailable[name];

          if (typeof fn != 'function') {
            throw new Error(`Layer ${name}: expected a function, got ${typeof fn}`);
          }

          layers.push(fn({
            visible: this.layerSelection.includes(name)
          }));
        }

        //console.log("setProps", this.sequenceDataTStamp, deck.layerManager && deck.layerManager.getLayers().length);

        //deck.setProps({layers, initialViewState: this.viewState});
        deck.setProps({layers});
        this.updateURL();
      }
    },

    zoomReset() {
      if (deck) {
        const bounds = this.getBBox(deck);
        if (bounds) {
          const viewport = deck.getViewports()[0];
          const newViewState = {
            ...this.viewStateDefaults,
            ...viewport.fitBounds(bounds)
          };
          newViewState.transitionDuration = 500;
          newViewState.transitionInterpolator = new FlyToInterpolator();
          deck.setProps({ initialViewState: newViewState });
        } else {
          console.warn('Unable to calculate bounding box');
        }
      } else {
        console.warn('Deck instance not available');
      }
    },

    zoomIn () {
      if (deck) {
        const viewState = deck.getViewports()[0];
        const initialViewState = {...this.viewStateDefaults, ...viewState};
        initialViewState.zoom += 1;
        initialViewState.transitionDuration = 300;
        deck.setProps({initialViewState});
      }
    },

    zoomOut () {
      if (deck) {
        const viewState = deck.getViewports()[0];
        const initialViewState = {...this.viewStateDefaults, ...viewState};
        initialViewState.zoom -= 1;
        initialViewState.transitionDuration = 300;
        deck.setProps({initialViewState});
      }
    },

    toggleFullscreen() {
      const mapElement = document.getElementById('map-container');
      if (!this.isFullscreen) {
        if (mapElement.requestFullscreen) {
          mapElement.requestFullscreen();
        } else if (mapElement.webkitRequestFullscreen) { // Safari
          mapElement.webkitRequestFullscreen();
        } else if (mapElement.msRequestFullscreen) { // IE11
          mapElement.msRequestFullscreen();
        }
        this.isFullscreen = true;
      } else {
        if (document.exitFullscreen) {
          document.exitFullscreen();
        } else if (document.webkitExitFullscreen) {
          document.webkitExitFullscreen();
        } else if (document.msExitFullscreen) {
          document.msExitFullscreen();
        }
        this.isFullscreen = false;
      }
    },

    async refresh () {
      if (window.caches) {
        const cache = await caches.open("dougal");
        for (const key of await cache.keys()) {
          // Match only resolved requests
          if (key instanceof Request) {
            if (key.url?.match(`/project/${this.$route.params.project}`)) {
              console.log("removing", key.method, key.url);
              cache.delete(key);
            }
          }
        }
      }
      await this.$root.sleep(300);
      this.sequenceDataElements = [];
      this.getSequenceData();
    },

    getBBox(deck, margin = 0.1) {
      if (!deck || !deck.layerManager) {
        console.warn('Invalid deck instance or layer manager');
        return null;
      }

      let λ0 = +Infinity, φ0 = +Infinity, λ1 = -Infinity, φ1 = -Infinity;

      const nonTileLayerTypes = ['ScatterplotLayer', 'GeoJsonLayer', 'LineLayer', 'IconLayer', 'DougalSequenceLayer'];

      deck.layerManager.layers.forEach(layer => {
        // Skip tile-based layers
        if (['TileLayer', 'MVTLayer'].includes(layer.constructor.layerName)) {
          return;
        }

        // Only process visible layers
        if (!layer.props.visible || !nonTileLayerTypes.includes(layer.constructor.layerName)) {
          return;
        }

        let data = layer.props.data;

        // Handle DougalSequenceLayer
        if (layer.constructor.layerName === 'DougalSequenceLayer') {
          const { positions } = data;
          if (positions && positions.length) {
            for (let i = 0; i < positions.length; i += 2) {
              const lon = positions[i];
              const lat = positions[i + 1];
              if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                λ0 = Math.min(λ0, lon);
                φ0 = Math.min(φ0, lat);
                λ1 = Math.max(λ1, lon);
                φ1 = Math.max(φ1, lat);
              }
            }
          }
        } else if (layer.constructor.layerName === 'ScatterplotLayer') {
          // Direct point data (e.g., navp)
          if (Array.isArray(data)) {
            data.forEach(d => {
              const [lon, lat] = layer.props.getPosition(d);
              if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                λ0 = Math.min(λ0, lon);
                φ0 = Math.min(φ0, lat);
                λ1 = Math.max(λ1, lon);
                φ1 = Math.max(φ1, lat);
              }
            });
          } else if (data && data.attributes && data.attributes.getPosition) {
            // Binary data
            const positions = data.attributes.getPosition.value;
            for (let i = 0; i < data.length * 2; i += 2) {
              const lon = positions[i];
              const lat = positions[i + 1];
              if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                λ0 = Math.min(λ0, lon);
                φ0 = Math.min(φ0, lat);
                λ1 = Math.max(λ1, lon);
                φ1 = Math.max(φ1, lat);
              }
            }
          }
        } else if (layer.constructor.layerName === 'GeoJsonLayer') {
          // Extract points from GeoJSON features
          let featureCollections = Array.isArray(data) ? data : [data];
          featureCollections.forEach(item => {
            let features = item;
            if (item?.type === 'FeatureCollection') {
              features = item.features || [];
            }
            if (Array.isArray(features)) {
              features.forEach(feature => {
                if (feature.geometry?.type === 'Point') {
                  const [lon, lat] = feature.geometry.coordinates;
                  if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                    λ0 = Math.min(λ0, lon);
                    φ0 = Math.min(φ0, lat);
                    λ1 = Math.max(λ1, lon);
                    φ1 = Math.max(φ1, lat);
                  }
                } else if (feature.geometry?.type === 'LineString') {
                  feature.geometry.coordinates.forEach(([lon, lat]) => {
                    if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                      λ0 = Math.min(λ0, lon);
                      φ0 = Math.min(φ0, lat);
                      λ1 = Math.max(λ1, lon);
                      φ1 = Math.max(φ1, lat);
                    }
                  });
                } else if (feature.geometry?.type === 'Polygon') {
                  feature.geometry.coordinates[0].forEach(([lon, lat]) => {
                    if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                      λ0 = Math.min(λ0, lon);
                      φ0 = Math.min(φ0, lat);
                      λ1 = Math.max(λ1, lon);
                      φ1 = Math.max(φ1, lat);
                    }
                  });
                }
              });
            }
          });
        } else if (layer.constructor.layerName === 'LineLayer') {
          // Extract source and target positions
          if (Array.isArray(data)) {
            data.forEach(d => {
              const source = layer.props.getSourcePosition(d);
              const target = layer.props.getTargetPosition(d);
              if (source && source[0] != null && source[1] != null && isFinite(source[0]) && isFinite(source[1])) {
                λ0 = Math.min(λ0, source[0]);
                φ0 = Math.min(φ0, source[1]);
                λ1 = Math.max(λ1, source[0]);
                φ1 = Math.max(φ1, source[1]);
              }
              if (target && target[0] != null && target[1] != null && isFinite(target[0]) && isFinite(target[1])) {
                λ0 = Math.min(λ0, target[0]);
                φ0 = Math.min(φ0, target[1]);
                λ1 = Math.max(λ1, target[0]);
                φ1 = Math.max(φ1, target[1]);
              }
            });
          }
        } else if (layer.constructor.layerName === 'IconLayer') {
          // Single point (e.g., crosshairs)
          if (Array.isArray(data)) {
            data.forEach(d => {
              const [lon, lat] = layer.props.getPosition(d);
              if (lon != null && lat != null && isFinite(lon) && isFinite(lat)) {
                λ0 = Math.min(λ0, lon);
                φ0 = Math.min(φ0, lat);
                λ1 = Math.max(λ1, lon);
                φ1 = Math.max(φ1, lat);
              }
            });
          }
        }
      });

      // Check if any valid points were found
      if (!isFinite(λ0) || !isFinite(φ0) || !isFinite(λ1) || !isFinite(φ1)) {
        console.warn('No valid coordinates found for non-tile layers');
        return null;
      }

      // Apply margin
      const δλ = λ1 - λ0;
      const δφ = φ1 - φ0;
      const mλ = δλ * margin;
      const mφ = δφ * margin;

      return [[λ0 - mλ, φ0 - mφ], [λ1 + mλ, φ1 + mφ]];
    },

    // Returns the current second, as an integer.
    // Used for triggering Deck.gl URL refreshes
    currentSecond () {
      return Math.floor(Date.now()/1000);
    },

    async getSequenceData (sequenceNumbers, types = [2, 3]) {

      //const types = [2, 3]; // Bundle types: 2 → raw/gun data; 3 ‒ final data. See bundles.js

      if (!sequenceNumbers) {
        const sequenceList = await this.api([`/project/${this.$route.params.project}/sequence`]);
        sequenceNumbers = sequenceList?.map(i => i.sequence) ?? [];
      }
      const sequenceCount = sequenceNumbers.length * types.length;
      let count = 0;

      const self = this;
      const iterator = async function* () {

        const download = (url) => {

          const init = {
            headers: {
              //Accept: "application/vnd.aaltronav.dougal+octet-stream; format=1c"
              Accept: "application/vnd.aaltronav.dougal+octet-stream"
            }
          };

          return new Promise( async (resolve, reject) => {
            const cb = (err, res) => {
              if (res && !err) {
                const etag = res.headers.get("ETag");
                res.arrayBuffer().then(data => {
                  resolve({etag, data});
                })
              } else {
                reject(err);
              }
            };

            // Technically we do not need to await but this is
            // to slow down the cient and avoid firing too many
            // requests at once.
            self.api([url, init, cb, {cache: true, format: "arrayBuffer"}]);
          });

        }

        for (const sequence of sequenceNumbers) {
          for (const type of types) {
            try {
              const url = `/project/${self.$route.params.project}/sequence/${sequence}?type=${type}`;

              const sequenceData = await download(url);
              if (sequenceData) {
                yield {
                  id: `seq-${sequence}-${type}`,
                  sequence: sequence,
                  type: type,
                  data: sequenceData.data
                };
              }
            } catch (err) {
              console.error(`Error downloading sequence ${sequence}`);
              console.error(err);
            }
          }
        }
      };

      const transferData = () => {
        this.sequenceBinaryData = this.getSequenceBinaryData(2);
        this.sequenceBinaryDataFinal = this.getSequenceBinaryData(3);
      };

      for await (const value of iterator()) {
        this.loadingProgress = ++count/sequenceCount*100;
        //console.log("PRG", count, sequenceCount, this.loadingProgress, );
        this.sequenceDataElements.push(Object.freeze(value));
        if (this.loadingRefreshInterval > 0) {
          setTimeout( () => {
            transferData();
            this.render();
          }, this.loadingRefreshInterval );
        }
      }

      transferData();

      this.loadingProgress = null;
      console.log("passed for await", deck);
    },

    getSequenceBinaryData (type) {
      const sequences = this.sequenceDataElements?.filter( el => el.type == type)?.sort((a, b) => a.sequence - b.sequence) || [];
      if (!sequences.length) {
        console.warn('No sequence data available');
        return { positions: new Float32Array(0), values: [], udv: type };
      }

      // Validate first sequence to get array sizes
      let firstBundle;
      try {
        firstBundle = DougalBinaryBundle.clone(sequences[0].data);
        if (!firstBundle.chunks || typeof firstBundle.chunks !== 'function') {
          throw new Error('Invalid DougalBinaryBundle: chunks method missing');
        }
        if (!firstBundle.chunks().length) {
          throw new Error('Invalid DougalBinaryBundle: bundle has no chunks');
        }
      } catch (e) {
        console.error('Failed to process first sequence:', e);
        return { positions: new Float32Array(0), values: [], udv: type };
      }

      const totalCount = sequences.reduce((acc, { data }) => {
        try {
          const bundle = DougalBinaryBundle.clone(data);
          return acc + bundle.chunks().reduce((sum, chunk) => sum + chunk.jCount, 0);
        } catch (e) {
          console.warn('Skipping invalid sequence data:', e);
          return acc;
        }
      }, 0);

      if (totalCount === 0) {
        console.warn('No valid points found in sequences');
        return { positions: new Float32Array(0), values: [], udv: type };
      }

      const ΔelemCount = firstBundle.chunks()[0].ΔelemCount;
      const elemCount = firstBundle.chunks()[0].elemCount;
      const positions = new Float32Array(totalCount * 2);
      const values = new Array(ΔelemCount + elemCount + 2);
      for (let k = 0; k < values.length; k++) {
        values[k] = new (k === 0 ? Uint16Array : k === 1 ? Uint32Array : k === 2 ? BigUint64Array : Float32Array)(totalCount);
      }

      let offset = 0;
      let udv = 2;
      sequences.forEach(({ data, sequence }) => {
        try {
          const bundle = DougalBinaryBundle.clone(data);
          const chunks = bundle.chunks();
          if (!chunks.length) {
            console.warn(`No chunks in sequence ${sequence}`);
            return;
          }
          udv = chunks[0].udv;

          if (udv != type) {
            console.warn(`Found bundle with udv = ${udv} but I'm only expecting udv = ${type}`);
          }

          let chunkOffset = offset;
          for (const chunk of chunks) {
            const λarray = chunk.elem(0);
            const φarray = chunk.elem(1);
            for (let i = 0; i < λarray.length; i++) {
              positions[chunkOffset * 2 + i * 2] = λarray[i];
              positions[chunkOffset * 2 + i * 2 + 1] = φarray[i];
            }

            values[0].set(new Uint16Array(chunk.jCount).fill(chunk.i), chunkOffset);
            values[1].set(Uint32Array.from({ length: chunk.jCount }, (_, i) => chunk.j0 + i * chunk.Δj), chunkOffset);

            for (let j = 0; j < chunk.ΔelemCount; j++) {
              values[2 + j].set(chunk.Δelem(j), chunkOffset);
            }
            for (let j = 2; j < chunk.elemCount; j++) {
              values[2 + chunk.ΔelemCount + j - 2].set(chunk.elem(j), chunkOffset);
            }

            chunkOffset += chunk.jCount;
          }

          offset += chunkOffset - offset;
        } catch (e) {
          console.warn(`Error processing sequence ${sequence}:`, e);
        }
      });

      if (offset !== totalCount) {
        console.warn(`Offset mismatch: ${offset} ≠ ${totalCount}`);
      }

      console.log(`Concatenated ${totalCount} points, ${values.length} value arrays`);
      return { positions, values, udv };
    },

    async initLayers (gl) {
      //console.log("SHOULD BE INITIALISING LAYERS HERE", gl);
      this.decodeURL();
      this.decodeURLHash();
      deck.onViewStateChange = this.updateURL;
    },

    setViewState () {
      const viewport = deck.getViewports()[0];
      if (deck && viewport && this.viewState) {
        const initialViewState = {
          ...this.initialViewState,
          ...viewport,
          ...this.viewState
        };
        deck.setProps({initialViewState});
      }
    },

    updateURL ({viewState} = {}) {
      if (!viewState && deck?.viewManager) {
        viewState = deck.getViewports()[0];
      }

      if (viewState) {
        const key = {x: "longitude", y: "latitude", z: "zoom", p: "pitch", b: "bearing"};
        const view = Object.keys(key).map(k => [k, viewState[key[k]]]).filter(i => i[1]).map(i => i.join("")).join("");
        const layers = this.layerSelection.join(";")

        const value = [view, layers].join(":");

        if (view.length && layers.length) {
          localStorage.setItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/view/v1`, value);
        }

      }
    },

    decodeURL () {
      const value = localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/view/v1`);

      if (value) {
        this.decodeURLHash(value);
      }
    },

    decodeURLHash (hash) {
      if (!hash) {
        hash = document.location.hash.substring(1);
      }

      if (hash) {
        const [ view, layers, crosshairs, labels ] = hash.split(":");

        if (crosshairs) {
          this.crosshairsPositions = crosshairs
            .split(";")
            .map(p => p
              .split(",", 2)
              .filter( v => v.trim().length )
              .map( v => Number(v) )
              .filter( v => !isNaN(v) )
            )
            .filter( v => v.length == 2);

          if (this.crosshairsPositions.length) {
            if (!this.layerSelection.includes("crosshairs")) {
              this.layerSelection.push("crosshairs");
            }
          }

          if (labels) {
            this.crosshairsLabels = labels.split("\u001F").map( i => decodeURIComponent(i) );
            if (!this.layerSelection.includes("labels")) {
              this.layerSelection.push("labels");
            }
          }

        }

        if (layers) {
          const addLayers = [];
          const remLayers = [];
          const invLayers = [];
          const setLayers = [];

          layers
            .split(";")
            .map( l => l.match(/([!+-])?(.+)/).slice(1) )
            .forEach( ([ action, name ]) => {
              if (this.layersAvailable.hasOwnProperty(name)) {
                const groupKeys = {"+": addLayers, "-": remLayers, "!": invLayers};
                const group = groupKeys[action] ?? setLayers;
                group.push(name);
              }
            });

          if (!addLayers.length && !remLayers.length && !invLayers.length && setLayers.length) {
            // Set layers as per setLayers
            this.layerSelection = [...setLayers];
          } else {
            // Otherwise, ignore setLayers and use the current layerset
            const layerset = new Set(this.layerSelection);
            addLayers.forEach( layer => layerset.add(layer) );
            remLayers.forEach( layer => layerset.delete(layer) );
            invLayers.forEach( layer =>
              layerset.has(layer) ? layerset.delete(layer) : layerset.add(layer) );
            this.layerSelection = [...layerset];
          }
        }

        if (view) {
          const key = {x: "longitude", y: "latitude", z: "zoom", p: "pitch", b: "bearing"};
          const entries = view
            .split(/([xyzpb])/)
            .filter( v => v.length )
            .reduce( (acc, cur, idx) =>
                (idx % 2 ? acc[acc.length-1].push(cur) : acc.push([cur]), acc), []
            ).map( ([k, v]) => [ key[k], Number(v) ] )
            .filter( ([k, v]) => k && !isNaN(v) );

          this.viewState = Object.fromEntries(entries);

        }
      }
    },

    setHeatmapValue (v) {
      this.heatmapValue = v;
      console.log("Switched heatmap to", v);
      this.render();
    },

    checkWebGLSupport() {
      const canvas = document.createElement('canvas');
      const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
      if (!gl) {
        //this.error = 'WebGL is not supported in this browser or device.';
        //this.loading = false;
        console.error('WebGL not supported');
        return false;
      }
      const isWebGL2 = gl instanceof WebGL2RenderingContext;
      console.log('WebGL Support:', isWebGL2 ? 'WebGL2' : 'WebGL1');
      return true;
    },

    toBase64 (binary) {
      return btoa(String.fromCharCode(...new Uint8Array(binary)));
    },

    fromBase64 (text) {
      const decoded = atob(text);
      const arr = new Uint8Array(decoded.length);
      for (let i=0; i<decoded.length; i++) {
        arr[i] = decoded.charCodeAt(i);
      }
      return arr.buffer;
    },

    updateVesselIntervalTimer (refreshInterval) {
      this.vesselTrackRefreshInterval = refreshInterval ??
        this.vesselTrackPeriodSettings[this.vesselTrackPeriod]?.refreshInterval ?? 0;

      this.vesselTrackIntervalID = clearInterval(this.vesselTrackIntervalID);
      if (this.vesselTrackRefreshInterval) {
        this.vesselTrackLastRefresh = this.currentSecond();
        this.vesselTrackIntervalID = setInterval( () => {
          this.vesselTrackLastRefresh = this.currentSecond();
        }, this.vesselTrackRefreshInterval * 1000);
      }
    },

    async handleSequences (context, {payload}) {
      if (payload.pid != this.$route.params.project) {
        console.warn(`${this.$route.params.project} ignoring notification for ${payload.pid}`);
        return;
      }

      console.log("handleSequences (Map)", payload);

      if (payload.old?.sequence) {
        console.log(`Remove sequence ${payload.old.sequence} from data`);
        this.sequenceDataElements = this.sequenceDataElements.filter( el => el.sequence != payload.old.sequence );
        if (window.caches) {
          const cache = await caches.open("dougal");
          const rx = new RegExp(`/project/${this.$route.params.project}/sequence/${payload.old.sequence}(\\?.*)?$`);
          const keys = await cache.keys();
          for (const req of keys) {
            if (rx.test(req.url)) {
              console.log(`Removing ${req.url} from cache`);
              cache.delete(req);
            }
          }
        }

      }

      if (payload.new?.sequence) {
        console.log(`Add sequence ${payload.new.sequence} to data`);
        this.getSequenceData([payload.new.sequence]);
      }
    },

    handleVesselPosition (context, {payload}) {
      if (payload.new?.geometry?.coordinates) {
        const now = Date.now();
        const lastRefresh = this.vesselPosition?._lastRefresh;

        // Limits refreshes to once every five seconds max
        if (lastRefresh && (now-lastRefresh) < 5000) return;

        this.vesselPosition = {
          ...payload.new.meta,
          tstamp: payload.new.tstamp,
          _lastRefresh: now
        };
        if (this.vesselPosition.lineStatus == "offline") {
          this.vesselPosition.x = this.vesselPosition.longitude ?? payload.new.geometry.coordinates[0];
          this.vesselPosition.y = this.vesselPosition.latitude ?? payload.new.geometry.coordinates[1];
        } else {
          this.vesselPosition.x = this.vesselPosition.longitudeMaster
            ?? payload.new.geometry.coordinates[0];
          this.vesselPosition.y = this.vesselPosition.latitudeMaster
            ?? payload.new.geometry.coordinates[1];
        }
        this.render();
      }
    },

    registerNotificationHandlers (action = "registerHandler") {

      ["raw_lines", "raw_shots", "final_lines", "final_shots"].forEach( table => {
        this.$store.dispatch(action, {
          table,
          handler: this.handleSequences
        })
      });

      this.$store.dispatch(action, {
        table: 'realtime',
        handler: this.handleVesselPosition
      });

    },

    unregisterNotificationHandlers () {
      this.registerNotificationHandlers("unregisterHandler");
    },

    ...mapActions(["api"])

  },

  async mounted () {

    if (!this.checkWebGLSupport()) {
      for (const countdown = 5; countdown > 0; countdown--) {
        if (countdown) {
          this.error = `This browser does not support WebGL. Switching to legacy map view in ${countdown} seconds.`;
          await new Promise( (resolve) => setTimeout(() => resolve, 1000) );
        }
      }
      this.error = null;
      console.log("TODO: Should switch to legacy map view");
    }

    this.updateVesselIntervalTimer();

    this.layersAvailable.osm = this.osmLayer;

    this.layersAvailable.sea = this.openSeaMapLayer;

    this.layersAvailable.navp = this.vesselTrackPointsLayer;

    this.layersAvailable.navl = this.vesselTrackLinesLayer;

    this.layersAvailable.log = this.eventsLogLayer;

    this.layersAvailable.psll = this.preplotSaillinesLinesLayer;

    this.layersAvailable.ppll = this.preplotLinesLayer;

    this.layersAvailable.planl = this.plannedLinesLinesLayer;

    this.layersAvailable.seqrl = this.rawSequencesLinesLayer;

    this.layersAvailable.seqfl = this.finalSequencesLinesLayer;

    this.layersAvailable.pslp = this.preplotSaillinesPointLayer;

    this.layersAvailable.pplp = this.preplotPointsLayer;

    this.layersAvailable.seqrp = this.rawSequencesPointsLayer;

    this.layersAvailable.seqfp = this.finalSequencesPointsLayer;

    this.layersAvailable.seqrh = this.heatmapLayer;
    this.layersAvailable.crosshairs = (options = {}) => {
      return new IconLayer({
        id: 'crosshairs',
        data: this.crosshairsPositions,
        getPosition: d => d,
        getSize: 32,
        getColor: [ 255, 0, 0 ],
        getIcon: (d) => {
          return {
            url: hashMarkerIconURL,
            width: 24,
            height: 24
          };
        },
        ...options
      });
    };

    this.layersAvailable.labels = (options = {}) => {
      return new TextLayer({
        id: 'labels',
        data: this.crosshairsPositions,
        getPosition: d => d,
        getSize: 18,
        getColor: [ 255, 0, 0 ],
        getText: (d, {index}) => this.crosshairsLabels[index],
        getPixelOffset: [ 0, -24 ],
        getTextAnchor: "start",
        getAlignmentBase: "bottom",
        ...options
      });
    };

    deck = new Deck({
      parent: document.getElementById("map"),
      mapStyle: 'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json',
      initialViewState: {
        ...this.viewStateDefaults,
        latitude: 61.5,
        longitude: 2.5,
        zoom: 7,
        pitch: 0,
      },
      controller: {inertia: true},
      layers: [],
      getTooltip: this.getTooltip,
      pickingRadius: 24,
      onWebGLInitialized: this.initLayers
    });

    // Get fullscreen state

    document.addEventListener('fullscreenchange', () => {
      this.isFullscreen = !!document.fullscreenElement;
    });
    document.addEventListener('webkitfullscreenchange', () => {
      this.isFullscreen = !!document.fullscreenElement;
    });
    document.addEventListener('msfullscreenchange', () => {
      this.isFullscreen = !!document.fullscreenElement;
    });

    this.registerNotificationHandlers();

    //this.layerSelection = [ "seq" ];
    this.getSequenceData();
  },

  beforeDestroy () {
    this.unregisterNotificationHandlers();
    this.vesselTrackIntervalID = this.clearInterval(this.vesselTrackIntervalID);
  }

}

</script>
