<script language="javascript">
  /* Define the {{ widget_name }} class */
  function {{ widget_name }}(frames, num_frames, id, interval, load_json, mode, cached){
      this.img_id = "_anim_img" + id;
      this.slider_id = "_anim_slider" + id;
      this.loop_select_id = "_anim_loop_select" + id;
      this.id = id;
      this.fig_id = "fig_" + id;
      this.interval = interval;
      this.current_frame = 0;
      this.direction = 0;
      this.timer = null;
      this.load_json = load_json;
      this.mode = mode;
      this.cached = cached;
      this.frames = frames;
      this.cache = {};
      this.length = num_frames;
      document.getElementById(this.slider_id).max = this.length - 1;
      this.init_slider(0);
  }

  {% block init_slider %}
  {{ widget_name }}.prototype.init_slider = function(init_val){
      if(this.cached) {
  	  this.update_cache();
  	  this.update(0);
      } else {
  	  this.dynamic_update(0);
      }
  }
  {% endblock %}

  {% block populate_cache %}
  {{ widget_name }}.prototype.populate_cache = function(idx){
      if(this.load_json) {
  	  var data_url = "{{ server }}/" + this.fig_id + "/" + idx;
  	  this.cache[idx].load(data_url);
      } else {
  	  this.cache[idx].html(this.frames[idx]);
      }
  }
  {% endblock %}


  {% block dynamic_update %}
  {{ widget_name }}.prototype.dynamic_update = function(current){
      var kernel = IPython.notebook.kernel;
      function callback(msg){
	  /* This callback receives data from Python as a string
	     in order to parse it correctly quotes are sliced off*/
	  var data = msg.content.data['text/plain'].slice(1, -1);
	  this.frames[current] = data;
	  this.update_cache();
	  this.update(current);
      }
      if(!(current in this.cache)) {
	  callbacks = {iopub: {output: $.proxy(callback, this)}};
	  var cmd = "holoviews.plotting.widgets.NdWidget.widgets['" + this.id + "'].update(" + current + ")";
	  kernel.execute("import holoviews;" + cmd, callbacks, {silent : false});
      } else {
	  this.update(current);
      }
  }
  {% endblock %}


  {{ widget_name }}.prototype.get_loop_state = function(){
      var button_group = document[this.loop_select_id].state;
      for (var i = 0; i < button_group.length; i++) {
          var button = button_group[i];
          if (button.checked) {
              return button.value;
          }
      }
      return undefined;
  }

  {{ widget_name }}.prototype.update_cache = function(){
      if(this.load_json) {
	  var frame_len = Object.keys(this.keyMap).length;
      } else {
	  var frame_len = Object.keys(this.frames).length;
      }
      for (var i=0; i<frame_len; i++) {
	  if(!this.load_json) {
	      i = Object.keys(this.frames)[i];
	  }
	  if(!(i in this.cache)) {
	      this.cache[i] = $('<div />').appendTo("#" + this.img_id).hide();
	      var cache_id = this.img_id+"_"+i;
	      this.cache[i].attr("id", cache_id);
	      this.populate_cache(i);
	  }
      }
  }

  {{ widget_name }}.prototype.set_frame = function(frame){
      this.current_frame = frame;
      document.getElementById(this.slider_id).value = this.current_frame;
      if(this.cached) {
	  this.update(frame)
      } else {
          this.dynamic_update(frame)
      }
  }

  {{ widget_name }}.prototype.update = function(current){
      if(current in this.cache) {
	  $.each(this.cache, function(index, value) {
	      value.hide();
	  });
	  this.cache[current].show();
      }
  }


  {{ widget_name }}.prototype.next_frame = function() {
      this.set_frame(Math.min(this.length - 1, this.current_frame + 1));
  }
  {{ widget_name }}.prototype.previous_frame = function() {
      this.set_frame(Math.max(0, this.current_frame - 1));
  }
  {{ widget_name }}.prototype.first_frame = function() {
      this.set_frame(0);
  }
  {{ widget_name }}.prototype.last_frame = function() {
      this.set_frame(this.length - 1);
  }
  {{ widget_name }}.prototype.slower = function() {
      this.interval /= 0.7;
      if(this.direction > 0){this.play_animation();}
      else if(this.direction < 0){this.reverse_animation();}
  }
  {{ widget_name }}.prototype.faster = function() {
      this.interval *= 0.7;
      if(this.direction > 0){this.play_animation();}
      else if(this.direction < 0){this.reverse_animation();}
  }
  {{ widget_name }}.prototype.anim_step_forward = function() {
      this.current_frame += 1;
      if(this.current_frame < this.length){
          this.set_frame(this.current_frame);
      }else{
          var loop_state = this.get_loop_state();
          if(loop_state == "loop"){
              this.first_frame();
          }else if(loop_state == "reflect"){
              this.last_frame();
              this.reverse_animation();
          }else{
              this.pause_animation();
              this.last_frame();
          }
      }
  }
  {{ widget_name }}.prototype.anim_step_reverse = function() {
      this.current_frame -= 1;
      if(this.current_frame >= 0){
          this.set_frame(this.current_frame);
      } else {
          var loop_state = this.get_loop_state();
          if(loop_state == "loop"){
              this.last_frame();
          }else if(loop_state == "reflect"){
              this.first_frame();
              this.play_animation();
          }else{
              this.pause_animation();
              this.first_frame();
          }
      }
  }
  {{ widget_name }}.prototype.pause_animation = function() {
      this.direction = 0;
      if (this.timer){
          clearInterval(this.timer);
          this.timer = null;
      }
  }
  {{ widget_name }}.prototype.play_animation = function() {
      this.pause_animation();
      this.direction = 1;
      var t = this;
      if (!this.timer) this.timer = setInterval(function(){t.anim_step_forward();}, this.interval);
  }
  {{ widget_name }}.prototype.reverse_animation = function() {
      this.pause_animation();
      this.direction = -1;
      var t = this;
      if (!this.timer) this.timer = setInterval(function(){t.anim_step_reverse();}, this.interval);
  }
