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

  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');

    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 img = document.createElement('img');
          img.src = base64;

          const form = document.createElement('form');
          form.method = 'POST';
          form.action = '/favorites';

          const image = document.createElement('input');
          image.type = 'hidden';
          image.name = 'dataURL';
          image.value = base64;
          form.appendChild(image);

          const lblName = document.createElement('label');
          lblName.setAttribute('for', 'name');
          lblName.innerText = 'Name:';
          form.appendChild(lblName);

          const name = document.createElement('input');
          name.type = 'text';
          name.name = 'name';
          name.placeholder = 'Name';
          name.value = 'New Name';
          form.appendChild(name);

          const submit = document.createElement('button');
          submit.innerText = 'Save';
          form.appendChild(submit);

          this.favourites.appendChild(img);
          this.favourites.appendChild(form);
        });
      }

      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 imgCtx = imgCanvas.getContext('2d');
          imgCtx.drawImage(player, 0, 0, canvas.width, canvas.height);
          const imgData = imgCtx.getImageData(0, 0, imgCanvas.width,
            imgCanvas.height);

          for (let i = 0; i < data.length; i += 4) {
            const pixel = (data[i] + 2 * data[i+1] + data[i+2]) / 4;
            data[i] = data[i+1] = data[i+2] = pixel;
          }
          imgCtx.putImageData(imgData, 0, 0);

          const img = document.createElement('img');
          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);
    }
  }
}
