<template>
  <div class="map--container" :style="{height: height, width: width}">
    <div :id="`map_${_uid}`" class="map" :style="{height: height, width: width}"></div>
    <button
      class="map-save-button"
      :disabled="disabled"
      @click="save"
      v-show="allow_edit && !hide_save"
      type="button"
    >{{ translate('save') }}</button>
    <button type="button" class="map-toggle-button" @click="toggleLayer">
      {{ currentLayer === 'standard' ? translate('toggle') : translate('map') }}
    </button>
  </div>
</template>

<script>
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import { Icon } from "leaflet";

delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
  iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
  iconUrl: require("leaflet/dist/images/marker-icon.png"),
  shadowUrl: require("leaflet/dist/images/marker-shadow.png"),
});
// Warn if overriding existing method
if (Array.prototype.equals) {
  /*
  console.warn(
    "Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code."
  );
  */
}

// attach the .equals method to Array's prototype to call it on any array
Array.prototype.equals = function (array) {
  // if the other array is a falsy value, return
  if (!array) return false;

  // compare lengths - can save a lot of time
  if (this.length != array.length) return false;

  for (var i = 0, l = this.length; i < l; i++) {
    // Check if we have nested arrays
    if (this[i] instanceof Array && array[i] instanceof Array) {
      // recurse into the nested arrays
      if (!this[i].equals(array[i])) return false;
    } else if (this[i] != array[i]) {
      // Warning - two different object instances will never be equal: {x:20} != {x:20}
      return false;
    }
  }
  return true;
};
// Hide method from for-in loops
Object.defineProperty(Array.prototype, "equals", { enumerable: false });
export default {
  data() {
    return {
      localValue: [],
      markerIcon: new Icon.Default(),
      markers: [],
      object: null,
      map: null,
      currentLayer: 'standard',
      layers: {
        standard: {
          url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
          attribution: '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
        },
        satellite: {
          url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
          attribution: "Tiles © Esri — Sources: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
        }
      },
    };
  },
  props: {
    height: {
      type: String,
      default: "350px",
    },
    width: {
      type: String,
      default: "auto",
    },
    mode: {
      type: String,
      default: "point",
    },
    allow_edit: {
      type: Boolean,
      default: false,
    },
    color: {
      type: String,
      default: "blue",
    },
    fill_color: {
      type: String,
      default: function () {
        return this.color;
      },
    },
    value: {
      type: Array,
      default: function () {
        return [];
      },
    },
    marker_options: Object,
    hide_save: {
      type: Boolean,
      default: false,
    },
    trigger_save_on_change: {
      type: Boolean,
      deafult: false,
    },
    lang: {
      type: String,
      default: 'en',
    },
  },
  watch: {
    marker_options: function (newVal) {
      this.markerIcon = L.icon(newVal);
    },
    compProps: function () {
      const vm = this;
      vm.map.off("dblclick");
      this.markers.forEach((marker) => {
        vm.map.removeLayer(marker);
      });
      vm.markers = [];
      if (vm.object) {
        vm.map.removeLayer(vm.object);
        vm.object = null;
      }
      this.setupMap();
    },
  },

  computed: {
    compProps: function () {
      const vm = this;
      return [vm.mode, vm.color, vm.fill_color, vm.allow_edit, JSON.stringify(vm.value)].join();
    },
    disabled: function () {
      return this.value.equals(this.markersCoordinates);
    },
    markersCoordinates: function () {
      return this.markers.map((marker) => {
        if (marker._latlng) {
          if (marker.iconDef) {
            return [marker._latlng.lat, marker._latlng.lng, marker.iconDef];
          }
          return [marker._latlng.lat, marker._latlng.lng];
        }
        return [];
      });
    },
  },

  mounted: function () {
    const vm = this;
    if (vm.marker_options) {
      vm.markerIcon = L.icon(vm.marker_options);
    }
    vm.map = L.map(`map_${vm._uid}`);
    vm.map.doubleClickZoom.disable();
    vm.addLayer(vm.currentLayer);
    vm.setupMap();
  },
  methods: {
    translate(key) {
      const translations = {
        en: {
          save: "Save",
          toggle: "Satellite",
          map: "Map",
        },
        ro: {
          save: "Salvează",
          toggle: "Satelit",
          map: "Hartă",
        }
      };
      return translations[this.lang][key];
    },
    addLayer(layerKey) {
      const layerInfo = this.layers[layerKey];
      L.tileLayer(layerInfo.url, {
        attribution: layerInfo.attribution,
      }).addTo(this.map);
    },
    toggleLayer() {
      this.map.eachLayer((layer) => {
        if (layer instanceof L.TileLayer) {
          this.map.removeLayer(layer);
        }
      });
      this.currentLayer = this.currentLayer === 'standard' ? 'satellite' : 'standard';
      this.addLayer(this.currentLayer);
    },
    validCoordinates(coord) {
      return (
        coord &&
        Array.isArray(coord) &&
        coord[0] >= -90 &&
        coord[0] <= 90 &&
        coord[1] >= -180 &&
        coord[1] <= 180
      );
    },

    validCoordinatesArray(coordArray) {
      if (!coordArray || !Array.isArray(coordArray)) {
        return false;
      }
      for (let i = 0; i < coordArray.length; i++) {
        if (!this.validCoordinates(coordArray[i])) {
          return false;
        }
      }
      return true;
    },

    setupMap: function () {
      const vm = this;
      vm.markers = [];
      if (vm.validCoordinates(vm.value[0])) {
        vm.map.setView(vm.value[0], 13);
      }
      switch (vm.mode) {
        case "point":
          vm.point();
          break;
        case "polyline":
          vm.line();
          break;
        case "polygon":
          vm.polygon();
          break;
        default:
          // console.error("Invalid mode provided!");
          break;
      }
    },
    save: function () {
      const vm = this;
      vm.$emit("input", vm.markersCoordinates);
    },
    point: function () {
      const vm = this;
      let marker;
      const markerHelper = (marker) => {
        marker.on("dblclick", function (e) {
          L.DomEvent.stopPropagation(e);
          vm.markers = [];
          vm.map.removeLayer(marker);
          if (vm.trigger_save_on_change) {
            vm.save();
          }
        });
      };
      if (vm.value.length && vm.validCoordinates(vm.value[0])) {
        marker = L.marker(vm.value[0], {
          icon:
            vm.value[0].length > 2 ? new L.icon(vm.value[0][2]) : vm.markerIcon,
          draggable: vm.allow_edit ? true : false,
        });
        vm.map.addLayer(marker);
        if (vm.value[0][2]) {
          marker.iconDef = vm.value[0][2];
        }
        vm.markers.push(marker);
        if (vm.allow_edit) {
          markerHelper(vm.markers[0]);
        }
      }
      if (vm.allow_edit) {
        vm.map.on("dblclick", function (e) {
          const coord = [e.latlng.lat, e.latlng.lng];
          marker = L.marker(coord, {
            icon:
              vm.value[0].length > 2
                ? new L.icon(vm.value[0][2])
                : vm.markerIcon,
            draggable: true,
          });
          vm.map.addLayer(marker);
          if (vm.value[0][2]) {
            marker.iconDef = vm.value[0][2];
          }
          if (vm.markers.length) {
            vm.map.removeLayer(vm.markers[0]);
          }
          vm.markers = [marker];
          markerHelper(vm.markers[0]);
          if (vm.trigger_save_on_change) {
            vm.save();
          }
        });
      }
    },
    line: function () {
      const vm = this;
      vm.helper("line");
    },
    polygon: function () {
      const vm = this;
      vm.helper("polygon");
    },
    helper: function (objectType) {
      const vm = this;
      let marker;
      const markerHelper = (marker) => {
        marker.on("dblclick", function (e) {
          L.DomEvent.stopPropagation(e);
          vm.map.removeLayer(marker);
          const newMarkes = vm.markers.filter(
            (item) =>
              item._latlng.lat !== marker._latlng.lat ||
              item._latlng.lng !== marker._latlng.lng
          );
          vm.markers = newMarkes;
          vm.map.removeLayer(vm.object);
          if (objectType === "line") {
            vm.object = L.polyline(vm.markersCoordinates, {
              color: vm.color,
              fillColor: vm.fill_color,
            });
          } else if (objectType === "polygon") {
            vm.object = L.polygon(vm.markersCoordinates, {
              color: vm.color,
              fillColor: vm.fill_color,
            });
          }
          vm.map.addLayer(vm.object);
          if (vm.trigger_save_on_change) {
            vm.save();
          }
        });

        marker.on("drag", function (e) {
          vm.map.removeLayer(vm.object);
          if (objectType === "line") {
            vm.object = L.polyline(vm.markersCoordinates, {
              color: vm.color,
              fillColor: vm.fill_color,
            });
          } else if (objectType === "polygon") {
            vm.object = L.polygon(vm.markersCoordinates, {
              color: vm.color,
              fillColor: vm.fill_color,
            });
          }
          vm.map.addLayer(vm.object);
        });

        marker.on("dragend", function (e) {
          if (vm.trigger_save_on_change) {
            vm.save();
          }
        });
      };
      if (vm.value.length && vm.validCoordinatesArray(vm.value)) {
        if (objectType === "line") {
          vm.object = L.polyline(vm.value, {
            color: vm.color,
            fillColor: vm.fill_color,
          });
        } else if (objectType === "polygon") {
          vm.object = L.polygon(vm.value, {
            color: vm.color,
            fillColor: vm.fill_color,
          });
        }
        vm.map.addLayer(vm.object);
        vm.map.fitBounds(vm.object.getBounds());
        if (vm.allow_edit) {
          vm.map.on("dblclick", function (e) {
            const coord = [e.latlng.lat, e.latlng.lng];
            marker = L.marker(coord, {
              icon: vm.markerIcon,
              draggable: true,
            });
            markerHelper(marker);
            vm.map.addLayer(marker);
            vm.markers.push(marker);
            vm.map.removeLayer(vm.object);
            if (objectType === "line") {
              vm.object = L.polyline(vm.markersCoordinates, {
                color: vm.color,
                fillColor: vm.fill_color,
              });
            } else if (objectType === "polygon") {
              vm.object = L.polygon(vm.markersCoordinates, {
                color: vm.color,
                fillColor: vm.fill_color,
              });
            }
            vm.map.addLayer(vm.object);
            if (vm.trigger_save_on_change) {
              vm.save();
            }
          });
          vm.value.forEach((coord) => {
            marker = L.marker(coord, {
              icon: coord.length > 2 ? new L.icon(coord[2]) : vm.markerIcon,
              draggable: true,
            });
            vm.map.addLayer(marker);
            if (coord[2]) {
              marker.iconDef = coord[2];
            }
            vm.markers.push(marker);
            markerHelper(marker);
          });
        }
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.map--container {
  position: relative;
}
.map {
  height: 350px;
}
.map-save-button {
  position: absolute;
  z-index: 999;
  right: 20px;
  bottom: 20px;
  font-size: 1rem;
  padding: 0 1rem;
  border: 1px solid #a7a7a7;
  background: white;
  border-radius: 3px;
  &:active {
    background: #efefef;
  }
  &:focus {
    outline: none;
  }
}
.map-toggle-button {
  position: absolute;
  z-index: 999;
  right: 20px;
  top: 20px;
  font-size: 1rem;
  padding: 0 1rem;
  border: 1px solid #a7a7a7;
  background: white;
  border-radius: 3px;
  &:hover {
    background: #efefef;
  }
  &:focus {
    outline: none;
  }
}

</style>
