// Generated by CoffeeScript 1.12.8

/*
 * {{{1 -->
 *
 *     File        : bigbang.coffee
 *     Maintainer  : Felix C. Stegerman <flx@obfusk.net>
 *     Date        : 2020-09-02
 *
 *     Copyright   : Copyright (C) 2020  Felix C. Stegerman
 *     Licence     : LGPLv3+
 *     Version     : v0.2.1
 *
 * <!-- }}}1
 */

(function() {
  var B, U, _Stop, draw_scene, empty_scene, handle_click, handle_keys, key_to_string, keycodes, keyranges, measure_text, mk_scene, mouse_position, place_image, place_text, polyRequestAnimationFrame, requestAnimationFrame, stop_with,
    slice = [].slice;

  U = this._ || require('underscore');

  B = function() {
    return B.bigbang.apply(B, arguments);
  };

  if (typeof exports !== "undefined" && exports !== null) {
    module.exports = B;
  } else {
    this.bigbang = B;
  }

  B.polyRequestAnimationFrame = polyRequestAnimationFrame = function(opts) {
    var delay, next;
    if (opts == null) {
      opts = {};
    }
    if (opts.warn) {
      console.warn('polyfilling *RequestAnimationFrame ...');
    }
    delay = opts.delay || 17;
    next = 0;
    return function(cb) {
      var cur, dt;
      cur = +(new Date);
      dt = Math.max(0, delay - (cur - next));
      next = cur + dt;
      return window.setTimeout((function() {
        return cb(+(new Date));
      }), dt);
    };
  };

  B.requestAnimationFrame = requestAnimationFrame = (typeof window !== "undefined" && window !== null ? window.requestAnimationFrame : void 0) || (typeof window !== "undefined" && window !== null ? window.webkitRequestAnimationFrame : void 0) || (typeof window !== "undefined" && window !== null ? window.mozRequestAnimationFrame : void 0) || polyRequestAnimationFrame({
    warn: true
  });

  B.bigbang = function(opts) {
    var anim, cancel_click, cancel_keys, change, changes, click, delay, delay_margin, done, draw, draw_changes, finish, fn, handlers, hc, hk, k, key, next, queue, ref, ref1, s, setup_value, tick, tickless, v, world;
    tickless = !opts.on_tick;
    queue = (ref = opts.queue) != null ? ref : true;
    anim = opts.animate || requestAnimationFrame;
    world = opts.world;
    delay = 1000 / (opts.fps || 60);
    delay_margin = delay / 17;
    done = (typeof opts.stop_when === "function" ? opts.stop_when(world) : void 0) || false;
    next = +(new Date) + delay;
    changes = [];
    setup_value = null;
    if (tickless && ((opts.queue != null) || opts.fps || opts.animate)) {
      throw new Error('queue, fps and animate require on_tick');
    }
    draw = function(w, d) {
      var f;
      f = d && opts.last_draw ? opts.last_draw : opts.to_draw;
      return draw_scene(f(w))(opts.canvas);
    };
    draw_changes = function() {
      var d, i, len, ref1, w;
      for (i = 0, len = changes.length; i < len; i++) {
        ref1 = changes[i], w = ref1.world, d = ref1.done;
        draw(w, d);
      }
      return changes = [];
    };
    change = function() {
      var args, f, x;
      f = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
      if (done || !U.isFunction(f)) {
        return;
      }
      x = f.apply(null, [world].concat(slice.call(args)));
      if (x instanceof _Stop) {
        world = x.world;
        done = true;
      } else {
        world = x;
        if (typeof opts.stop_when === "function" ? opts.stop_when(world) : void 0) {
          done = true;
        }
      }
      if (!tickless && queue) {
        if (changes.length === queue) {
          changes.shift();
        }
        return changes.push({
          world: world,
          done: done
        });
      } else {
        draw(world, done);
        if (done) {
          return finish();
        }
      }
    };
    s = +(new Date);
    tick = function(t) {
      if (t >= next - delay_margin) {
        next += delay;
        change(opts.on_tick, t);
      }
      if (queue) {
        draw_changes();
      }
      if (done) {
        if (queue) {
          return finish();
        }
      } else {
        return anim(tick);
      }
    };
    key = function(k) {
      return change(opts.on_key, k);
    };
    click = function(x, y) {
      return change(opts.on_click, x, y);
    };
    handlers = {};
    ref1 = opts.on || {};
    fn = function(k, v) {
      return handlers[k] = function() {
        var args;
        args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
        return change.apply(null, [v].concat(slice.call(args)));
      };
    };
    for (k in ref1) {
      v = ref1[k];
      fn(k, v);
    }
    finish = function() {
      var tv;
      if (typeof cancel_keys === "function") {
        cancel_keys();
      }
      if (typeof cancel_click === "function") {
        cancel_click();
      }
      tv = typeof opts.teardown === "function" ? opts.teardown(opts.canvas, handlers, setup_value) : void 0;
      return typeof opts.on_stop === "function" ? opts.on_stop(world, tv) : void 0;
    };
    hk = opts.handle_keys || handle_keys;
    hc = opts.handle_click || handle_click;
    cancel_keys = opts.on_key && hk(opts.canvas, key, opts.$);
    cancel_click = opts.on_click && hc(opts.canvas, click, opts.$);
    setup_value = typeof opts.setup === "function" ? opts.setup(opts.canvas, handlers) : void 0;
    draw(world, done);
    if (!tickless) {
      anim(tick);
    }
    return {
      world: (function() {
        return world;
      }),
      done: (function() {
        return done;
      })
    };
  };

  B.stop_with = stop_with = function(w) {
    return new _Stop(w);
  };

  _Stop = (function() {
    function _Stop(world1) {
      this.world = world1;
    }

    return _Stop;

  })();

  B._Stop = _Stop;

  B.mk_scene = mk_scene = function(scene, f) {
    var g;
    g = function(canvas, draw_lower) {
      if (draw_lower == null) {
        draw_lower = true;
      }
      if (draw_lower) {
        if (typeof scene === "function") {
          scene(canvas);
        }
      }
      return f(canvas);
    };
    g.lower_scene = scene;
    return g;
  };

  B.draw_scene = draw_scene = function(scene) {
    return function(canvas) {
      var i, len, s, scenes;
      if ((s = scene).lower_scene) {
        scenes = [scene];
        while (s = s.lower_scene) {
          scenes.push(s);
        }
        scenes.reverse();
        for (i = 0, len = scenes.length; i < len; i++) {
          s = scenes[i];
          s(canvas, false);
        }
        return null;
      } else {
        return scene(canvas);
      }
    };
  };

  B.empty_scene = empty_scene = function(width, height) {
    return mk_scene(null, function(canvas) {
      canvas.width = width;
      return canvas.height = height;
    });
  };

  B.place_text = place_text = function(string, x, y, fontsize, colour, scene, $) {
    if ($ == null) {
      $ = window.$;
    }
    return mk_scene(scene, function(canvas) {
      var ctx, h, ref, w;
      ctx = canvas.getContext('2d');
      ctx.save();
      ctx.font = fontsize + " sans-serif";
      ctx.fillStyle = colour;
      ctx.textBaseline = 'bottom';
      ref = measure_text($, string, fontsize, 'sans-serif'), w = ref.w, h = ref.h;
      ctx.fillText(string, Math.round(x - w / 2), Math.round(y + h / 2));
      return ctx.restore();
    });
  };

  B.place_image = place_image = function(image, x, y, scene) {
    return mk_scene(scene, function(canvas) {
      var ctx, x_, y_;
      ctx = canvas.getContext('2d');
      x_ = x - Math.round(image.width / 2);
      y_ = y - Math.round(image.height / 2);
      return ctx.drawImage(image, x_, y_);
    });
  };

  B.handle_keys = handle_keys = function(elem, f, $) {
    var h;
    if ($ == null) {
      $ = window.$;
    }
    h = function(e) {
      var k;
      if (!e.altKey && !e.ctrlKey && !e.metaKey && e.which !== keycodes.SHIFT) {
        k = key_to_string(e.which, e.shiftKey);
        if (k) {
          f(k);
        }
        return k === null;
      } else {
        return true;
      }
    };
    $(elem).on('keydown', h);
    return function() {
      return $(elem).off('keydown', h);
    };
  };

  B.key_to_string = key_to_string = function(which, shift) {
    var c, k, s, v, w;
    w = which;
    s = shift;
    switch (false) {
      case !((keyranges.ALPHA.from <= w && w <= keyranges.ALPHA.to)):
        c = String.fromCharCode(w);
        if (s) {
          return c;
        } else {
          return c.toLowerCase();
        }
        break;
      case !((keyranges.NUM.from <= w && w <= keyranges.NUM.to)):
        c = String.fromCharCode(w);
        if (s) {
          return "SHIFT_" + c;
        } else {
          return c;
        }
        break;
      default:
        for (k in keycodes) {
          v = keycodes[k];
          if (v === w) {
            if (s) {
              return k;
            } else {
              return k.toLowerCase();
            }
          }
        }
        return null;
    }
  };

  B.keycodes = keycodes = {
    BACKSPACE: 8,
    COMMA: 188,
    DELETE: 46,
    DOWN: 40,
    END: 35,
    ENTER: 13,
    ESCAPE: 27,
    HOME: 36,
    LEFT: 37,
    PAGE_DOWN: 34,
    PAGE_UP: 33,
    PERIOD: 190,
    RIGHT: 39,
    SHIFT: 16,
    SPACE: 32,
    TAB: 9,
    UP: 38
  };

  B.keyranges = keyranges = {
    ALPHA: {
      from: 65,
      to: 90
    },
    NUM: {
      from: 48,
      to: 57
    }
  };

  B.handle_click = handle_click = function(elem, f, $) {
    var h;
    if ($ == null) {
      $ = window.$;
    }
    h = function(e) {
      var ref, x, y;
      ref = mouse_position(e), x = ref.x, y = ref.y;
      f(x, y);
      return false;
    };
    $(elem).on('click', h);
    return function() {
      return $(elem).off('click', h);
    };
  };

  B.mouse_position = mouse_position = function(event, elem, $, cache) {
    var e;
    if (elem == null) {
      elem = event.target;
    }
    if ($ == null) {
      $ = window.$;
    }
    if (cache == null) {
      cache = {};
    }
    e = $(elem);
    if (cache.left == null) {
      cache.left = (e.outerWidth() - e.width()) / 2;
    }
    if (cache.top == null) {
      cache.top = (e.outerHeight() - e.height()) / 2;
    }
    return {
      x: event.offsetX - cache.left,
      y: event.offsetY - cache.top
    };
  };

  B.measure_text = measure_text = function($, text, size, family) {
    var c, d, h, w;
    c = measure_text.cache[size + "|" + family + "|" + text];
    if (c) {
      return c;
    }
    d = $('<div>');
    d.text(text);
    d.css({
      display: 'none',
      'font-size': size,
      'font-family': family
    });
    $('body').append(d);
    w = d.width();
    h = d.height();
    d.remove();
    return measure_text.cache[size + "|" + family + "|" + text] = {
      w: w,
      h: h
    };
  };

  measure_text.cache = {};

}).call(this);