</script>

<div class="animation" align="center">
    <div id="_anim_img{{ id }}"></div>
    <br>
    <input id="_anim_slider{{ id }}" type="range" style="width:350px" name="points" min="0" max="1" step="1" value="0" oninput="anim{{ id }}.set_frame(parseInt(this.value));"></input>
    <br>
    <button onclick="anim{{ id }}.slower()" style="text-align: center">&#8211;</button>
    <button onclick="anim{{ id }}.first_frame()" style="text-align: center; min-width: 40px">&#9616;&#9664;&#9664;</button>
    <button onclick="anim{{ id }}.previous_frame()" style="text-align: center; min-width: 40px">&#9616;&#9664;</button>
    <button onclick="anim{{ id }}.reverse_animation()" style="text-align: center; min-width: 40px">&#9664;</button>
    <button onclick="anim{{ id }}.pause_animation()" style="text-align: center; min-width: 40px">&#9616;&nbsp;&#9612;</button>
    <button onclick="anim{{ id }}.play_animation()" style="text-align: center; min-width: 40px">&#9654;</button>
    <button onclick="anim{{ id }}.next_frame()" style="text-align: center; min-width: 40px">&#9654;&#9612;</button>
    <button onclick="anim{{ id }}.last_frame()" style="text-align: center; min-width: 40px">&#9654;&#9654;&#9612;</button>
    <button onclick="anim{{ id }}.faster()" style="text-align: center">+</button>
  <form action="#n" name="_anim_loop_select{{ id }}" class="anim_control">
    <input type="radio" name="state" value="once" {once_checked}> Once </input>
    <input type="radio" name="state" value="loop" {loop_checked}> Loop </input>
    <input type="radio" name="state" value="reflect" {reflect_checked}> Reflect </input>
  </form>
</div>


<script language="javascript">
  /* Instantiate the {{ widget_name }} class. */
  /* The IDs given should match those used in the template above. */
  (function() {
      var frame_data = {{ frames | safe }};

      function create_widget() {
          setTimeout(function() {
              anim{{ id }} = new {{ widget_name }}(frame_data, {{ Nframes }}, "{{ id }}", {{ interval }}, {{ load_json }}, {{ mode }}, {{ cached }});
          }, 0);
      }

      {% block create_widget %}
      create_widget();
      {% endblock %}
  })()
</script>
