import Utils from "/app/js/common/Utils.js";
import NESPalette from "/app/js/models/NESPalette.js";


function BitMask(offset, inv){
  switch(offset){
    case 0:
      return parseInt((inv === true) ? '01111111' : '10000000', 2);
    case 1:
      return parseInt((inv === true) ? '10111111' : '01000000', 2);
    case 2:
      return parseInt((inv === true) ? '11011111' : '00100000', 2);
    case 3:
      return parseInt((inv === true) ? '11101111' : '00010000', 2);
    case 4:
      return parseInt((inv === true) ? '11110111' : '00001000', 2);
    case 5:
      return parseInt((inv === true) ? '11111011' : '00000100', 2);
    case 6:
      return parseInt((inv === true) ? '11111101' : '00000010', 2);
  }
  return parseInt((inv === true) ? '11111110' : '00000001', 2);
}



export default class NESTile{
  constructor(){
    this.__paletteIndex = 0;
    this.__data = new Uint8Array(16);
  }

  get width(){return 8;}
  get height(){return 8;}

  get pixels(){
    return new Proxy(this, {
      get: function(obj, prop){
        if (prop === "length")
          return 64;
        if (!Utils.isInt(prop))
          throw new TypeError("Expected integer index.");
        if (prop < 0 || prop >= 64)
          throw new RangeError("Index out of bounds.");
        var dindex = Math.floor(prop*0.125);
        var bitoffset = 7 - (prop%8);
        var v = (obj.__data[dindex] & (1 << bitoffset)) >> bitoffset;
        v += 2*((obj.__data[8+dindex] & (1 << bitoffset)) >> bitoffset);
        return v;
      },


      set: function(obj, prop, value){
        if (!Utils.isInt(prop))
          throw new TypeError("Expected integer index.");
        prop = parseInt(prop);
        if (!Utils.isInt(value))
          throw new TypeError("Color index expected to be integer.");
        if (prop < 0 || prop >= 64)
          throw new RangeError("Index out of bounds.");
        if (value < 0 || value >= 4)
          throw new RangeError("Color index out of bounds.");
        var dindex = Math.floor(prop*0.125);
        var bitoffset = (prop % 8);
        if (value == 1 || value == 3){
          obj.__data[dindex] |= BitMask(bitoffset);
        } else {
          obj.__data[dindex] &= BitMask(bitoffset, true);
        }
        if (value == 2 || value == 3){
          obj.__data[8+dindex] |= BitMask(bitoffset);
        } else {
          obj.__data[8+dindex] &= BitMask(bitoffset, true);
        }
        return true;
      }
    });
  }

  get dataArray(){
    var d = [];
    for (var y = 0; y < 8; y++){
      for (var x = 0; x < 8; x++){
        d.push(this.getPixelIndex(x, y));
      }
    }
    return d;
  }

  get chr(){
    return new Uint8Array(this.__data);
  }

  get base64(){
    var b = ""
    for (var i = 0; i < this.__data.length; i++) {
      b += String.fromCharCode(this.__data[i]);
    }
    b += String.fromCharCode(this.__paletteIndex);
    return window.btoa(b);
  }
  set base64(s){
    var b =  window.atob(s);
    var len = b.length;
    if (b.length !== 17){
      throw new Error("Base64 string contains invalid byte count.");
    }
    var bytes = new Uint8Array(b.length-1);
    for (var i=0; i < b.length-1; i++){
      bytes[i] = b.charCodeAt(i);
    }
    this.__data = bytes;
    this.__paletteIndex = b.charCodeAt(b.length-1);
  }


  get paletteIndex(){return this.__paletteIndex;}
  set paletteIndex(pi){
    if (!Utils.isInt(pi))
      throw new TypeError("Palette index expected to be an integer.");
    if (pi < 0 || pi >= 4){
      throw new RangeError("Palette index out of bounds.");
    }
    this.__paletteIndex = pi;
  }

  setPixelIndex(x, y, ci){
    if (x < 0 || x >= 8 || y < 0 || y >= 8){
      throw new ValueError("Coordinates out of bounds.");
    }
    if (ci < 0 || ci >= 4){
      throw new ValueError("Color index out of bounds.");
    }
    this.pixels[(y*8)+x] = ci;
    return this;
  }

  getPixelIndex(x, y){
    if (x < 0 || x >= 8 || y < 0 || y >= 8){
      throw new ValueError("Coordinates out of bounds.");
    }
    return this.pixels[(8*y) + x];
  }

  flip(flag){
    if (flag >= 1 && flag <= 3){
      var oldData = this.__data;
      var newData = new Uint8Array(16);
      for (var x = 0; x < 8; x++){
        for (var y = 0; y < 8; y++){
          this.__data = oldData;
          var ci = this.getPixelIndex(x, y);
          this.__data = newData;
          this.setPixelIndex(
              (flag == 1 || flag == 3) ? 7 - x : x,
              (flag == 2 || flag == 3) ? 7 - y : y,
              ci
          );
        }
      }
    }
    return this;
  }


  clone(){
    return (new NESTile()).copy(this);
  }

  copy(t){
    if (!(t instanceof NESTile))
      throw new TypeError("Expected NESTile object.");
    this.__data.set(t.__data);
    return this;
  }

  isEq(tile){
    if (!(tile instanceof NESTile)){
      throw new TypeError("Expected NESTile instance.");
    }
    var b64 = this.base64;
    if (tile.base64 === b64){
      return 0;
    }
    var tc = tile.clone().flip(1); // Flip horizontal.
    if (tc.base64 === b64){
      return 1;
    }

    tc.flip(3); // Flip horizontal AND verticle. Net effect is the same as tile.clone().flip(2) ... Flip Verticle
    if (tc.base64 === b64){
      return 2;
    }

    tc.flip(1); // Flip horizontal again. Net effect is the same as tile.clone().flip(3) ... flip H & V
    if (tc.base64 === b64){
      return 3;
    }
    return -1;
  }
}