/*
  termap - Terminal Map Viewer
  by Michael Strassburger <codepoet@cpan.org>

  Simple pixel to braille character mapper

  Implementation inspired by node-drawille (https://github.com/madbence/node-drawille)
  * added color support
  * added text label support
  * general optimizations

  Will either be merged into node-drawille or become an own module at some point
*/
'use strict';
const stringWidth = require('string-width');
const config = require('./config');
const utils = require('./utils');

const asciiMap = {
  // '▬': [2+32, 4+64],
  // '¯': [1+16],
  '▀': [1+2+16+32],
  '▄': [4+8+64+128],
  '■': [2+4+32+64],
  '▌': [1+2+4+8],
  '▐': [16+32+64+128],
  // '▓': [1+4+32+128, 2+8+16+64],
  '█': [255],
};
const termReset = '\x1B[39;49m';

class BrailleBuffer {
  constructor(width, height) {
    this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]];

    this.pixelBuffer = null;
    this.charBuffer = null;
    this.foregroundBuffer = null;
    this.backgroundBuffer = null;

    this.asciiToBraille = [];

    this.globalBackground = null;

    this.width = width;
    this.height = height;

    const size = width*height/8;
    this.pixelBuffer = new Buffer(size);
    this.foregroundBuffer = new Buffer(size);
    this.backgroundBuffer = new Buffer(size);

    this._mapBraille();
    this.clear();
  }

  clear() {
    this.pixelBuffer.fill(0);
    this.charBuffer = [];
    this.foregroundBuffer.fill(0);
    this.backgroundBuffer.fill(0);
  }

  setGlobalBackground(background) {
    this.globalBackground = background;
  }

  setBackground(x, y, color) {
    if (0 <= x && x < this.width && 0 <= y && y < this.height) {
      const idx = this._project(x, y);
      this.backgroundBuffer[idx] = color;
    }
  }

  setPixel(x, y, color) {
    this._locate(x, y, (idx, mask) => {
      this.pixelBuffer[idx] |= mask;
      this.foregroundBuffer[idx] = color;
    });
  }

  unsetPixel(x, y) {
    this._locate(x, y, (idx, mask) => {
      this.pixelBuffer[idx] &= ~mask;
    });
  }

  _project(x, y) {
    return (x>>1) + (this.width>>1)*(y>>2);
  }

  _locate(x, y, cb) {
    if (!((0 <= x && x < this.width) && (0 <= y && y < this.height))) {
      return;
    }
    const idx = this._project(x, y);
    const mask = this.brailleMap[y & 3][x & 1];
    return cb(idx, mask);
  }

  _mapBraille() {
    this.asciiToBraille = [' '];

    const masks = [];
    for (const char in asciiMap) {
      const bits = asciiMap[char];
      if (!(bits instanceof Array)) continue;
      for (const mask of bits) {
        masks.push({
          mask: mask,
          char: char,
        });
      }
    }

    //TODO Optimize this part
    var i, k;
    const results = [];
    for (i = k = 1; k <= 255; i = ++k) {
      const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128);
      results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) {
        const covered = utils.population(mask.mask & braille);
        if (!best || best.covered < covered) {
          return {
            char: mask.char,
            covered: covered,
          };
        } else {
          return best;
        }
      }), void 0).char);
    }
    return results;
  }

  _termColor(foreground, background) {
    background |= this.globalBackground;
    if (foreground && background) {
      return `\x1B[38;5;${foreground};48;5;${background}m`;
    } else if (foreground) {
      return `\x1B[49;38;5;${foreground}m`;
    } else if (background) {
      return `\x1B[39;48;5;${background}m`;
    } else {
      return termReset;
    }
  }

  frame() {
    const output = [];
    let currentColor = null;
    let skip = 0;

    for (let y = 0; y < this.height/4; y++) {
      skip = 0;

      for (let x = 0; x < this.width/2; x++) {
        const idx = y*this.width/2 + x;

        if (idx && !x) {
          output.push(config.delimeter);
        }

        const colorCode = this._termColor(this.foregroundBuffer[idx], this.backgroundBuffer[idx]);
        if (currentColor !== colorCode) {
          output.push(currentColor = colorCode);
        }

        const char = this.charBuffer[idx];
        if (char) {
          skip += stringWidth(char)-1;
          if (skip+x < this.width/2) {
            output.push(char);
          }
        } else {
          if (!skip) {
            if (config.useBraille) {
              output.push(String.fromCharCode(0x2800+this.pixelBuffer[idx]));
            } else {
              output.push(this.asciiToBraille[this.pixelBuffer[idx]]);
            }
          } else {
            skip--;
          }
        }
      }
    }

    output.push(termReset+config.delimeter);
    return output.join('');
  }

  setChar(char, x, y, color) {
    if (0 <= x && x < this.width && 0 <= y && y < this.height) {
      const idx = this._project(x, y);
      this.charBuffer[idx] = char;
      this.foregroundBuffer[idx] = color;
    }
  }

  writeText(text, x, y, color, center = true) {
    if (center) {
      x -= text.length/2+1;
    }
    for (let i = 0; i < text.length; i++) {
      this.setChar(text.charAt(i), x+i*2, y, color);
    }
  }
}

module.exports = BrailleBuffer;

@@ -1169,35 +1169,37 @@
 ixelBuffer =
- new
  Buffer
+.alloc
 (size);%0A    
@@ -1217,35 +1217,37 @@
 oundBuffer =
- new
  Buffer
+.alloc
 (size);%0A    
@@ -1273,19 +1273,21 @@
 er =
- new
  Buffer
+.alloc
 (siz

Replace deprecated new Buffer(size) with Buffer.alloc()