/**
 * Copyright (c) 2020-2025, JGraph Holdings Ltd
 * Copyright (c) 2020-2025, draw.io AG
 */
/**
 * @author shaozilee
 *
 * Bmp format decoder,support 1bit 4bit 8bit 24bit bmp
 *
 */

function BmpDecoder(buffer,is_with_alpha) {
  this.pos = 0;
  this.buffer = buffer;
  this.is_with_alpha = !!is_with_alpha;
  //Header should be BM
  if (this.buffer[0] != 66 && this.buffer[1] != 77) throw new Error("Invalid BMP File");
  this.pos += 2;
  this.parseHeader();
  this.parseBGR();
}

BmpDecoder.prototype.parseHeader = function() {
  var b =  this.buffer;
  this.fileSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
  this.pos += 4;
  this.reserved = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
  this.pos += 4;
  this.offset = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.headerSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.width = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos];
  this.pos += 4;
  this.height = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.planes = (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 2;
  this.bitPP = (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 2;
  this.compress = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.rawSize = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.hr = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.vr = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.colors = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;
  this.importantColors = (b[this.pos+3] << 24) | (b[this.pos+2] << 16) | (b[this.pos+1] << 8) | b[this.pos]; 
  this.pos += 4;

  if(this.bitPP === 16 && this.is_with_alpha){
    this.bitPP = 15
  };
  if (this.bitPP < 15) {
    var len = this.colors === 0 ? 1 << this.bitPP : this.colors;
    this.palette = new Array(len);
    for (var i = 0; i < len; i++) {
      var blue = this.buffer[this.pos++];
      var green = this.buffer[this.pos++]; 
      var red = this.buffer[this.pos++]; 
      var quad = this.buffer[this.pos++]; 
      this.palette[i] = {
        red: red,
        green: green,
        blue: blue,
        quad: quad
      };
    }
  }

}

BmpDecoder.prototype.parseBGR = function() {
  this.pos = this.offset;
  try {
    var bitn = "bit" + this.bitPP;
    
    var canvas = document.createElement("canvas");
    var ctx = canvas.getContext("2d");
	var imageData = ctx.createImageData(this.width, this.height);
	this.imageData = imageData;
    this.data = imageData.data;

    this[bitn]();
  } catch (e) {
    console.log("bit decode error:" + e);
  }

};

BmpDecoder.prototype.bit1 = function() {
  var xlen = Math.ceil(this.width / 8);
  var mode = xlen%4;
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < xlen; x++) {
      var b = this.buffer[this.pos++];
      var location = y * this.width * 4 + x*8*4;
      for (var i = 0; i < 8; i++) {
        if(x*8+i<this.width){
          var rgb = this.palette[((b>>(7-i))&0x1)];
          this.data[location+i*4] = rgb.red;
          this.data[location+i*4 + 1] = rgb.green;
          this.data[location+i*4 + 2] = rgb.blue;
          this.data[location+i*4 + 3] = 0xFF;
        }else{
          break;
        }
      }
    }

    if (mode != 0){
      this.pos+=(4 - mode);
    }
  }
};

BmpDecoder.prototype.bit4 = function() {
  var xlen = Math.ceil(this.width/2);
  var mode = xlen%4;
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < xlen; x++) {
      var b = this.buffer[this.pos++];//this.buffer.readUInt8(this.pos++);
      var location = y * this.width * 4 + x*2*4;

      var before = b>>4;
      var after = b&0x0F;

      var rgb = this.palette[before];
      this.data[location] = rgb.red;
      this.data[location + 1] = rgb.green;
      this.data[location + 2] = rgb.blue;
      this.data[location + 3] = 0xFF;

      if(x*2+1>=this.width)break;

      rgb = this.palette[after];
      this.data[location+4] = rgb.red;
      this.data[location+4 + 1] = rgb.green;
      this.data[location+4 + 2] = rgb.blue;
      this.data[location+4 + 3] = 0xFF;
    }

    if (mode != 0){
      this.pos+=(4 - mode);
    }
  }

};

