const PrinterType = require('./printer-type');

class Custom extends PrinterType {
  constructor () {
    super();
    this.config = require('./custom-config');
  }

  // ------------------------------ Get paper status ------------------------------
  getStatus () {
    // https://www.epson-biz.com/modules/ref_escpos/index.php?content_id=124#gs_lr
    return this.config.TRANSMIT_PAPER_STATUS;
  }

  // ------------------------------ Append ------------------------------
  append (appendBuffer) {
    if (this.buffer) {
      this.buffer = Buffer.concat([this.buffer, appendBuffer]);
    } else {
      this.buffer = appendBuffer;
    }
  }

  // ------------------------------ Beep ------------------------------
  // "numberOfBeeps" is the number of beeps from 1 to 9
  // "lengthOfTheSound" is the length of the sound from 1 to 9 (it's not in seconds, it's just the preset value)
  beep (numberOfBeeps = 1, lengthOfTheSound = 1) {
    if (numberOfBeeps < 1 || numberOfBeeps > 9) throw new Error('numberOfBeeps: Value must be between 1 and 9');
    if (lengthOfTheSound < 1 || lengthOfTheSound > 9) throw new Error('lengthOfTheSound: Value must be between 1 and 9');
    this.buffer = null;
    this.append(this.config.BEEP);
    this.append(Buffer.from([numberOfBeeps, lengthOfTheSound]));
    return this.buffer;
  }

  // ------------------------------ Set text size ------------------------------
  setTextSize (height, width) {
    this.buffer = null;
    if (height > 7 || height < 0) throw new Error('setTextSize: Height must be between 0 and 7');
    if (width > 7 || width < 0) throw new Error('setTextSize: Width must be between 0 and 7');
    const x = Buffer.from(`${height}${width}`, 'hex');
    this.append(Buffer.from([0x1D, 0x21]));
    this.append(x);
    return this.buffer;
  }

  // ------------------------------ CODE 128 ------------------------------
  code128 (data, settings) {
    this.buffer = null;
    settings = {
      hriPos: 0,
      hriFont: 0,
      width: 3,
      height: 162,
      ...settings
    };

    // Set HRI characters Position, 0-3 (none, top, bottom, top/bottom), default 0 none
    this.append(Buffer.from([0x1d, 0x48])); // GS H
    this.append(Buffer.from([settings.hriPos]));

    // Set HRI character font. 0-4, 48-52, 97, 98 (depending on printer, 0 and 1 available on all), default 0
    this.append(Buffer.from([0x1d, 0x66])); // GS f
    this.append(Buffer.from([settings.hriFont]));

    // Set width 2-6, default 3
    this.append(Buffer.from([0x1d, 0x77])); // GS W
    this.append(Buffer.from([settings.width]));

    // Set height 1 - 255 default 162
    this.append(Buffer.from([0x1d, 0x68])); // GS h
    this.append(Buffer.from([settings.height]));

    // Print Barcode
    this.append(this.config.BARCODE_CODE128);
    this.append(Buffer.from([data.length + 2]));
    this.append(Buffer.from([0x7b, 0x42]));

    // Data
    this.append(Buffer.from(data));
    return this.buffer;
  }

  // ------------------------------ QR ------------------------------
  printQR (str, settings) {
    this.buffer = null;
    settings = {
      model: 2,
      cellSize: 3,
      correction: 'M',
      ...settings
    };

    // [Name] Select the QR code model
    // [Code] 1D 28 6B 04 00 31 41 n1 n2
    // n1
    // [49 x31, model 1]
    // [50 x32, model 2]
    // [51 x33, micro qr code]
    // n2 = 0
    // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=140
    if (settings.model === 1) this.append(this.config.QRCODE_MODEL1);
    else if (settings.model === 3) this.append(this.config.QRCODE_MODEL3);
    else this.append(this.config.QRCODE_MODEL2);

    // [Name]: Set the size of module
    // 1D 28 6B 03 00 31 43 n
    // n depends on the printer
    // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=141
    const size = 'QRCODE_CELLSIZE_'.concat(settings.cellSize.toString());
    this.append(this.config[size]);

    // [Name] Select the error correction level
    // 1D 28 6B 03 00 31 45 n
    // n
    // [48 x30 -> 7%]
    // [49 x31-> 15%]
    // [50 x32 -> 25%]
    // [51 x33 -> 30%]
    // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=142
    const correction = 'QRCODE_CORRECTION_'.concat(settings.correction.toUpperCase());
    this.append(this.config[correction]);

    // [Name] Store the data in the symbol storage area
    // 1D 28  6B pL pH 31 50 30 d1...dk
    // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=143
    const s = str.length + 3;
    const lsb = parseInt(s % 256);
    const msb = parseInt(s / 256);
    this.append(Buffer.from([0x1d, 0x28, 0x6b, lsb, msb, 0x31, 0x50, 0x30]));
    this.append(Buffer.from(str));

    // [Name] Print the symbol data in the symbol storage area
    // 1D 28 6B 03 00 31 51 m
    // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=144
    this.append(this.config.QRCODE_PRINT);

    return this.buffer;
  }

