// vim: set ts=2 sw=2 et tw=80:
class App {
  static get BRUSHES() {
    return {
      'PenBrush': new PenBrush(),
      'DiscBrush': new DiscBrush(),
      'StarBrush': new StarBrush(),
    };
  }

  render_timestamp = 0;
  render_last = null;
  render_timeout = null;
  socketSessionId = null;

  registerFavoriteEvents(_id, dom) {
    const imgurForm = dom.querySelector('form.favorites');
    const replaceToggle = dom.querySelector('input[name=replace]');
    const albumInput = imgurForm.querySelector('input[name=album]');
    const oldNameInput = imgurForm.querySelector('input[name=old_name]');
    const replacePanel = imgurForm.querySelector('.replace');
    const detailsPanel = imgurForm.querySelector('.details');

    dom.querySelector('button.imgur').onclick = () => {
      imgurForm.classList.toggle('hidden');
    };

    replaceToggle.onclick = () => {
      if (replaceToggle.checked) {
        replacePanel.classList.remove('hidden');
        albumInput.setAttribute('disabled', 'disabled');
        albumInput.removeAttribute('required');
        oldNameInput.setAttribute('required', 'required');
      } else {
        replacePanel.classList.add('hidden');
        albumInput.removeAttribute('disabled');
        albumInput.setAttribute('required', 'required');
        oldNameInput.removeAttribute('required');
      }
    };

    imgurForm.onsubmit = e => {
      imgurForm.classList.remove('ok');
      imgurForm.classList.remove('err');
      detailsPanel.innerHTML = '';

      e.preventDefault();

      const data = new FormData(imgurForm);
      const obj = {
        dataURL: data.get('dataURL'),
        name: dom.querySelector('form.data input[name=name]').value,
        replace: data.get('replace'),
        oldName: data.get('old_name'),
        album: data.get('album'),
        tags: data.get('tags'),
        favorites: data.get('favorites')
      };

      doJSONRequest('POST', '/imgur/ordeal', {}, obj)
        .then(e => {
          if (e.ordealSuccess === true) {
            imgurForm.classList.add('ok');
            if (e.views !== undefined) {
              detailsPanel.innerHTML +=
                `<strong>Views:</strong> ${e.views}<br>`;
            }
            if (e.votes) {
              detailsPanel.innerHTML +=
                `<strong>Upvotes:</strong> ${e.votes.ups}<br>
                 <strong>Downvotes:</strong> ${e.votes.downs}<br>`;
            }
          } else {
            imgurForm.classList.add('err');
            if (e.error) {
              detailsPanel.innerHTML += `
                <strong>Error:</strong> ${e.error}<br>`;
            }
          }
        }).catch(e => {
          console.error(e);
          imgurForm.classList.add('err');
        });
    };

    const form = dom.querySelector('form.data');

    const delay = (f, ...args) => {
      const execute = (f, ...args) => {
        console.log('don\'t delay, solve Carzaniga today', ...args);
        this.render_timestamp = new Date().getTime();
        this.render_timeout = null;
        this.render_last = null;
        f(...args);
      };

      if (new Date().getTime() - this.render_timestamp < 100000) {
        console.log('delaying', ...args);
        this.render_last = () => execute(f, ...args);
        if (this.render_timeout === null) {
          this.render_timeout = setTimeout(() => this.render_last(), 100);
        }
        return;
      }

      execute(f, ...args);
    }

    form.querySelector('input[name=name]').onkeyup =
      () => delay(submit, 'data-update', false);

    const submit = (action, update = true) => {
      let p;

      const formData = new FormData(form);
      const name = formData.get('name');
      const dataURL = formData.get('dataURL');
      const query = '?socketid=' + this.socketSessionId;

      switch (action) {
        case 'data-update':
          p = doJSONRequest('PUT', '/favorites/' + _id + query, {},
            { name, dataURL });
          break;
        case 'data-delete':
          p = doJSONRequest('DELETE', '/favorites/' + _id + query);
          break;
        case 'data-bookmark':
          p = doJSONRequest('PUT', `/favorites/${_id}/bookmarked` + query,
            {}, { bookmarked: true });
          break;
        case 'data-del-bookmark':
          p = doJSONRequest('PUT', `/favorites/${_id}/bookmarked` + query,
            {}, { bookmarked: false });
          break;
        default:
          console.warn(action, ' is not a valid action');
          return;
      }

      p.catch(console.error);

      if (action == 'data-delete' && update) {
        p.then(_ => dom.remove());
      } else if (update) {
        p.then(e => this.renderFavorite(e, dom));
      }

      if (!update) {
        // updating via javascript to not disrupt cursor in name when
        // replacing html
        dom.querySelector('h3').innerText = name;
      }
    };

    form.onsubmit = e => {
      e.preventDefault();

      const formData = new FormData(form);
      const name = formData.get('name');
      const dataURL = formData.get('dataURL');

      const action = e.explicitOriginalTarget.getAttributeNames()
        .filter(e => e.startsWith('data-'))[0];

      submit(action);
    };
  }