BmpDecoder.prototype.bit8 = function() {
  var mode = this.width%4;
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < this.width; x++) {
      var b = this.buffer[this.pos++];
      var location = y * this.width * 4 + x*4;
      if(b < this.palette.length) {
        var rgb = this.palette[b];
        this.data[location] = rgb.red;
        this.data[location + 1] = rgb.green;
        this.data[location + 2] = rgb.blue;
        this.data[location + 3] = 0xFF;
      } else {
        this.data[location] = 0xFF;
        this.data[location + 1] = 0xFF;
        this.data[location + 2] = 0xFF;
        this.data[location + 3] = 0xFF;
      }
    }
    if (mode != 0){
      this.pos+=(4 - mode);
    }
  }
};

//Currently not used!
BmpDecoder.prototype.bit15 = function() {
  //FIXED BUG, padding is based on number of bytes not the width
  var dif_w = (this.width * 2) % 4; 
  if (dif_w != 0) {
	  dif_w = 4 - dif_w;
  }
  var _11111 = parseInt("11111", 2),_1_5 = _11111;
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < this.width; x++) {

      var B = (this.buffer[this.pos+1] << 8) | this.buffer[this.pos];
      this.pos+=2;
      var blue = (B & _1_5) / _1_5 * 255 | 0;
      var green = (B >> 5 & _1_5 ) / _1_5 * 255 | 0;
      var red = (B >> 10 & _1_5) / _1_5 * 255 | 0;
      var alpha = (B>>15)?0xFF:0x00;

      var location = y * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = alpha;
    }
    //skip extra bytes
    this.pos += dif_w;
  }
};

//TODO support other RGB masks, e.g., RGB565
BmpDecoder.prototype.bit16 = function() {
  //FIXED BUG, padding is based on number of bytes not the width
  var dif_w = (this.width * 2) % 4;
  if (dif_w != 0) {
	  dif_w = 4 - dif_w;
  }
  var _11111 = parseInt("11111", 2),_1_5 = _11111;
  var _111111 = parseInt("111111", 2),_1_6 = _111111;
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < this.width; x++) {

      var B = (this.buffer[this.pos+1] << 8) | this.buffer[this.pos];
      this.pos+=2;
      var alpha = 0xFF;
      var blue = (B & _1_5) / _1_5 * 255 | 0;
      var green = (B >> 5 & _1_5) / _1_5 * 255 | 0;
      var red = (B >> 10 & _1_5) / _1_5 * 255 | 0;

      var location = y * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = alpha;
    }
    //skip extra bytes
    this.pos += dif_w;
  }
};

BmpDecoder.prototype.bit24 = function() {
  //when height > 0
  //FIXED BUG, padding is based on number of bytes not the width
  var dif_w = ((this.width * 3) % 4);
  if (dif_w != 0) {
	  dif_w = 4 - dif_w;
  }
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < this.width; x++) {
      var blue = this.buffer[this.pos++];
      var green = this.buffer[this.pos++];
      var red = this.buffer[this.pos++];
      var location = y * this.width * 4 + x * 4;
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = 0xFF;
    }
    //skip extra bytes
    this.pos += dif_w;
  }

};

/**
 * add 32bit decode func 
 * @author soubok
 */
BmpDecoder.prototype.bit32 = function() {
  //when height > 0
  for (var y = this.height - 1; y >= 0; y--) {
    for (var x = 0; x < this.width; x++) {
      var blue = this.buffer[this.pos++];
      var green = this.buffer[this.pos++];
      var red = this.buffer[this.pos++];
      var alpha = this.buffer[this.pos++];
      var location = y * this.width * 4 + x * 4;
      //FIXED BUG alpha is the last byte in image data
      this.data[location] = red;
      this.data[location + 1] = green;
      this.data[location + 2] = blue;
      this.data[location + 3] = alpha;
    }
    //FIXED BUG no padding is needed for 32 bit images "the length of the rows IS a multiple of four bytes"
  }

};

BmpDecoder.prototype.getData = function() {
  return this.data;
};
