import 'dart:typed_data';

import 'pvrtc_bit_utility.dart';
import 'pvrtc_color.dart';

/**
 * Ported from Jeffrey Lim's PVRTC encoder/decoder,
 * https://bitbucket.org/jthlim/pvrtccompressor
 */
class PvrtcPacket {
  Uint32List rawData;
  int index;

  PvrtcPacket(TypedData data)
      : rawData = new Uint32List.view(data.buffer);

  void setBlock(int x, int y) => setIndex(_getMortonNumber(x, y));

  void setIndex(int i) {
    // A PvrtcPacket uses 2 uint32 values, so get the physical index
    // from the logical index by multiplying by 2.
    index = i << 1;
    // Pull in the values from the raw data.
    _update();
  }

  int get modulationData => rawData[index];

  set modulationData(int x) => rawData[index] = x;

  int get colorData => rawData[index + 1];

  set colorData(int x) => rawData[index + 1] = x;

  int get usePunchthroughAlpha => _usePunchthroughAlpha;

  set usePunchthroughAlpha(int x) {
    _usePunchthroughAlpha = x;
    colorData = _getColorData();
  }

  int get colorA => _colorA;

  set colorA(int x) {
    _colorA = x;
    colorData = _getColorData();
  }

  int get colorAIsOpaque => _colorAIsOpaque;

  set colorAIsOpaque(int x) {
    _colorAIsOpaque = x;
    colorData = _getColorData();
  }

  int get colorB => _colorB;

  set colorB(int x) {
    _colorB = x;
    colorData = _getColorData();
  }

  int get colorBIsOpaque => _colorBIsOpaque;

  set colorBIsOpaque(int x) {
    _colorBIsOpaque = x;
    colorData = _getColorData();
  }

  void setColorRgbA(PvrtcColorRgb c) {
    int r = BitUtility.BITSCALE_8_TO_5_FLOOR[c.r];
    int g = BitUtility.BITSCALE_8_TO_5_FLOOR[c.g];
    int b = BitUtility.BITSCALE_8_TO_4_FLOOR[c.b];
    colorA = r << 9 | g << 4 | b;
    colorAIsOpaque = 1;
  }

  void setColorRgbaA(PvrtcColorRgba c) {
    int a = BitUtility.BITSCALE_8_TO_3_FLOOR[c.a];
    if (a == 7) {
      int r = BitUtility.BITSCALE_8_TO_5_FLOOR[c.r];
      int g = BitUtility.BITSCALE_8_TO_5_FLOOR[c.g];
      int b = BitUtility.BITSCALE_8_TO_4_FLOOR[c.b];
      colorA = r << 9 | g << 4 | b;
      colorAIsOpaque = 1;
    } else {
      int r = BitUtility.BITSCALE_8_TO_4_FLOOR[c.r];
      int g = BitUtility.BITSCALE_8_TO_4_FLOOR[c.g];
      int b = BitUtility.BITSCALE_8_TO_3_FLOOR[c.b];
      colorA = a << 11 | r << 7 | g << 3 | b;
      colorAIsOpaque = 0;
    }
  }

  void setColorRgbB(PvrtcColorRgb c) {
    int r = BitUtility.BITSCALE_8_TO_5_CEIL[c.r];
    int g = BitUtility.BITSCALE_8_TO_5_CEIL[c.g];
    int b = BitUtility.BITSCALE_8_TO_5_CEIL[c.b];
    colorB = r << 10 | g << 5 | b;
    colorBIsOpaque = 1;
  }

  void setColorRgbaB(PvrtcColorRgba c) {
    int a = BitUtility.BITSCALE_8_TO_3_CEIL[c.a];
    if (a == 7) {
      int r = BitUtility.BITSCALE_8_TO_5_CEIL[c.r];
      int g = BitUtility.BITSCALE_8_TO_5_CEIL[c.g];
      int b = BitUtility.BITSCALE_8_TO_5_CEIL[c.b];
      colorB = r << 10 | g << 5 | b;
      colorBIsOpaque = 1;
    } else {
      int r = BitUtility.BITSCALE_8_TO_4_CEIL[c.r];
      int g = BitUtility.BITSCALE_8_TO_4_CEIL[c.g];
      int b = BitUtility.BITSCALE_8_TO_4_CEIL[c.b];
      colorB = a << 12 | r << 8 | g << 4 | b;
      colorBIsOpaque = 0;
    }
  }

  PvrtcColorRgb getColorRgbA() {
    if (colorAIsOpaque != 0) {
      var r = colorA >> 9;
      var g = colorA >> 4 & 0x1f;
      var b = colorA & 0xf;
      return new PvrtcColorRgb(BitUtility.BITSCALE_5_TO_8[r],
          BitUtility.BITSCALE_5_TO_8[g],
          BitUtility.BITSCALE_4_TO_8[b]);
    } else {
      var r = (colorA >> 7) & 0xf;
      var g = (colorA >> 3) & 0xf;
      var b = colorA & 7;
      return new PvrtcColorRgb(BitUtility.BITSCALE_4_TO_8[r],
          BitUtility.BITSCALE_4_TO_8[g],
          BitUtility.BITSCALE_3_TO_8[b]);
    }
  }

