module.exports = window.R = {
  _location: location,
  _replaceState: history.replaceState.bind(history),
  _pushState: history.pushState.bind(history),
  _decodeURIComponent: decodeURIComponent,
  _encodeURIComponent: encodeURIComponent,
  init: function(opts) {
    if (opts == null) {
      opts = {};
    }
    this.root || (this.root = opts.root || 'root');
    this.views || (this.views = opts.views || window.Views || [this.root]);
    this.render || (this.render = opts.render || window.render);
    this.h || (this.h = opts.helpers || window._);
    window.onpopstate = this.url_changed.bind(this)
	this.read();
  },
  cache: {},
  getters: {},
  setters: {},
  param: function(key) {
    var base;
    if (this.getters[key]) {
      return (base = this.cache)[key] || (base[key] = this.getters[key](this._decodeURIComponent(this.params[key] || '')));
    } else {
      return this._decodeURIComponent(this.params[key] || '');
    }
  },
  write: function() {
    var i, j, k, len, v;
	for (i = j = 0, len = arguments.length; j < len; i = ++j) {
	  k = arguments[i];
	  if (!(i % 2)) {
		v = arguments[i + 1];
		this._write(k, v);
	  }
	}
    this._replaceState({}, this._location.pathname, this.to_path(this.view, this.params));
    this.url_changed();
  },
  _write: function(k, v) {
	var value = this.setters[k] ? this.setters[k](v) : v;
	value ? this.params[k] = this._encodeURIComponent(value) : delete this.params[k];
  },
  toggle: function(flag, state) {
    this.write(flag, state != null ? (state ? 1 : void 0) : (!this.params[flag] ? 1 : void 0));
  },
  go: function(path) {
    this._pushState({}, null, path);
    this.url_changed();
  },
  read: function() {
    var ref;
    ref = this.split_path(this._location.pathname), this.view = ref[0], this.params = ref[1];
    this._safe_params();
  },
  split_path: function(path) {
    if (path.length == 1) {
      return [this.root, {}];
	}
    var list, params, view;
    list = this.h.compact(path.split("/"));
    view = this._existance(list.shift());
    params = this.h.fromPairs(this.h.chunk(list, 2));
    return [view, params];
  },
  _existance: function(view) {
    if (this.h.includes(this.views, view)) {
      return view;
    } else {
      return 'not_found';
    }
  },
  _safe_params: function() {
    return this.safe_params = this.h.fromPairs(this.h.reject(this.h.toPairs(this.params), function(pair) {
      return /^_/.test(pair[0]);
    }));
  },
  to_path: function(view, params) {
    var array;
    if (view == null) {
      view = this.view;
    }
    if (params == null) {
      params = this.safe_params;
    }
    array = this.h.flatten(this.h.toPairs(params));
    return '/' + view + '/' + array.join('/');
  },
  url_changed: function() {
    this.cache = {};
    this.read();
    this.render();
  }

};