/* Copyright 2015 Bloomberg Finance L.P.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as widgets from '@jupyter-widgets/base';
import * as d3 from 'd3';
// var d3 =Object.assign({}, require("d3-selection"), require("d3-zoom"));
import { d3GetEvent } from './utils';
import { Mark } from './Mark';
import { MapModel } from './MapModel';

export class Map extends Mark {
  render() {
    const base_render_promise = super.render();

    this.map = this.d3el.append('svg').attr('viewBox', '0 0 1200 980');
    this.width = this.parent.plotareaWidth;
    this.height = this.parent.plotareaHeight;
    this.map_id = widgets.uuid();
    this.enable_hover = this.model.get('enable_hover');
    this.display_el_classes = ['event_layer'];
    this.displayed.then(() => {
      this.parent.tooltip_div.node().appendChild(this.tooltip_div.node());
      this.create_tooltip();
    });

    return base_render_promise.then(() => {
      this.event_listeners = {};
      this.process_interactions();
      this.create_listeners();
      this.draw();
    });
  }

  set_ranges() {}

  relayout() {}

  set_positional_scales() {
    const geo_scale = this.scales.projection;
    this.listenTo(geo_scale, 'domain_changed', () => {
      if (!this.model.dirty) {
        this.draw();
      }
    });
  }

  initialize_additional_scales() {
    const color_scale = this.scales.color;
    if (color_scale) {
      this.listenTo(color_scale, 'domain_changed', () => {
        this.update_style();
      });
      color_scale.on('color_scale_range_changed', this.update_style, this);
    }
  }

  remove_map() {
    d3.selectAll('.world_map.map' + this.map_id).remove();
  }

  draw() {
    this.set_ranges();
    this.remove_map();
    this.transformed_g = this.map
      .append('g')
      .attr('class', 'world_map map' + this.map_id);
    this.fill_g = this.transformed_g.append('g');
    this.highlight_g = this.transformed_g.append('g');
    this.stroke_g = this.transformed_g.append('g');
    const projection = this.scales.projection;
    //Bind data and create one path per GeoJSON feature
    this.fill_g
      .selectAll('path')
      .data(this.model.geodata)
      .enter()
      .append('path')
      .attr('d', projection.path)
      .style('fill', (d, i) => {
        return this.fill_g_colorfill(d, i);
      });
    this.stroke_g
      .selectAll('path')
      .data(this.model.geodata)
      .enter()
      .append('path')
      .attr('class', 'event_layer')
      .attr('d', projection.path)
      .style('fill-opacity', 0.0)
      .on('click', (d, i) => {
        return this.event_dispatcher('element_clicked', { data: d, index: i });
      });
    if (this.validate_color(this.model.get('stroke_color'))) {
      this.stroke_g
        .selectAll('path')
        .style('stroke', this.model.get('stroke_color'));
    }
    this.zoom = d3
      .zoom()
      .scaleExtent([1, 8])
      .on('zoom', () => {
        this.zoomed(this);
      });
    this.parent.bg_events.call(this.zoom);

    this.parent.bg_events.on('dblclick.zoom', null);
    this.parent.bg_events.on('dblclick', () => {
      this.reset_zoom(this);
    });
  }

  validate_color(color) {
    return color !== '';
  }

  mouseover_handler() {
    if (!this.model.get('hover_highlight')) {
      return;
    }
    const el = d3.select(d3GetEvent().target);
    if (this.is_hover_element(el)) {
      const data: any = el.data()[0];
      const idx = this.model.get('selected') || [];
      const select = Array.from(idx);
      const node = this.highlight_g.append(() => {
        return el.node().cloneNode(true);
      });
      node.classed('hovered', true);
      node.classed('event_layer', false);

      if (
        this.validate_color(this.model.get('hovered_styles').hovered_stroke) &&
        select.indexOf(data.id) === -1
      ) {
        node
          .style('stroke', this.model.get('hovered_styles').hovered_stroke)
          .style(
            'stroke-width',
            this.model.get('hovered_styles').hovered_stroke_width
          );
      }
      if (
        this.validate_color(this.model.get('hovered_styles').hovered_fill) &&
        select.indexOf(data.id) === -1
      ) {
        node.style('fill-opacity', 1.0).style('fill', () => {
          return this.model.get('hovered_styles').hovered_fill;
        });
      }
    }
  }

  mouseout_handler() {
    if (!this.model.get('hover_highlight')) {
      return;
    }
    const el = d3.select(d3GetEvent().target);
    if (this.is_hover_element(el)) {
      el.transition('mouseout_handler')
        .style('fill', (d, i) => {
          return this.fill_g_colorfill(d, i);
        })
        .style('stroke', (d, i) => {
          return this.hoverfill(d, i);
        });
      this.highlight_g.selectAll('.hovered').remove();
    }
  }

  click_handler() {
    const el = d3.select(d3GetEvent().target);
    if (this.is_hover_element(el)) {
      const data: any = el.data()[0];
      const idx = this.model.get('selected') || [];
      const selected = Array.from(idx);
      const elem_index = selected.indexOf(data.id);
      if (elem_index > -1) {
        selected.splice(elem_index, 1);
        el.transition('click_handler').style('fill-opacity', 0.0);
        this.highlight_g.selectAll('.hovered').remove();
        const choice = '#c'.concat(data.id.toString());
        d3.select(choice).remove();
      } else {
        this.highlight_g.selectAll('.hovered').remove();
        this.highlight_g
          .append(() => {
            return el.node().cloneNode(true);
          })
          .attr('id', 'c' + data.id)
          .classed('selected', true)
          .classed('event_layer', false);

        if (
          this.validate_color(this.model.get('selected_styles').selected_fill)
        ) {
          this.highlight_g
            .selectAll('.selected')
            .style('fill-opacity', 1.0)
            .style('fill', this.model.get('selected_styles').selected_fill);
        }

        if (
          this.validate_color(this.model.get('selected_styles').selected_stroke)
        ) {
          this.highlight_g
            .selectAll('.selected')
            .style('stroke', this.model.get('selected_styles').selected_stroke)
            .style(
              'stroke-width',
              this.model.get('selected_styles').selected_stroke_width
            );
        }
        selected.push(data.id);
        this.model.set('selected', selected);
        this.touch();
      }
      this.model.set('selected', selected.length === 0 ? null : selected, {
        updated_view: this,
      });
      this.touch();
    }
  }

  reset_zoom(that) {
    that.zoom.transform(that.parent.bg, d3.zoomIdentity);
  }

  zoomed(that) {
    const tr = d3GetEvent().transform;
    const h = that.height / 3;
    const w = 2 * that.width;
    tr.x = Math.min(
      (that.width / 2) * (tr.k - 1),
      Math.max((w / 2) * (1 - tr.k), tr.x)
    );
    tr.y = Math.min(
      (that.height / 2) * (tr.k - 1) + this.height * tr.k,
      Math.max((h / 2) * (1 - tr.k) - that.width * tr.k, tr.y)
    );
    that.transformed_g.style('stroke-width', 1 / tr.k).attr('transform', tr);
  }

  create_listeners() {
    this.d3el
      .on('mouseover', () => {
        this.event_dispatcher('mouse_over');
      })
      .on('mousemove', () => {
        this.event_dispatcher('mouse_move');
      })
      .on('mouseout', () => {
        this.event_dispatcher('mouse_out');
      });

    this.listenTo(this.model, 'data_updated', this.draw);
    this.listenTo(this.model, 'change:color', this.update_style);
    this.listenTo(this.model, 'change:stroke_color', this.change_stroke_color);
    this.listenTo(this.model, 'change:colors', this.change_map_color);
    this.listenTo(this.model, 'change:selected', this.change_selected);
    this.listenTo(this.model, 'change:selected_styles', () => {
      this.change_selected_fill();
      this.change_selected_stroke();
    });
    this.listenTo(this.model, 'change:interactions', this.process_interactions);
    this.listenTo(this.parent, 'bg_clicked', () => {
      this.event_dispatcher('parent_clicked');
    });
  }

  process_click(interaction) {
    super.process_click(interaction);
    if (interaction === 'select') {
      this.event_listeners.parent_clicked = this.reset_selection;
      this.event_listeners.element_clicked = this.click_handler;
    }
  }

  process_hover(interaction) {
    super.process_hover(interaction);
    if (interaction === 'tooltip') {
      this.event_listeners.mouse_over = () => {
        this.mouseover_handler();
        return this.refresh_tooltip();
      };
      this.event_listeners.mouse_move = this.move_tooltip;
      this.event_listeners.mouse_out = () => {
        this.mouseout_handler();
        return this.hide_tooltip();
      };
    }
  }

  change_selected_fill() {
    if (!this.validate_color(this.model.get('selected_styles').selected_fill)) {
      this.highlight_g.selectAll('.selected').style('fill-opacity', 0.0);
    } else {
      this.highlight_g
        .selectAll('.selected')
        .style('fill-opacity', 1.0)
        .style('fill', this.model.get('selected_styles').selected_fill);
    }
  }

  change_selected_stroke() {
    if (
      !this.validate_color(this.model.get('selected_styles').selected_stroke)
    ) {
      this.highlight_g.selectAll('.selected').style('stroke-width', 0.0);
    } else {
      this.highlight_g
        .selectAll('.selected')
        .style(
          'stroke-width',
          this.model.get('selected_styles').selected_stroke_width
        )
        .style('stroke', this.model.get('selected_styles').selected_stroke);
    }
  }

  change_selected() {
    this.highlight_g.selectAll('path').remove();
    const idx = this.model.get('selected');
    const select = idx ? idx : [];
    const temp = this.stroke_g.selectAll('path').data();
    this.stroke_g.selectAll('path').style('stroke', (d, i) => {
      return this.hoverfill(d, i);
    });
    const nodes = this.stroke_g.selectAll('path');
    for (let i = 0; i < temp.length; i++) {
      if (select.indexOf((temp[i] as any).id) > -1) {
        this.highlight_g
          .append(() => {
            return (nodes.nodes()[i] as any).cloneNode(true);
          })
          .attr('id', (temp[i] as any).id)
          .style('fill-opacity', () => {
            if (
              this.validate_color(
                this.model.get('selected_styles').selected_fill
              )
            ) {
              return 1.0;
            } else {
              return 0.0;
            }
          })
          .style('fill', this.model.get('selected_styles').selected_fill)
          .style('stroke-opacity', () => {
            if (
              this.validate_color(
                this.model.get('selected_styles').selected_stroke
              )
            ) {
              return 1.0;
            } else {
              return 0.0;
            }
          })
          .style('stroke', this.model.get('selected_styles').selected_stroke)
          .style(
            'stroke-width',
            this.model.get('selected_styles').selected_stroke_width
          )
          .classed('selected', true);
      }
    }
  }

  reset_selection() {
    this.model.set('selected', []);
    this.touch();
    this.highlight_g.selectAll('.selected').remove();
    d3.select(this.d3el.node().parentNode as Element)
      .selectAll('path')
      .classed('selected', false);
    d3.select(this.d3el.node().parentNode as Element)
      .selectAll('path')
      .classed('hovered', false);

    this.stroke_g.selectAll('path').style('stroke', (d, i) => {
      return this.hoverfill(d, i);
    });
    this.fill_g
      .selectAll('path')
      .classed('selected', false)
      .style('fill', (d, i) => {
        return this.fill_g_colorfill(d, i);
      });
  }

  change_stroke_color() {
    this.stroke_g
      .selectAll('path')
      .style('stroke', this.model.get('stroke_color'));
  }

  change_map_color() {
    if (!this.is_object_empty(this.model.get('color'))) {
      return;
    }
    this.fill_g.selectAll('path').style('fill', (d, i) => {
      return this.fill_g_colorfill(d, i);
    });
  }

  update_style() {
    const color_data = this.model.get('color');
    if (!this.is_object_empty(color_data)) {
      this.fill_g.selectAll('path').style('fill', (d, i) => {
        return this.fill_g_colorfill(d, i);
      });
    }
  }

  is_object_empty(object: any) {
    return Object.keys(object).length === 0 && object.constructor === Object;
  }

  hoverfill(d, j) {
    const idx = this.model.get('selected');
    const select = idx ? idx : [];
    if (
      select.indexOf(d.id) > -1 &&
      this.validate_color(this.model.get('selected_styles').selected_stroke)
    ) {
      return this.model.get('selected_styles').selected_stroke;
    } else {
      return this.model.get('stroke_color');
    }
  }

  fill_g_colorfill(d, j) {
    const color_scale = this.scales.color;
    const idx = this.model.get('selected');
    const selection = idx ? idx : [];
    const color_data = this.model.get('color');
    const colors = this.model.get('colors');

    if (selection.indexOf(d.id) > -1) {
      return this.model.get('selected_styles').selected_fill;
    } else if (this.is_object_empty(color_data)) {
      return colors[d.id] || colors.default_color;
    } else if (
      color_data[d.id] === undefined ||
      color_data[d.id] === null ||
      color_scale === undefined
    ) {
      return colors.default_color;
    } else {
      return color_scale.scale(color_data[d.id]);
    }
  }

  clear_style() {}
  compute_view_padding() {}
  set_default_style() {}
  set_style_on_elements() {}

  map: d3.Selection<SVGSVGElement, any, any, any>;
  map_id: string;
  width: number;
  height: number;
  enable_hover: boolean;
  stroke_g: d3.Selection<SVGGElement, any, any, any>;
  fill_g: d3.Selection<SVGGElement, any, any, any>;
  highlight_g: d3.Selection<SVGGElement, any, any, any>;
  transformed_g: d3.Selection<SVGGElement, any, any, any>;
  zoom: d3.ZoomBehavior<Element, any>;
  model: MapModel;
}