  PvrtcColorRgba getColorRgbaA() {
    if (colorAIsOpaque != 0) {
      var r = colorA >> 9;
      var g = colorA >> 4 & 0x1f;
      var b = colorA & 0xf;
      return new PvrtcColorRgba(BitUtility.BITSCALE_5_TO_8[r],
          BitUtility.BITSCALE_5_TO_8[g],
          BitUtility.BITSCALE_4_TO_8[b],
          255);
    } else {
      var a = colorA >> 11 & 7;
      var r = (colorA >> 7) & 0xf;
      var g = (colorA >> 3) & 0xf;
      var b = colorA & 7;
      return new PvrtcColorRgba(BitUtility.BITSCALE_4_TO_8[r],
          BitUtility.BITSCALE_4_TO_8[g],
          BitUtility.BITSCALE_3_TO_8[b],
          BitUtility.BITSCALE_3_TO_8[a]);
    }
  }

  PvrtcColorRgb getColorRgbB() {
    if (colorBIsOpaque != 0) {
      var r = colorB >> 10;
      var g = colorB >> 5 & 0x1f;
      var b = colorB & 0x1f;
      return new PvrtcColorRgb(BitUtility.BITSCALE_5_TO_8[r],
          BitUtility.BITSCALE_5_TO_8[g],
          BitUtility.BITSCALE_5_TO_8[b]);
    } else {
      var r = colorB >> 8 & 0xf;
      var g = colorB >> 4 & 0xf;
      var b = colorB & 0xf;
      return new PvrtcColorRgb(BitUtility.BITSCALE_4_TO_8[r],
          BitUtility.BITSCALE_4_TO_8[g],
          BitUtility.BITSCALE_4_TO_8[b]);
    }
  }

  PvrtcColorRgba getColorRgbaB() {
    if (colorBIsOpaque != 0) {
      var r = colorB >> 10;
      var g = colorB >> 5 & 0x1f;
      var b = colorB & 0x1f;
      return new PvrtcColorRgba(BitUtility.BITSCALE_5_TO_8[r],
          BitUtility.BITSCALE_5_TO_8[g],
          BitUtility.BITSCALE_5_TO_8[b],
          255);
    } else {
      var a = colorB >> 12 & 7;
      var r = colorB >> 8 & 0xf;
      var g = colorB >> 4 & 0xf;
      var b = colorB & 0xf;
      return new PvrtcColorRgba(BitUtility.BITSCALE_4_TO_8[r],
          BitUtility.BITSCALE_4_TO_8[g],
          BitUtility.BITSCALE_4_TO_8[b],
          BitUtility.BITSCALE_3_TO_8[a]);
    }
  }

  int _usePunchthroughAlpha = 0;
  int _colorA = 0;
  int _colorAIsOpaque = 0;
  int _colorB = 0;
  int _colorBIsOpaque = 0;

  int _getColorData() =>
      ((usePunchthroughAlpha & 1)) |
      ((colorA & BITS_14) << 1) |
      ((colorAIsOpaque & 1) << 15) |
      ((colorB & BITS_15) << 16) |
      ((colorBIsOpaque & 1) << 31);

  void _update() {
    int x = colorData;
    usePunchthroughAlpha = x & 1;
    colorA = (x >> 1) & BITS_14;
    colorAIsOpaque = (x >> 15) & 1;
    colorB = (x >> 16) & BITS_15;
    colorBIsOpaque = (x >> 31) & 1;
  }

  static int _getMortonNumber(int x, int y) {
    return MORTON_TABLE[x >> 8] << 17 | MORTON_TABLE[y >> 8] << 16 |
           MORTON_TABLE[x & 0xFF] << 1 | MORTON_TABLE[y & 0xFF];
  }

  static const BITS_14 = (1 << 14) - 1;
  static const BITS_15 = (1 << 15) - 1;

  static const BILINEAR_FACTORS = const [
    const [ 4, 4, 4, 4 ],
    const [ 2, 6, 2, 6 ],
    const [ 8, 0, 8, 0 ],
    const [ 6, 2, 6, 2 ],

    const [ 2, 2, 6, 6 ],
    const [ 1, 3, 3, 9 ],
    const [ 4, 0, 12, 0 ],
    const [ 3, 1, 9, 3 ],

    const [ 8, 8, 0, 0 ],
    const [ 4, 12, 0, 0 ],
    const [ 16, 0, 0, 0 ],
    const [ 12, 4, 0, 0 ],

    const [ 6, 6, 2, 2 ],
    const [ 3, 9, 1, 3 ],
    const [ 12, 0, 4, 0 ],
    const [ 9, 3, 3, 1 ],
  ];

  // Weights are { colorA, colorB, alphaA, alphaB }
  static const WEIGHTS = const [
    // Weights for Mode=0
    const [ 8, 0, 8, 0 ],
    const [ 5, 3, 5, 3 ],
    const [ 3, 5, 3, 5 ],
    const [ 0, 8, 0, 8 ],

    // Weights for Mode=1
    const [ 8, 0, 8, 0 ],
    const [ 4, 4, 4, 4 ],
    const [ 4, 4, 0, 0 ],
    const [ 0, 8, 0, 8 ],
  ];

  static const MORTON_TABLE = const [
        0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015,
        0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055,
        0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115,
        0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155,
        0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415,
        0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455,
        0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515,
        0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555,
        0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015,
        0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055,
        0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115,
        0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155,
        0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415,
        0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455,
        0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515,
        0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555,
        0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015,
        0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055,
        0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115,
        0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155,
        0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415,
        0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455,
        0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515,
        0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555,
        0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015,
        0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055,
        0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115,
        0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155,
        0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415,
        0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455,
        0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515,
        0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555
    ];
}
