import Check from "./Check.js";
import RuntimeError from "./RuntimeError.js";

const compressedMagic = 0x7468dead;
const compressedMagicSwap = 0xadde6874;

/**
 * Decodes data that is received from the Google Earth Enterprise server.
 *
 * @param {ArrayBuffer} key The key used during decoding.
 * @param {ArrayBuffer} data The data to be decoded.
 *
 * @private
 */
function decodeGoogleEarthEnterpriseData(key, data) {
  if (decodeGoogleEarthEnterpriseData.passThroughDataForTesting) {
    return data;
  }

  //>>includeStart('debug', pragmas.debug);
  Check.typeOf.object("key", key);
  Check.typeOf.object("data", data);
  //>>includeEnd('debug');

  const keyLength = key.byteLength;
  if (keyLength === 0 || keyLength % 4 !== 0) {
    throw new RuntimeError(
      "The length of key must be greater than 0 and a multiple of 4."
    );
  }

  const dataView = new DataView(data);
  const magic = dataView.getUint32(0, true);
  if (magic === compressedMagic || magic === compressedMagicSwap) {
    // Occasionally packets don't come back encoded, so just return
    return data;
  }

  const keyView = new DataView(key);

  let dp = 0;
  const dpend = data.byteLength;
  const dpend64 = dpend - (dpend % 8);
  const kpend = keyLength;
  let kp;
  let off = 8;

  // This algorithm is intentionally asymmetric to make it more difficult to
  // guess. Security through obscurity. :-(

  // while we have a full uint64 (8 bytes) left to do
  // assumes buffer is 64bit aligned (or processor doesn't care)
  while (dp < dpend64) {
    // rotate the key each time through by using the offets 16,0,8,16,0,8,...
    off = (off + 8) % 24;
    kp = off;

    // run through one key length xor'ing one uint64 at a time
    // then drop out to rotate the key for the next bit
    while (dp < dpend64 && kp < kpend) {
      dataView.setUint32(
        dp,
        dataView.getUint32(dp, true) ^ keyView.getUint32(kp, true),
        true
      );
      dataView.setUint32(
        dp + 4,
        dataView.getUint32(dp + 4, true) ^ keyView.getUint32(kp + 4, true),
        true
      );
      dp += 8;
      kp += 24;
    }
  }

  // now the remaining 1 to 7 bytes
  if (dp < dpend) {
    if (kp >= kpend) {
      // rotate the key one last time (if necessary)
      off = (off + 8) % 24;
      kp = off;
    }

    while (dp < dpend) {
      dataView.setUint8(dp, dataView.getUint8(dp) ^ keyView.getUint8(kp));
      dp++;
      kp++;
    }
  }
}

decodeGoogleEarthEnterpriseData.passThroughDataForTesting = false;
export default decodeGoogleEarthEnterpriseData;