  // ------------------------------ PDF417 ------------------------------
  pdf417 (data, settings) {
    this.buffer = null;
    settings = {
      correction: 1,
      rowHeight: 3,
      width: 3,
      columns: 0,
      truncated: false,
      ...settings
    };

    // Set error correction ratio 1 - 40, default 1
    this.append(this.config.PDF417_CORRECTION);
    this.append(Buffer.from([settings.correction]));

    // Set row height 2 - 8, default 3
    this.append(this.config.PDF417_ROW_HEIGHT);
    this.append(Buffer.from([settings.rowHeight]));

    // Set width of module 2 - 8, default 3
    this.append(this.config.PDF417_WIDTH);
    this.append(Buffer.from([settings.width]));

    // Manually set columns 1 - 30, default auto
    this.append(this.config.PDF417_COLUMNS);
    this.append(Buffer.from([settings.columns]));

    // Standard or truncated option
    if (settings.truncated) this.append(this.config.PDF417_OPTION_TRUNCATED);
    else this.append(this.config.PDF417_OPTION_STANDARD);

    // Set PDF417 bar code data
    const s = data.length + 3;
    const lsb = parseInt(s % 256);
    const msb = parseInt(s / 256);

    this.append(Buffer.from([0x1d, 0x28, 0x6b, lsb, msb, 0x30, 0x50, 0x30]));
    this.append(Buffer.from(data.toString()));

    // Print barcode
    this.append(Buffer.from(this.config.PDF417_PRINT));

    return this.buffer;
  }

  // ------------------------------ MAXI CODE ------------------------------
  maxiCode (data, settings) {
    this.buffer = null;
    settings = {
      mode: 4,
      ...settings
    };

    // Maxi Mode
    // 2 - Formatted data/structured Carrier Message with a numeric postal code. (US)
    // 3 - Formatted data/structured Carrier Message with a numeric postal code. (International)
    // 4 - Unformatted data/Standard Error Correction.
    // 5 - Unformatted data/Enhanced Error Correction.
    // 6 - Used for programming hardware devices.
    if (settings.mode == 2) this.append(this.config.MAXI_MODE2);
    else if (settings.mode == 3) this.append(this.config.MAXI_MODE3);
    else if (settings.mode == 5) this.append(this.config.MAXI_MODE5);
    else if (settings.mode == 6) this.append(this.config.MAXI_MODE6);
    else this.append(this.config.MAXI_MODE4);

    // Setup size of MaxiCode data
    const s = data.length + 3;
    const lsb = parseInt(s % 256);
    const msb = parseInt(s / 256);

    // Send Data
    this.append(Buffer.from([0x1d, 0x28, 0x6b, lsb, msb, 0x32, 0x50, 0x30]));
    this.append(Buffer.from(data.toString()));

    // Print barcode
    this.append(this.config.MAXI_PRINT);

    return this.buffer;
  }

  // ------------------------------ BARCODE ------------------------------
  printBarcode (data, type, settings) {
    this.buffer = null;
    settings = {
      hriPos: 0,
      hriFont: 0,
      width: 3,
      height: 162,
      ...settings
    };

    // Set HRI characters Position, 0-3 (none, top, bottom, top/bottom)
    this.append(Buffer.from([0x1d, 0x48])); // GS H
    this.append(Buffer.from([settings.hriPos]));

    // Set HRI character font. 0-4, 48-52, 97, 98 (depending on printer, 0 and 1 available on all), default 0
    this.append(Buffer.from([0x1d, 0x66])); // GS f
    this.append(Buffer.from([settings.hriFont]));

    // Set width 2-6, default 3
    this.append(Buffer.from([0x1d, 0x77])); // GS W
    this.append(Buffer.from([settings.width]));

    // Set height 1 - 255 default 162
    this.append(Buffer.from([0x1d, 0x68])); // GS h
    this.append(Buffer.from([settings.height]));

    // Print Barcode
    this.append(Buffer.from([0x1d, 0x6b])); // GS k

    // Select type and bit length of data
    if (type == 73) {
      this.append(Buffer.from([type, data.length + 2]));
      this.append(Buffer.from([0x7b, 0x42]));
    } else {
      this.append(Buffer.from([type, data.length]));
    }

    // Data
    this.append(Buffer.from(data));

    return this.buffer;
  }

  // ----------------------------------------------------- PRINT IMAGE -----------------------------------------------------
  // https://reference.epson-biz.com/modules/ref_escpos/index.php?content_id=88
  async printImage (image) {
    const fs = require('fs');
    const { PNG } = require('pngjs');
    try {
      const data = fs.readFileSync(image);
      const png = PNG.sync.read(data);
      const buff = this.printImageBuffer(png.width, png.height, png.data);
      return buff;
    } catch (error) {
      throw error;
    }
  }

  printImageBuffer (width, height, data) {
    this.buffer = null;
  
    const mode = 33; // 24-dot double density for best quality
  
    // Round width to next multiple of 8
    if (width % 8 !== 0) {
      width += 8 - (width % 8);
    }
  
    const nL = width % 256;
    const nH = Math.floor(width / 256);
    
    for (let i = 0; i < height; i += 24) {
      const line = [];
    
      // ESC * m nL nH
      line.push(0x1b, 0x2a, mode, nL, nH);
    
      for (let j = 0; j < width; j++) {
        for (let k = 0; k < 3; k++) {
          let byte = 0x0;
          for (let b = 0; b < 8; b++) {
            const y = i + k * 8 + b;
            if (y >= height) continue;
    
            const idx = (width * y + j) << 2;
            const r = data[idx];
            const g = data[idx + 1];
            const b_ = data[idx + 2];
            const a = data[idx + 3];
    
            if (a > 126) {
              const grayscale = parseInt(0.2126 * r + 0.7152 * g + 0.0722 * b_);
              if (grayscale < 128) { // checking color
                const mask = 1 << 7 - b; // setting bitwise mask
                byte |= mask; // setting the correct bit to 1
              }
            }
          }
          line.push(byte);
        }
      }
    
      // Feed line after each 24-row block
      line.push(0x1B, 0x33, 0x00, 0x0A);
      this.append(Buffer.from(line));
    }
    this.append(Buffer.from([0x1B, 0x32]));
    return this.buffer;
  }
}

module.exports = Custom;