  renderFavorite(data, div) {
    data.bookmarked = data.bookmarked === true || data.bookmarked === 'true';
    dust.render('favourite_partial',
      data,
      (err, out) => {
        if (err) {
          console.error(err);
        } else {
          let dom;

          if (div === 'top' || !div) {
            dom = document.createElement('div');
            dom.id = 'favorite_' + data._id;
            dom.innerHTML = out;

            if (div === 'top') {
              this.favourites.prepend(dom);
            } else {
              this.favourites.appendChild(dom);
            }
          } else {
            div.innerHTML = out;
            dom = div;
          }
          dom.className = data.bookmarked ? 'bookmarked' : '';

          this.registerFavoriteEvents(data._id, dom);
        }
      });
  }

  getFavorites(nameFilter, onlyBookmarked = false) {
    let url = '/favorites/search?ajax=true';

    if (nameFilter) {
      url += '&name=' + encodeURIComponent(nameFilter);
    }

    if (onlyBookmarked) {
      url += '&bookmarked=true';
    }

    doFetchRequest('GET', url, { 'Accept': 'text/html' })
      .then(res => res.text())
      .then(html => {
        this.favourites.innerHTML = html;
        this.favourites.childNodes.forEach(e => {
          const id = e.id.substring('favorite_'.length);
          this.registerFavoriteEvents(id, e);
        });
      })
      .catch(console.error);
  }

  initSocket() {
    const socket = io();

    socket.on('connect', () => {
      console.log('Socket connected');
    });

    socket.on('sessionId', (msg) => {
      console.log('Socket session id is', msg.id);
      this.socketSessionId = msg.id;
    });

    socket.on('disconnect', (reason) => {
      console.log('Socket disconnected');
    });

    socket.on('reconnect', (attemptNumber) => {
      console.log('Socket reconnected');
    });

    socket.on('favorite.created', (msg) => {
      if (msg.socketid != this.socketSessionId) {
        console.log(msg);
        this.renderFavorite(msg, 'top');
      }
    });

    socket.on('favorite.updated', (msg) => {
      if (msg.socketid != this.socketSessionId) {
        this.renderFavorite(msg, document
          .getElementById('favorite_' + msg._id));
      }
      console.log(msg);
    });

    socket.on('favorite.deleted', (msg) => {
      if (msg.socketid != this.socketSessionId) {
        document.getElementById('favorite_' + msg._id).remove();
      }
    });
  }

