var $, ImagePool, Renderer, TileRenderer, TileRendererView, _, logger, p, wmts,
  bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  hasProp = {}.hasOwnProperty,
  indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

_ = require("underscore");

$ = require("jquery");

ImagePool = require("./image_pool");

wmts = require("./wmts_tile_source");

Renderer = require("../renderers/renderer");

logger = require("../../core/logging").logger;

p = require("../../core/properties");

TileRendererView = (function(superClass) {
  extend(TileRendererView, superClass);

  function TileRendererView() {
    this._update = bind(this._update, this);
    this._prefetch_tiles = bind(this._prefetch_tiles, this);
    this._on_tile_error = bind(this._on_tile_error, this);
    this._on_tile_cache_load = bind(this._on_tile_cache_load, this);
    this._on_tile_load = bind(this._on_tile_load, this);
    this._add_attribution = bind(this._add_attribution, this);
    return TileRendererView.__super__.constructor.apply(this, arguments);
  }

  TileRendererView.prototype.initialize = function(options) {
    this.attributionEl = null;
    return TileRendererView.__super__.initialize.apply(this, arguments);
  };

  TileRendererView.prototype.bind_bokeh_events = function() {
    return this.listenTo(this.model, 'change', this.request_render);
  };

  TileRendererView.prototype.get_extent = function() {
    return [this.x_range.get('start'), this.y_range.get('start'), this.x_range.get('end'), this.y_range.get('end')];
  };

  TileRendererView.prototype._set_data = function() {
    this.pool = new ImagePool();
    this.map_plot = this.plot_model.plot;
    this.map_canvas = this.plot_view.canvas_view.ctx;
    this.map_frame = this.plot_model.frame;
    this.x_range = this.map_plot.get('x_range');
    this.x_mapper = this.map_frame.get('x_mappers')['default'];
    this.y_range = this.map_plot.get('y_range');
    this.y_mapper = this.map_frame.get('y_mappers')['default'];
    this.extent = this.get_extent();
    this._last_height = void 0;
    return this._last_width = void 0;
  };

  TileRendererView.prototype._add_attribution = function() {
    var attribution, border_width, bottom_offset, max_width, overlays, right_offset;
    attribution = this.mget('tile_source').get('attribution');
    if (_.isString(attribution) && attribution.length > 0) {
      if (this.attributionEl != null) {
        return this.attributionEl.html(attribution);
      } else {
        border_width = this.map_plot.get('outline_line_width');
        bottom_offset = this.map_plot.get('min_border_bottom') + border_width;
        right_offset = this.map_frame.get('right') - this.map_frame.get('width');
        max_width = this.map_frame.get('width') - border_width;
        this.attributionEl = $('<div>').html(attribution).addClass('bk-tile-attribution').css({
          'position': 'absolute',
          'bottom': bottom_offset + "px",
          'right': right_offset + "px",
          'max-width': max_width + "px",
          'background-color': 'rgba(255,255,255,0.8)',
          'font-size': '9pt',
          'font-family': 'sans-serif'
        });
        overlays = this.plot_view.$el.find('div.bk-canvas-events');
        return this.attributionEl.appendTo(overlays);
      }
    }
  };

  TileRendererView.prototype._map_data = function() {
    var new_extent, zoom_level;
    this.initial_extent = this.get_extent();
    zoom_level = this.mget('tile_source').get_level_by_extent(this.initial_extent, this.map_frame.get('height'), this.map_frame.get('width'));
    new_extent = this.mget('tile_source').snap_to_zoom(this.initial_extent, this.map_frame.get('height'), this.map_frame.get('width'), zoom_level);
    this.x_range.set('start', new_extent[0]);
    this.y_range.set('start', new_extent[1]);
    this.x_range.set('end', new_extent[2]);
    this.y_range.set('end', new_extent[3]);
    return this._add_attribution();
  };

  TileRendererView.prototype._on_tile_load = function(e) {
    var tile_data;
    tile_data = e.target.tile_data;
    tile_data.img = e.target;
    tile_data.current = true;
    tile_data.loaded = true;
    return this.request_render();
  };

  TileRendererView.prototype._on_tile_cache_load = function(e) {
    var tile_data;
    tile_data = e.target.tile_data;
    tile_data.img = e.target;
    return tile_data.loaded = true;
  };

  TileRendererView.prototype._on_tile_error = function(e) {
    return '';
  };

  TileRendererView.prototype._create_tile = function(x, y, z, bounds, cache_only) {
    var normalized_coords, ref, tile;
    if (cache_only == null) {
      cache_only = false;
    }
    normalized_coords = this.mget('tile_source').normalize_xyz(x, y, z);
    tile = this.pool.pop();
    if (cache_only) {
      tile.onload = this._on_tile_cache_load;
    } else {
      tile.onload = this._on_tile_load;
    }
    tile.onerror = this._on_tile_error;
    tile.alt = '';
    tile.tile_data = {
      tile_coords: [x, y, z],
      normalized_coords: normalized_coords,
      quadkey: this.mget('tile_source').tile_xyz_to_quadkey(x, y, z),
      cache_key: this.mget('tile_source').tile_xyz_to_key(x, y, z),
      bounds: bounds,
      loaded: false,
      x_coord: bounds[0],
      y_coord: bounds[3]
    };
    this.mget('tile_source').tiles[tile.tile_data.cache_key] = tile.tile_data;
    tile.src = (ref = this.mget('tile_source')).get_image_url.apply(ref, normalized_coords);
    return tile;
  };

  TileRendererView.prototype._enforce_aspect_ratio = function() {
    var extent, new_extent, zoom_level;
    if (this._last_height !== this.map_frame.get('height') || this._last_width !== this.map_frame.get('width')) {
      extent = this.get_extent();
      zoom_level = this.mget('tile_source').get_level_by_extent(extent, this.map_frame.get('height'), this.map_frame.get('width'));
      new_extent = this.mget('tile_source').snap_to_zoom(extent, this.map_frame.get('height'), this.map_frame.get('width'), zoom_level);
      this.x_range.set({
        start: new_extent[0],
        end: new_extent[2]
      });
      this.y_range.set({
        start: new_extent[1],
        end: new_extent[3]
      });
      this.extent = new_extent;
      this._last_height = this.map_frame.get('height');
      this._last_width = this.map_frame.get('width');
      return true;
    }
    return false;
  };

  TileRendererView.prototype.render = function(ctx, indices, args) {
    if (this.map_initialized == null) {
      this._set_data();
      this._map_data();
      this.map_initialized = true;
    }
    if (this._enforce_aspect_ratio()) {
      return;
    }
    this._update();
    if (this.prefetch_timer != null) {
      clearTimeout(this.prefetch_timer);
    }
    return this.prefetch_timer = setTimeout(this._prefetch_tiles, 500);
  };

  TileRendererView.prototype._draw_tile = function(tile_key) {
    var ref, ref1, sh, sw, sx, sxmax, sxmin, sy, symax, symin, tile_obj;
    tile_obj = this.mget('tile_source').tiles[tile_key];
    if (tile_obj != null) {
      ref = this.plot_view.frame.map_to_screen([tile_obj.bounds[0]], [tile_obj.bounds[3]], this.plot_view.canvas), sxmin = ref[0], symin = ref[1];
      ref1 = this.plot_view.frame.map_to_screen([tile_obj.bounds[2]], [tile_obj.bounds[1]], this.plot_view.canvas), sxmax = ref1[0], symax = ref1[1];
      sxmin = sxmin[0];
      symin = symin[0];
      sxmax = sxmax[0];
      symax = symax[0];
      sw = sxmax - sxmin;
      sh = symax - symin;
      sx = sxmin;
      sy = symin;
      return this.map_canvas.drawImage(tile_obj.img, sx, sy, sw, sh);
    }
  };

  TileRendererView.prototype._set_rect = function() {
    var h, l, outline_width, t, w;
    outline_width = this.plot_model.plot.properties.outline_line_width.value();
    l = this.plot_view.canvas.vx_to_sx(this.map_frame.get('left')) + (outline_width / 2);
    t = this.plot_view.canvas.vy_to_sy(this.map_frame.get('top')) + (outline_width / 2);
    w = this.map_frame.get('width') - outline_width;
    h = this.map_frame.get('height') - outline_width;
    this.map_canvas.rect(l, t, w, h);
    return this.map_canvas.clip();
  };

  TileRendererView.prototype._render_tiles = function(tile_keys) {
    var i, len, tile_key;
    this.map_canvas.save();
    this._set_rect();
    this.map_canvas.globalAlpha = this.mget('alpha');
    for (i = 0, len = tile_keys.length; i < len; i++) {
      tile_key = tile_keys[i];
      this._draw_tile(tile_key);
    }
    return this.map_canvas.restore();
  };

  TileRendererView.prototype._prefetch_tiles = function() {
    var bounds, c, cbounds, children, cx, cy, cz, extent, h, i, ref, results, t, tile_source, tiles, w, x, y, z, zoom_level;
    tile_source = this.mget('tile_source');
    extent = this.get_extent();
    h = this.map_frame.get('height');
    w = this.map_frame.get('width');
    zoom_level = this.mget('tile_source').get_level_by_extent(extent, h, w);
    tiles = this.mget('tile_source').get_tiles_by_extent(extent, zoom_level);
    results = [];
    for (t = i = 0, ref = Math.min(10, tiles.length); i <= ref; t = i += 1) {
      x = t[0], y = t[1], z = t[2], bounds = t[3];
      children = this.mget('tile_source').children_by_tile_xyz(x, y, z);
      results.push((function() {
        var j, len, results1;
        results1 = [];
        for (j = 0, len = children.length; j < len; j++) {
          c = children[j];
          cx = c[0], cy = c[1], cz = c[2], cbounds = c[3];
          if (tile_source.tile_xyz_to_key(cx, cy, cz) in tile_source.tiles) {
            continue;
          } else {
            results1.push(this._create_tile(cx, cy, cz, cbounds, true));
          }
        }
        return results1;
      }).call(this));
    }
    return results;
  };

  TileRendererView.prototype._fetch_tiles = function(tiles) {
    var bounds, i, len, results, t, x, y, z;
    results = [];
    for (i = 0, len = tiles.length; i < len; i++) {
      t = tiles[i];
      x = t[0], y = t[1], z = t[2], bounds = t[3];
      results.push(this._create_tile(x, y, z, bounds));
    }
    return results;
  };

  TileRendererView.prototype._update = function() {
    var bounds, c, cached, cbounds, child_key, children, cx, cy, cz, extent, h, i, j, k, key, len, len1, len2, max_zoom, min_zoom, need_load, parent_key, parent_tile, parents, px, py, pz, ref, snap_back, t, tile, tile_source, tiles, w, x, y, z, zoom_level, zooming_out;
    tile_source = this.mget('tile_source');
    min_zoom = tile_source.get('min_zoom');
    max_zoom = tile_source.get('max_zoom');
    tile_source.update();
    extent = this.get_extent();
    zooming_out = this.extent[2] - this.extent[0] < extent[2] - extent[0];
    h = this.map_frame.get('height');
    w = this.map_frame.get('width');
    zoom_level = tile_source.get_level_by_extent(extent, h, w);
    snap_back = false;
    if (zoom_level < min_zoom) {
      extent = this.extent;
      zoom_level = min_zoom;
      snap_back = true;
    } else if (zoom_level > max_zoom) {
      extent = this.extent;
      zoom_level = max_zoom;
      snap_back = true;
    }
    if (snap_back) {
      this.x_range.set({
        x_range: {
          start: extent[0],
          end: extent[2]
        }
      });
      this.y_range.set({
        start: extent[1],
        end: extent[3]
      });
      this.extent = extent;
    }
    this.extent = extent;
    tiles = tile_source.get_tiles_by_extent(extent, zoom_level);
    parents = [];
    need_load = [];
    cached = [];
    children = [];
    for (i = 0, len = tiles.length; i < len; i++) {
      t = tiles[i];
      x = t[0], y = t[1], z = t[2], bounds = t[3];
      key = tile_source.tile_xyz_to_key(x, y, z);
      tile = tile_source.tiles[key];
      if ((tile != null) && tile.loaded === true) {
        cached.push(key);
      } else {
        if (this.mget('render_parents')) {
          ref = tile_source.get_closest_parent_by_tile_xyz(x, y, z), px = ref[0], py = ref[1], pz = ref[2];
          parent_key = tile_source.tile_xyz_to_key(px, py, pz);
          parent_tile = tile_source.tiles[parent_key];
          if ((parent_tile != null) && parent_tile.loaded && indexOf.call(parents, parent_key) < 0) {
            parents.push(parent_key);
          }
          if (zooming_out) {
            children = tile_source.children_by_tile_xyz(x, y, z);
            for (j = 0, len1 = children.length; j < len1; j++) {
              c = children[j];
              cx = c[0], cy = c[1], cz = c[2], cbounds = c[3];
              child_key = tile_source.tile_xyz_to_key(cx, cy, cz);
              if (child_key in tile_source.tiles) {
                children.push(child_key);
              }
            }
          }
        }
      }
      if (tile == null) {
        need_load.push(t);
      }
    }
    this._render_tiles(parents);
    this._render_tiles(children);
    this._render_tiles(cached);
    for (k = 0, len2 = cached.length; k < len2; k++) {
      t = cached[k];
      tile_source.tiles[t].current = true;
    }
    if (this.render_timer != null) {
      clearTimeout(this.render_timer);
    }
    return this.render_timer = setTimeout(((function(_this) {
      return function() {
        return _this._fetch_tiles(need_load);
      };
    })(this)), 65);
  };

  return TileRendererView;

})(Renderer.View);

TileRenderer = (function(superClass) {
  extend(TileRenderer, superClass);

  function TileRenderer() {
    return TileRenderer.__super__.constructor.apply(this, arguments);
  }

  TileRenderer.prototype.default_view = TileRendererView;

  TileRenderer.prototype.type = 'TileRenderer';

  TileRenderer.define({
    alpha: [p.Number, 1.0],
    x_range_name: [p.String, "default"],
    y_range_name: [p.String, "default"],
    tile_source: [
      p.Instance, function() {
        return new wmts.Model();
      }
    ],
    render_parents: [p.Bool, true]
  });

  TileRenderer.override({
    level: 'underlay'
  });

  return TileRenderer;

})(Renderer.Model);

module.exports = {
  Model: TileRenderer,
  View: TileRendererView
};
