
DomUtils = {
  hide_animated: function(element, user_options) {
    element._change_visibility_animated(false, user_options);
  },
  show_animated: function(element, user_options) {
    element._change_visibility_animated(true, user_options);
  },
  _change_visibility_animated: function(element, visible, user_options) {
    var options = { duration: 0.5, queue: { position: 'end', scope: element.identify() }, afterFinish: function() {} };
    if (user_options) Object.extend(options, user_options);
    new Effect.Parallel([
      new Effect[visible ? 'BlindDown' : 'BlindUp'](element, {sync: true}),
      new Effect.Opacity(element, {sync: true, from: (visible ? 0 : 1), to: (visible ? 1 : 0)})
    ], options);
  }
}

Element.addMethods(DomUtils);

Page = {
  questions: [],
  index_for_question: function(q1) {
    var index = null;
    this.questions.each(function(q2, i){ if (q1 == q2) { index = i; throw $break; } });
    return index;
  },
  question_at_offset_from_question: function(q, o) {
    var new_index = this.index_for_question(q) + o;
    return new_index >= 0 && new_index < this.questions.length ? this.questions[new_index] : null;
  },
  question_following: function(q) {
    return this.question_at_offset_from_question(q, 1);
  },
  question_preceding: function(q) {
    return this.question_at_offset_from_question(q, -1);
  },

  running_question_fx_count: 0,  // tracks questions being shown/hidden using Effects, whose positions are thus in flux

  prompter: {
    _update: function() {
      var missing = $A([]);
      var last_answered_index = -1;
      var promptable = null;
      for (var i = Page.questions.length - 1; i >= 0; i --) { // work backwards from end, marking last answered and then any missing
        var q = Page.questions[i];
        if (last_answered_index != -1 &&   q.blank() && q.really_visible && q.enabled() && ! q.no_missing_prompt) missing.push(q);
        if (last_answered_index == -1 && ! q.blank() && q.really_visible) last_answered_index = i;
      }
      for (var i = last_answered_index + 1; i < Page.questions.length; i ++) { // work forwards from last answered, finding promptable if it exists
        var q = Page.questions[i];
        if (q.really_visible && ! q.no_next_prompt) {
          promptable = q; break;
        }
      }
      Page.questions.each(function(q) {
        missing.include(q) ? q.show_prompt('missing') : q.hide_prompt('missing');
        // q == promptable    ? q.show_prompt('next')    : q.hide_prompt('next'); // for the simple fading in/out next prompt
      });
      Page.prompter.new_page_prompt_q = promptable; // for the sliding next prompt
    },
    update: function() {
      setTimeout(Page.prompter._update, 1);
    },

    page_prompt: null,
    current_page_prompt_q: null,
    new_page_prompt_q: null,
    update_page_prompt: function() {
      var pp = Page.prompter;
      if (! pp.page_prompt) {
        pp.page_prompt = $(document.createElement('div'));
        pp.page_prompt.addClassName('page_prompt');
        pp.page_prompt.setOpacity(0);
        $('page').insertBefore(pp.page_prompt, $('page').firstChild);
      }
      if (Page.running_question_fx_count == 0) {
        if (pp.new_page_prompt_q) {
          var coords =  pp.new_page_prompt_q.get_page_coords();
          if (pp.current_page_prompt_q == null) {
            pp.page_prompt.style.top  = coords.top + 20 + 'px';
            Page.running_question_fx_count ++;
            new Effect.Opacity(pp.page_prompt, {from: 0, to: 1, duration: 0.5, queue: { position: 'end', scope: 'page' }, afterFinish: function() { Page.running_question_fx_count --; }});
          } else {
            var old_x = parseFloat(pp.page_prompt.getStyle('left'));
            var old_y = parseFloat(pp.page_prompt.getStyle('top'));
            var new_y = coords.top + 20;
            if (old_y != new_y) {
              Page.running_question_fx_count ++;

              if (window.Websperiment$auto_scroll) {
                var max_scroll = $('page').getHeight() - document.viewport.getHeight();
                var vp_target = document.viewport.getHeight() * 0.33;
                new Effect.Parallel(
                  [new Effect.Move(pp.page_prompt, {x: old_x, y: new_y, mode: 'absolute', sync: true}),
                  new Effect.ScrollTo('page', {offset: Math.max(Math.min(new_y - vp_target, max_scroll), 0), sync: true})],
                  {duration: 0.5, queue: { position: 'end', scope: 'page' }, afterFinish: function() { Page.running_question_fx_count --; }}
                );
              } else {
                new Effect.Move(pp.page_prompt, {x: old_x, y: new_y, mode: 'absolute', duration: 0.5, queue: { position: 'end', scope: 'page' }, afterFinish: function() { Page.running_question_fx_count --; }});
              }

            }
          }
        } else {
          if (pp.current_page_prompt_q) {
            Page.running_question_fx_count ++;
            new Effect.Opacity(pp.page_prompt, {from: 1, to: 0, duration: 0.5, queue: { position: 'end', scope: 'page' }, afterFinish: function() { Page.running_question_fx_count --; }});
          }
        }
        pp.current_page_prompt_q = pp.new_page_prompt_q;
      }
    }
  }
}

