/*
 * noVNC: HTML5 VNC client
 * Copyright (C) 2019 The noVNC Authors
 * Licensed under MPL 2.0 (see LICENSE.txt)
 *
 * See README.md for usage and integration instructions.
 *
 */

import * as Log from '../util/logging.js';

export default class HextileDecoder {
  constructor() {
    this._tiles = 0;
    this._lastsubencoding = 0;
    this._tileBuffer = new Uint8Array(16 * 16 * 4);
  }

  decodeRect(x, y, width, height, sock, display, depth) {
    if (this._tiles === 0) {
      this._tilesX = Math.ceil(width / 16);
      this._tilesY = Math.ceil(height / 16);
      this._totalTiles = this._tilesX * this._tilesY;
      this._tiles = this._totalTiles;
    }

    while (this._tiles > 0) {
      let bytes = 1;

      if (sock.rQwait('HEXTILE', bytes)) {
        return false;
      }

      let rQ = sock.rQ;
      let rQi = sock.rQi;

      let subencoding = rQ[rQi]; // Peek
      if (subencoding > 30) {
        // Raw
        throw new Error('Illegal hextile subencoding (subencoding: ' + subencoding + ')');
      }

      const currTile = this._totalTiles - this._tiles;
      const tileX = currTile % this._tilesX;
      const tileY = Math.floor(currTile / this._tilesX);
      const tx = x + tileX * 16;
      const ty = y + tileY * 16;
      const tw = Math.min(16, x + width - tx);
      const th = Math.min(16, y + height - ty);

      // Figure out how much we are expecting
      if (subencoding & 0x01) {
        // Raw
        bytes += tw * th * 4;
      } else {
        if (subencoding & 0x02) {
          // Background
          bytes += 4;
        }
        if (subencoding & 0x04) {
          // Foreground
          bytes += 4;
        }
        if (subencoding & 0x08) {
          // AnySubrects
          bytes++; // Since we aren't shifting it off

          if (sock.rQwait('HEXTILE', bytes)) {
            return false;
          }

          let subrects = rQ[rQi + bytes - 1]; // Peek
          if (subencoding & 0x10) {
            // SubrectsColoured
            bytes += subrects * (4 + 2);
          } else {
            bytes += subrects * 2;
          }
        }
      }

      if (sock.rQwait('HEXTILE', bytes)) {
        return false;
      }

      // We know the encoding and have a whole tile
      rQi++;
      if (subencoding === 0) {
        if (this._lastsubencoding & 0x01) {
          // Weird: ignore blanks are RAW
          Log.Debug('     Ignoring blank after RAW');
        } else {
          display.fillRect(tx, ty, tw, th, this._background);
        }
      } else if (subencoding & 0x01) {
        // Raw
        let pixels = tw * th;
        // Max sure the image is fully opaque
        for (let i = 0; i < pixels; i++) {
          rQ[rQi + i * 4 + 3] = 255;
        }
        display.blitImage(tx, ty, tw, th, rQ, rQi);
        rQi += bytes - 1;
      } else {
        if (subencoding & 0x02) {
          // Background
          this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
          rQi += 4;
        }
        if (subencoding & 0x04) {
          // Foreground
          this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
          rQi += 4;
        }

        this._startTile(tx, ty, tw, th, this._background);
        if (subencoding & 0x08) {
          // AnySubrects
          let subrects = rQ[rQi];
          rQi++;

          for (let s = 0; s < subrects; s++) {
            let color;
            if (subencoding & 0x10) {
              // SubrectsColoured
              color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
              rQi += 4;
            } else {
              color = this._foreground;
            }
            const xy = rQ[rQi];
            rQi++;
            const sx = xy >> 4;
            const sy = xy & 0x0f;

            const wh = rQ[rQi];
            rQi++;
            const sw = (wh >> 4) + 1;
            const sh = (wh & 0x0f) + 1;

            this._subTile(sx, sy, sw, sh, color);
          }
        }
        this._finishTile(display);
      }
      sock.rQi = rQi;
      this._lastsubencoding = subencoding;
      this._tiles--;
    }

    return true;
  }

  // start updating a tile
  _startTile(x, y, width, height, color) {
    this._tileX = x;
    this._tileY = y;
    this._tileW = width;
    this._tileH = height;

    const red = color[0];
    const green = color[1];
    const blue = color[2];

    const data = this._tileBuffer;
    for (let i = 0; i < width * height * 4; i += 4) {
      data[i] = red;
      data[i + 1] = green;
      data[i + 2] = blue;
      data[i + 3] = 255;
    }
  }

  // update sub-rectangle of the current tile
  _subTile(x, y, w, h, color) {
    const red = color[0];
    const green = color[1];
    const blue = color[2];
    const xend = x + w;
    const yend = y + h;

    const data = this._tileBuffer;
    const width = this._tileW;
    for (let j = y; j < yend; j++) {
      for (let i = x; i < xend; i++) {
        const p = (i + j * width) * 4;
        data[p] = red;
        data[p + 1] = green;
        data[p + 2] = blue;
        data[p + 3] = 255;
      }
    }
  }

  // draw the current tile to the screen
  _finishTile(display) {
    display.blitImage(this._tileX, this._tileY, this._tileW, this._tileH, this._tileBuffer, 0);
  }
}
