app.View = class View extends Events {
  constructor(el) {
    super();
    if (el instanceof HTMLElement) {
      this.el = el;
    }
    this.setupElement();
    if (this.el.className) {
      this.originalClassName = this.el.className;
    }
    if (this.constructor.className) {
      this.resetClass();
    }
    this.refreshElements();
    if (typeof this.init === "function") {
      this.init();
      this.refreshElements();
    }
  }

  setupElement() {
    if (this.el == null) {
      this.el =
        typeof this.constructor.el === "string"
          ? $(this.constructor.el)
          : this.constructor.el
            ? this.constructor.el
            : document.createElement(this.constructor.tagName || "div");
    }

    if (this.constructor.attributes) {
      for (var key in this.constructor.attributes) {
        var value = this.constructor.attributes[key];
        this.el.setAttribute(key, value);
      }
    }
  }

  refreshElements() {
    if (this.constructor.elements) {
      for (var name in this.constructor.elements) {
        var selector = this.constructor.elements[name];
        this[name] = this.find(selector);
      }
    }
  }

  addClass(name) {
    this.el.classList.add(name);
  }

  removeClass(name) {
    this.el.classList.remove(name);
  }

  toggleClass(name) {
    this.el.classList.toggle(name);
  }

  hasClass(name) {
    return this.el.classList.contains(name);
  }

  resetClass() {
    this.el.className = this.originalClassName || "";
    if (this.constructor.className) {
      for (var name of Array.from(this.constructor.className.split(" "))) {
        this.addClass(name);
      }
    }
  }

  find(selector) {
    return $(selector, this.el);
  }

  findAll(selector) {
    return $$(selector, this.el);
  }

  findByClass(name) {
    return this.findAllByClass(name)[0];
  }

  findLastByClass(name) {
    const all = this.findAllByClass(name)[0];
    return all[all.length - 1];
  }

  findAllByClass(name) {
    return this.el.getElementsByClassName(name);
  }

  findByTag(tag) {
    return this.findAllByTag(tag)[0];
  }

  findLastByTag(tag) {
    const all = this.findAllByTag(tag);
    return all[all.length - 1];
  }

  findAllByTag(tag) {
    return this.el.getElementsByTagName(tag);
  }

  append(value) {
    $.append(this.el, value.el || value);
  }

  appendTo(value) {
    $.append(value.el || value, this.el);
  }

  prepend(value) {
    $.prepend(this.el, value.el || value);
  }

  prependTo(value) {
    $.prepend(value.el || value, this.el);
  }

  before(value) {
    $.before(this.el, value.el || value);
  }

  after(value) {
    $.after(this.el, value.el || value);
  }

  remove(value) {
    $.remove(value.el || value);
  }

  empty() {
    $.empty(this.el);
    this.refreshElements();
  }

  html(value) {
    this.empty();
    this.append(value);
  }

  tmpl(...args) {
    return app.templates.render(...args);
  }

  delay(fn, ...args) {
    const delay = typeof args[args.length - 1] === "number" ? args.pop() : 0;
    return setTimeout(fn.bind(this, ...args), delay);
  }

  onDOM(event, callback) {
    $.on(this.el, event, callback);
  }

  offDOM(event, callback) {
    $.off(this.el, event, callback);
  }

  bindEvents() {
    let method, name;
    if (this.constructor.events) {
      for (name in this.constructor.events) {
        method = this.constructor.events[name];
        this[method] = this[method].bind(this);
        this.onDOM(name, this[method]);
      }
    }

    if (this.constructor.routes) {
      for (name in this.constructor.routes) {
        method = this.constructor.routes[name];
        this[method] = this[method].bind(this);
        app.router.on(name, this[method]);
      }
    }

    if (this.constructor.shortcuts) {
      for (name in this.constructor.shortcuts) {
        method = this.constructor.shortcuts[name];
        this[method] = this[method].bind(this);
        app.shortcuts.on(name, this[method]);
      }
    }
  }

  unbindEvents() {
    let method, name;
    if (this.constructor.events) {
      for (name in this.constructor.events) {
        method = this.constructor.events[name];
        this.offDOM(name, this[method]);
      }
    }

    if (this.constructor.routes) {
      for (name in this.constructor.routes) {
        method = this.constructor.routes[name];
        app.router.off(name, this[method]);
      }
    }

    if (this.constructor.shortcuts) {
      for (name in this.constructor.shortcuts) {
        method = this.constructor.shortcuts[name];
        app.shortcuts.off(name, this[method]);
      }
    }
  }

  addSubview(view) {
    return (this.subviews || (this.subviews = [])).push(view);
  }

  activate() {
    if (this.activated) {
      return;
    }
    this.bindEvents();
    if (this.subviews) {
      for (var view of Array.from(this.subviews)) {
        view.activate();
      }
    }
    this.activated = true;
    return true;
  }

  deactivate() {
    if (!this.activated) {
      return;
    }
    this.unbindEvents();
    if (this.subviews) {
      for (var view of Array.from(this.subviews)) {
        view.deactivate();
      }
    }
    this.activated = false;
    return true;
  }

  detach() {
    this.deactivate();
    $.remove(this.el);
  }
};