$A(['dom:loaded', 'click', 'keyup']).each(function(e) { document.observe(e, Page.prompter.update); });
setInterval(Page.prompter.update_page_prompt, 100); // for the sliding next prompt

Log = {
  stack: [],
  levels: ['debug', 'info', 'warn', 'error'],
  log: function(level, data) { Log.stack.push({level: level, data: data}); },
  show: function() { alert(this.stack.map(function(i) { return this.levels[i.level] + ": " + i.data; }, this).join("\n")); }
}
Log.levels.each(function(l, i) { Log[l] = function(data) { Log.log(i, data) } });


document.observe('dom:loaded', function() {

  // Hide any .no_js elements (we definitively *have* JS!)
  $$('.no_js').invoke('hide');

  // Turn obfuscated email spans into links
  var spans = $$('span.em_obf');
  spans.each(function(span) {
    var content = span.removeChild(span.firstChild);
    var link = new Element('a', {href: '#contact'});
    link.appendChild(content);
    span.appendChild(link);
  });

  // Deal with the email links when clicked
  document.observe('click', function(event) {
    var email = event.element().up('.em_obf');
    if (email) {
      var adr = 'mailto:';
      $A(email.firstChild.firstChild.childNodes).each(function(c) {
        if (c.nodeType == 3) adr += c.nodeValue;
        else adr += ($(c).hasClassName('em_obf_a') ? '@' : '.');
      });
      window.location = adr;
      event.stop();
    }
  });

  // Make any standard links open in a new window, to avoid taking people away from the survey
  document.observe('click', function(event) {
    var element = event.element();
    if (element.nodeName.toLowerCase() != 'a') element = element.up('a');

    if (element && element.href && element.href.match(/^https?:\/\//) && ! element.onclick) {
      window.open(element.href);
      event.stop();
    }
  });

  // Add autoscroll control
  if (location.hash == '#as' || get_cookie('autoscroll')) {  // enable only for testing at present
    var as_control = new Element('div').update('auto-scroll is ');
    as_control.id = 'as_control';
    var as_link = new Element('a');
    as_link.id = 'as_link';
    as_link.href = '#toggle';
    Websperiment$auto_scroll = (get_cookie('autoscroll') == 'on');
    var as_toggle = function() {
      Websperiment$auto_scroll = ! Websperiment$auto_scroll;
      set_cookie('autoscroll', Websperiment$auto_scroll ? 'on' : 'off');
    }
    var as_update = function() { as_link.update(Websperiment$auto_scroll ? 'on' : 'off'); }
    as_link.onclick = function() { as_toggle(); as_update(); return false; }
    as_control.appendChild(as_link);
    as_update();
    document.body.appendChild(as_control);
  }

});


if (window.Websperiment$PNGs_need_fixing) {

  Event.observe(window, 'load', function() {
    // Fix PNGs on load rather than dom:loaded because IE6 goes almost understandably weird
    // if we try to set width/height before img has loaded and these dimensions are available
    $$('img').each(function(img) {
      if (img.src.substring(img.src.length - 4, img.src.length).toLowerCase() != ".png" || img.hasClassName('skip_ie_fix')) return;
      var style = img.style.cssText + '; display: inline-block; width: ' + img.width + 'px; height: ' + img.height + 'px; ';
      style += "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + img.src + "', sizingMethod='scale'); ";
      if (img.parentElement.href) style += 'cursor: hand; ';
      var span = new Element('span', { title: img.title || img.alt, style: style });
      span.id = img.id;
      span.className = img.className;
      img.replace(span);
    });
  });

}

function set_cookie(name, value, expires, path, domain, secure) {
	var curCookie = name + '=' + escape(value) + ((expires) ? '; expires=' + expires.toGMTString() : '') + ((path) ? '; path=' + path : '') + ((domain) ? '; domain=' + domain : '') + ((secure) ? '; secure' : '');
	document.cookie = curCookie;
}

function get_cookie(name) {
	var dc = document.cookie;
	var prefix = name + "=";
	var begin = dc.indexOf("; " + prefix);
	if (begin == -1) {
		begin = dc.indexOf(prefix);
		if (begin != 0) return null;
	}
	else begin += 2;
	var end = document.cookie.indexOf(";", begin);
	if (end == -1) end = dc.length;
	return unescape(dc.substring(begin + prefix.length, end));
}

Class$Q$Base = Class.create({
  initialize: function(div_id) {
    this.div_id = div_id;
    this.really_visible = true;  /* this variable is necessary because, for example, when we call hide_animated() we want to
                                    know whether the question is logically hidden, even if it is still visible() while being slid out */
    this.prompts = {};
    this.prompts_showing = {};
    Page.questions.push(this);
  },
  get_div: function() {
    if (! this.div) this.div = $(this.div_id);
    return this.div;
  },

  show: function() {
    this.get_div().show();
    this.really_visible = true;
    return this;
  },
  hide: function() {
    this.get_div().hide();
    this.really_visible = false;
    return this;
  },

  show_animated: function() {
    if (! this.really_visible) {
      this.really_visible = true;
      Page.running_question_fx_count ++;
      this.get_div().show_animated({ afterFinish: function() { Page.running_question_fx_count --; } });
    }
    return this;
  },
  hide_animated: function() {
    if (this.really_visible) {
      this.really_visible = false;
      Page.running_question_fx_count ++;
      this.get_div().hide_animated({ afterFinish: function() { Page.running_question_fx_count --; } });
    }
    return this;
  },

  enable: function() {
    this.get_div().down('input').disabled = false;
    return this;
  },
  disable: function() {
    this.get_div().down('input').disabled = true;
    return this;
  },
  set_enabled: function(b) {
    return b ? this.enable() : this.disable();
  },
  enabled: function() {
    return ! this.get_div().down('input').disabled;
  },

  answer: function() {
    var input = this.get_div().down('input');
    if (arguments.length) {
      input.value = arguments[0];
      return this;
    } else {
      return input.value;
    }
  },
  blank: function() {
    return ! this.answer();
  },

  show_if: function(cond_fn) {
    var change_handler = function() {
      if (cond_fn()) this.show_animated();
      else this.hide_animated();
    }.bindAsEventListener(this);
    document.observe('dom:loaded', function() {
      if (! cond_fn()) this.hide();
      $A(['click', 'keyup']).each(function(e) { document.observe(e, change_handler); });  // handle many changes with these (onchange most appropriate, but broken in IE)
    }.bindAsEventListener(this));
  },

  show_prompt: function(type) {
    if (! this.prompts[type]) {
      this.prompts[type] = $(document.createElement('div'));
      this.prompts[type].addClassName(type + '_prompt');
      this.prompts[type].setOpacity(0);
      this.get_div().insertBefore(this.prompts[type], this.get_div().firstChild);
    }
    if (! this.prompts_showing[type]) {
      new Effect.Opacity(this.prompts[type], {from: 0, to: 1, duration: 0.5, queue: { position: 'end', scope: this.div_id }});
      this.prompts_showing[type] = true;
    }
  },
  hide_prompt: function(type) {
    if (this.prompts_showing[type]) {
      new Effect.Opacity(this.prompts[type], {from: 1, to: 0, duration: 0.5, queue: { position: 'end', scope: this.div_id }});
      this.prompts_showing[type] = false;
    }
  },

  get_page_coords: function() {
    return this.get_div().cumulativeOffset();
  }
});

Class$Q$Text = Class.create(Class$Q$Base, {
  default_text: '',
  set_default_text: function(t) {
    var input = this.get_div().down('input') || this.get_div().down('textarea');
    var me = this;
    this.default_text = t;
    var show_default = function() {
      input.addClassName('default_text');
      me.answer(t);
    }
    var hide_default = function() {
      input.removeClassName('default_text');
      me.answer('');
    }
    document.observe('dom:loaded', function() { if (input.value == t || input.value == '') show_default(); });
    input.observe('focus', function() { if (input.value == t) hide_default(); });
    input.observe('blur', function() { if (input.value == '') show_default(); });
  },
  add_text_filter: function(filter_fn) {
    var input = this.get_div().down('input') || this.get_div().down('textarea');
    var me = this;
    var handler_fn = function(e) {
      var original = e.element().value;
      var filtered = filter_fn(original);
      if (filtered != original) e.element().value = filtered;  // only change if different, else annoyingly resets cursor in IE
    }
    document.observe('dom:loaded', function() {
      $A(['keyup', 'change']).each(function(e) { input.observe(e, handler_fn); });
    });
  },
  answer: function() {
    var input = this.get_div().down('input') || this.get_div().down('textarea');
    if (arguments.length) {
      input.value = arguments[0];
      return this;
    } else {
      return input.value != this.default_text ? input.value : '';
    }
  }
});

Class$Q$Radio = Class.create(Class$Q$Base, {
  answer: function() {
    var radios = this.get_div().select('input');
    if (arguments.length) {
      var value = arguments[0]; // each function's arguments array will overwrite this one
      radios.each(function(r) { if (r.value == value) r.checked = true; });
      return this;
    } else {
      var radio = radios.find(function(r) { return r.getValue() }); // getValue returns null unless radio is selected
      return radio ? radio.getValue() : null;
    }
  },
  clear: function() {
    var radios = this.get_div().select('input');
    radios.each(function(r) { r.checked = false; });
    return this;
  },
  monitor_deselection: function() {
    var radios = this.get_div().select('input');
    var labels = this.get_div().select('label');
    var deselect = null;
    var me = this;
    this.get_div().observe('mousedown', function(event) {
      var el = event.element();
      if (labels.include(el)) el = radios.find(function(l) { return l.id == (el.getAttribute('for') || el.htmlFor); });
      if (radios.include(el) && el.checked) deselect = el;
    });
    this.get_div().observe('mouseout', function(event) {
      deselect = null;
    });
    this.get_div().observe('click', function(event) {
      var el = event.element();
      if (radios.include(el)) {
        if (el == deselect) { el.checked = false; }
        deselect = null;
      }
    });
  },

  /* --- clear control: obsolete? --- */
  clear_control: null,
  clear_control_really_visible: true,
  create_clear_control: function() {
    var me = this;
    var c = new Element('div').addClassName('radio_clear_control');
    var a = new Element('a', { 'href': '#clear' }).update('clear');
    a.onclick = function(e) { me.clear(); return false; }
    c.appendChild(a);
    if (! this.answer()) {
      c.setOpacity(0);
      this.clear_control_really_visible = false;
    }
    $A(['click', 'keypress']).each(function(e) { document.observe(e, function() { me.update_clear_control_visibility(); }) });
    this.get_div().firstChild.appendChild(c);
    this.clear_control = c;
  },
  update_clear_control_visibility: function() {
    var a = this.answer();
    var change_needed = (a ? true : false) != this.clear_control_really_visible;
    if (change_needed) {
      new Effect.Opacity(this.clear_control, {from: (a ? 0 : 1), to: (a ? 1 : 0), duration: 0.5, queue: { position: 'end', scope: this.div_id + '_cc' }});
      this.clear_control_really_visible = (a ? true : false);
    }
  }
  /* --- clear control: obsolete? --- */

});

Class$Q$Dropdown = Class.create(Class$Q$Base, {
  answer: function() {
    var sel = this.get_div().down('select')
    if (arguments.length) {
      sel.value = arguments[0];
      return this;
    } else {
      return sel.getValue();
    }
  },
  enable: function() {
    this.get_div().down('select').disabled = false;
    return this;
  },
  disable: function() {
    this.get_div().down('select').disabled = true;
    return this;
  },
  enabled: function() {
    return ! this.get_div().down('select').disabled;
  }
});

Class$Q$AnswerHistory = Class.create(Class$Q$Base, {
  get_answers: function() {
    var a = this.answer();
    return a && a.length ? $A(a.split(',')).map(function(i) { return unescape(i); }) : $A([]);
  },
  set_answers: function(as) {
    this.answer($A(as).map(function(i) { return escape(i) }).join(','));
  },
  monitor_question: function(q) {
    var me = this;
    var change_handler = function() {
      var a = q.answer();
      var as = me.get_answers();
      if (as.last() != a) {
        as.push(a);
        me.set_answers(as);
      }
    }
    document.observe('dom:loaded', function() {
      $A(['click', 'keyup']).each(function(e) { document.observe(e, change_handler); });
    });
  }
});

Class$Q$Checkbox = Class.create(Class$Q$Base, {
  answer: function() {
    var boxes = this.get_div().select('input');
    if (arguments.length) {
      var to_check = $A(arguments[0]); // because the each function has its own arguments array, overwriting the top-level one
      boxes.each(function(b) {
        if (to_check.include(b.value)) b.checked = true;
        else b.checked = false;
      });
      return this;
    } else {
      return boxes.invoke('getValue').reject(function(v) { return v == null; });
    }
  },
  blank: function() {
    return this.answer().length == 0;
  },
  enforce_exclusivity: function(value) {
    var me = this;
    document.observe('dom:loaded', function() {
      $A(['click', 'keypress']).each(function(e) { document.observe(e, function(event) {
        var el = event.element();
        if ($A(['input', 'label']).include(el.tagName.toLowerCase()) && me.answer().include(value) && el.ancestors().include(me.get_div())) {
          var excl_chk = me.get_div().select('input').find(function(i) {
            return i.value == value;
          });
          var excl_label = me.get_div().select('label').find(function(l) {
            return l.getAttribute('for') == excl_chk.id || l.htmlFor == excl_chk.id;
          });
          if (el == excl_chk || el == excl_label || el.ancestors().include(excl_label)) {
            // click was on the exclusive option? clear the others
            me.answer([value]);
          } else {
            // otherwise click was on one of the others: clear exclusive option
            me.answer(me.answer().without(value));
          }
        }
      });
    }); });
  }
});


Class$Q$VirtualEarth = Class.create(Class$Q$Base, {
  setup: function(map_id, start_location, zoom) {
    var me = this;
    document.observe('dom:loaded', function() {
      var map_div = me.get_div().down('.ve_map');
      var message_div = me.get_div().down('.ve_map_message');
      var map = new VEMap(map_id);
      map.SetDashboardSize(VEDashboardSize.Tiny);
      map.AttachEvent('onmousewheel', function() { return true; }); // disable mouse wheel -- could be confusing when trying to scroll
      map.LoadMap(new VELatLong(51.5001524, -0.1262362), zoom, VEMapStyle.Hybrid, false, VEMapMode.Mode2D, false, 1);
      var handle_change = function() {
        me.answer(map.GetCenter().Latitude + ',' + map.GetCenter().Longitude + ',' + map.GetZoomLevel());
        map.GetZoomLevel() < zoom ? Effect.Appear(message_div, {duration: 0.5}) : Effect.Fade(message_div, {duration: 0.5});
      }
      var finalize_display = function () {
        handle_change();
        map.AttachEvent('onchangeview', handle_change);
        var marker_div = me.get_div().down('.ve_map_marker');
        map_div.insertBefore(marker_div, map_div.firstChild); // move marker image inside map div, so clicks and drags fall through it
        marker_div.style.visibility = $(map_id).style.visibility = 'visible';
      }
      if (me.answer()) {
        var coords = me.answer().split(',');
        var latLong = new VELatLong(coords[0], coords[1]);
        map.SetCenterAndZoom(latLong, coords[2]);
        finalize_display();
      } else if (start_location && start_location.length > 0) {
        map.Find(null, start_location, null, null, null, null, null, null, null, null, function() {
          map.SetZoomLevel(zoom);
          finalize_display();
        });
      } else {
        map.SetZoomLevel(zoom);
        finalize_display();
      }
    });
  }
});
