/**
 * Copyright 2019 - 2021 Jia Yu (jiayu@apache.org)
 * Copyright 2017 Volume Integration
 * Copyright 2017 Tom Grant
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
import Visualization from 'zeppelin-vis';
import ColumnselectorTransformation from 'zeppelin-tabledata/columnselector';

import L from 'leaflet/dist/leaflet';
import 'leaflet/dist/leaflet.css';
// workaround https://github.com/Leaflet/Leaflet/issues/4968
import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

let DefaultIcon = L.icon({
  iconUrl: icon,
  iconSize: [25, 41],
  iconAnchor: [12, 41],
  popupAnchor: [0, -41],
  tooltipAnchor: [12, -28],
  shadowUrl: iconShadow,
});
L.Marker.prototype.options.icon = DefaultIcon;

export default class LeafletMap extends Visualization {
  constructor(targetEl, config) {
    super(targetEl, config);

    const columnSpec = [{name: 'mapimage'}, {name: 'geometry'}, {name: 'info'}];

    this.transformation = new ColumnselectorTransformation(config, columnSpec);
    this.chartInstance = this.createMap();
  }

  getTransformation() {
    return this.transformation;
  }

  showChart() {
    super.setConfig(config);
    this.transformation.setConfig(config);
    if (!this.chartInstance) {
      this.chartInstance = this.createMap();
    }
    return this.chartInstance;
  }

  createMap() {
    // using canvas renderer to support drawing geometry like LineString, Polygon etc.
    return L.map(this.getChartElementId(), {renderer: L.canvas()});
  }

  getChartElementId() {
    return this.targetEl[0].id;
  }

  getChartElement() {
    return document.getElementById(this.getChartElementId());
  }

  clearChart() {
    if (this.chartInstance) {
      this.chartInstance.off();
      this.chartInstance.remove();
      this.chartInstance = null;
    }
  }

  showError(error) {
    this.clearChart();
    this.getChartElement().innerHTML = `
        <div style="margin-top: 60px; text-align: center; font-weight: 100">
            <span style="font-size:30px;">
                ${error.message}
            </span>
        </div>`;
  }

  drawMapChart(chartDataModel) {
    const map = this.showChart();
    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
      attribution:
        '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
    }).addTo(map);

    this.getChartElement().style.height = this.targetEl.height();
    map.invalidateSize(true);

    var imageBounds = null;
    const markers = chartDataModel.rows.flatMap((row) => {
      const {image, boundary, info} = row;
      var markers = [];

      // throw new Error(image);
      var jsts = require('jsts');
      // Read WKT string from Sedona
      var reader = new jsts.io.WKTReader();
      var obj = reader.read(boundary);
      // Collect the centroid point of the input geometry
      var centroid = obj.getCentroid();
      var marker = L.marker([centroid.getY(), centroid.getX()]);
      const mapMarker = marker.addTo(map);
      markers.push(marker);

      if (obj.getGeometryType() !== 'Point') {
        var writer = new jsts.io.GeoJSONWriter();
        var geojson = writer.write(obj);
        marker = L.geoJSON(geojson);
        marker.addTo(map);
        markers.push(marker);
      }

      // Attach the marker information if exists
      if (info) {
        mapMarker.bindTooltip(info);
      }
      // Overlay the generated image over the tile layer
      if (image) {
        var envelope = obj.getEnvelopeInternal();
        // Read the boundary of the map viewport
        imageBounds = [
          [envelope.getMinY(), envelope.getMinX()],
          [envelope.getMaxY(), envelope.getMaxX()],
        ];
        var imageUrl = 'data:image/png;base64,' + image;
        L.imageOverlay(imageUrl, imageBounds).addTo(map);
      }
      return markers;
    });
    // Adjust the location of the viewport
    // If imageBounds was initialized, use imageBounds instead
    var bounds = null;
    if (imageBounds) {
      bounds = imageBounds;
    } else {
      let featureGroup = L.featureGroup(markers);
      bounds = featureGroup.getBounds().pad(0.5);
    }
    // throw new Error(featureGroup.getBounds().pad(0.5).toString())
    map.fitBounds(bounds);
  }

  createMapDataModel(data) {
    const getColumnIndex = (config, fieldName, isOptional) => {
      const fieldConf = config[fieldName];
      if (fieldConf instanceof Object) {
        return fieldConf.index;
      } else if (isOptional) {
        return -1;
      } else {
        throw {
          message: 'Please set ' + fieldName + ' in Settings',
        };
      }
    };

    const config = this.getTransformation().config;
    const imageIdx = getColumnIndex(config, 'mapimage', true);
    const boundaryIdx = getColumnIndex(config, 'geometry');
    const infoIdx = getColumnIndex(config, 'info', true);
    const rows = data.rows.map((tableRow) => {
      const image = imageIdx < 0 ? null : tableRow[imageIdx];
      const boundary = tableRow[boundaryIdx];
      const info = infoIdx < 0 ? null : tableRow[infoIdx];
      return {
        image,
        boundary,
        info,
      };
    });

    return {
      rows,
    };
  }

  render(data) {
    try {
      const mapDataModel = this.createMapDataModel(data);
      this.clearChart();
      this.drawMapChart(mapDataModel);
    } catch (error) {
      console.error(error);
      this.showError(error);
    }
  }
}