  constructor(conf) {
    if (!(typeof conf === 'object' && conf)) {
      throw new Error('Argument conf different from specification');
    }

    this.canvas = document.getElementById(conf.canvas);
    if (!this.canvas || this.canvas.tagName !== 'CANVAS') {
      throw new Error(`canvas is not a canvas`);
    }

    this.ctx = this.canvas.getContext('2d');
    this.favourites = document.querySelector('div#favourites');

    this.initSocket();
    this.getFavorites(false);

    const nameFilter = document.getElementById('name-filter');
    const bookmarkFilter = document.getElementById('bookmarked-filter');

    const onSearch = () => {
      const name = nameFilter.value;
      const bookmark = bookmarkFilter.checked;

      this.getFavorites(name, bookmark);
    }

    nameFilter.onkeyup = onSearch;
    bookmarkFilter.onclick = onSearch;

    if (typeof conf.buttons === 'object' && conf.buttons) {
      this.buttons = {}
      for (const b of ['clear', 'undo', 'camera'])
        this.buttons[b] = document.getElementById(conf.buttons[b]);

      if (this.buttons.clear) {
        this.buttons.clear.addEventListener('click', () => {
          this.erase();
          this.background = null;
          history.clear();
        });
      }

      if (this.buttons.undo) {
        this.buttons.undo.addEventListener('click', () => {
          history.pop();
          this.redrawAll();
        });
      }

      if (this.buttons.camera) {
        this.buttons.camera.addEventListener('click', () => {
          const base64 = this.canvas.toDataURL();

          const data = {
            dataURL: base64,
            name: 'My New Masterpiece',
            bookmarked: false
          };

          doJSONRequest('POST', '/favorites?socketid=' + this.socketSessionId,
            {}, data)
            .then(data => this.renderFavorite(data, 'top'))
            .catch(console.error);
        });
      }

      const player = document.createElement('video');
      player.addEventListener('loadeddata', () => {
        player.play();
        setTimeout(() => {
          const imgCanvas = document.createElement('canvas');
          imgCanvas.width = this.canvas.width;
          imgCanvas.height = this.canvas.height;
          const img = document.createElement('img');
          imgCanvas.getContext('2d').drawImage(player, 0, 0, imgCanvas.width,
            imgCanvas.height);

          console.log(imgCanvas.toDataURL());

          img.src = imgCanvas.toDataURL();
          img.addEventListener('load', () => {
            this.background = img;
            this.redrawAll();
          });

          player.srcObject.getVideoTracks().forEach(track => track.stop());
        }, 100);
      });

      const button = document.createElement('button');
      button.type = 'button';
      button.innerHTML = 'Photo';
      button.addEventListener('click', () => {
        navigator.mediaDevices.getUserMedia({ video: true })
          .then((stream) => {
            player.srcObject = stream;
          });
      });

      document.getElementById('left-toolbar').appendChild(button);
    }

    this.ctx.lineWidth = 1;
    this.strokeStyle = this.constructor.defaultStrokeStyle;
    this.brush = 'PenBrush';

    const brushToolbar = document.querySelector('#brush-toolbar');
    if (brushToolbar) {
      for (const name in App.BRUSHES) {
        const b = document.createElement('button');
        b.innerText = name;
        b.addEventListener('click', () => this.brush = name);
        brushToolbar.appendChild(b);
      }

      const label = document.createElement('label');
      label.setAttribute('for', 'color');
      const color = document.createElement('input');
      color.type = 'color';
      color.name = 'color';
      color.value = this.constructor.defaultStrokeStyle;
      color.addEventListener('change', () => this.strokeStyle = color.value);
      brushToolbar.appendChild(label);
      brushToolbar.appendChild(color);
    }

    const toMouse = (e, func) => {
      if (e && e.touches && e.touches[0]) {
        return func.bind(this)({
          offsetX: e.touches[0].pageX - this.canvas.offsetLeft,
          offsetY: e.touches[0].pageY - this.canvas.offsetTop
        });
      }
    };

    this.canvas.addEventListener('touchstart', e => toMouse(e, this.startPath));
    this.canvas.addEventListener('mousedown', this.startPath.bind(this));
    this.canvas.addEventListener('touchmove', e => toMouse(e, this.draw));
    this.canvas.addEventListener('mousemove', this.draw.bind(this));
    this.canvas.addEventListener('touchcancel', e => toMouse(e, this.endPath));
    this.canvas.addEventListener('mouseup', this.endPath.bind(this));
    this.canvas.addEventListener('mouseout', this.endPath.bind(this));
  }

  static get defaultStrokeStyle() {
    return 'black';
  }

  get strokeStyle() {
    if (this.ctx.strokeStyle == '#000000') {
      return 'black';
    }
    return this.ctx.strokeStyle;
  }

  set strokeStyle(style) {
    if (typeof style !== 'string') {
      throw new Error('style is not a string');
    }
    this.ctx.strokeStyle = style;
  }

  get brush() {
    return this._brush.name;
  }

  set brush(brushName) {
    this._brush = App.BRUSHES[brushName];
  }

  erase() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  }

  startPath(e, record = true) {
    this.ctx.beginPath();
    this.ctx.moveTo(e.offsetX, e.offsetY);

    if (record) {
      history.initializeNewPath();
      history.push(new Stroke(this.brush, this.strokeStyle,
        e.offsetX, e.offsetY));
    }

    if (e instanceof MouseEvent) {
      this.mousedown = true;
    }
  }

  draw(e, beginNew = true, record = true) {
    if (this.mousedown || !(e instanceof MouseEvent)) {
      this._brush.draw(this.ctx, this.strokeStyle, e.offsetX, e.offsetY);

      if (record) {
        history.push(new Stroke(this.brush, this.strokeStyle,
          e.offsetX, e.offsetY));
      }

      if (beginNew) {
        this.ctx.beginPath();
        this.ctx.moveTo(e.offsetX, e.offsetY);
      } else if (e instanceof MouseEvent) {
        this.mousedown = false;
      }
    }
  }

  endPath(e, record = true) {
    this.draw(e, false, record);
  }

  drawPath(path) {
    const last = path.length - 1;
    const lastBrush = this.brush;
    const lastStyle = this.strokeStyle;
    for (let i = 0; i <= last; i++) {
      this.brush = path[i].brushName;
      this.strokeStyle = path[i].strokeStyle;
      switch(i) {
        case 0:    this.startPath(path[i], false); break;
        case last: this.endPath(path[i], false); break;
        default:   this.draw(path[i], true, false);
      }
    }
    this.brush = lastBrush;
    this.strokeStyle = lastStyle;
  }

  redrawAll() {
    this.erase();
    if (this.background) {
      this.ctx.drawImage(this.background, 0, 0, this.canvas.width,
        this.canvas.height);
    }
    for (const path of history.paths) {
      this.drawPath(path);
    }
  }
}
