/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);

function run_test() {
  run_next_test();
}

/*
 * Helper function to covert a HEX string to a byte array.
 *
 * @param hexString
 *        A hexadecimal string of which the length is even.
 */
function hexStringToBytes(hexString) {
  let bytes = [];

  let length = hexString.length;

  for (let i = 0; i < length; i += 2) {
    bytes.push(Number.parseInt(hexString.substr(i, 2), 16));
  }

  return bytes;
}

/*
 * Helper function to covert a byte array to a HEX string.
 *
 * @param bytes
 *        Could be a regular byte array or Uint8Array.
 */
function bytesToHexString(bytes) {
  let hexString = "";
  let hex;

  for (let i = 0; i < bytes.length; i++) {
    hex = bytes[i].toString(16).toUpperCase();
    if (hex.length === 1) {
      hexString += "0";
    }
    hexString += hex;
  }

  return hexString;
}

/*
 * Helper function to ecode Opaque UserData
 *
 * @param msg_type
 *        PDU_CDMA_MSG_TYPE_SUBMIT or PDU_CDMA_MSG_TYPE_DELIVER
 * @param data
 *        The byte array of opaque data to be encoded.
 */
function encodeOpaqueUserData(bitBufferHelper, options) {
  let bearerDataBuffer = [];
  bitBufferHelper.startWrite(bearerDataBuffer);

  // Msg-Id
  bitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
  bitBufferHelper.writeBits(3, 8);
  bitBufferHelper.writeBits(options.msg_type, 4); // MSG_TYPE
  bitBufferHelper.writeBits(1, 16);               // MSG_ID
  bitBufferHelper.flushWithPadding();             // HEADER_IND (1) + RESERVED (3)

  // User Data
  bitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
  let dataLength = options.data.length;
  bitBufferHelper.writeBits(2 + dataLength, 8);   // 2 bytes for MSG_ENCODING, NUM_FIELDS
  bitBufferHelper.writeBits(PDU_CDMA_MSG_CODING_OCTET, 5); //MSG_ENCODING
                                                  // MSG_TYPE is omitted if MSG_ENCODING is CODING_OCTET
  bitBufferHelper.writeBits(dataLength, 8);       // NUM_FIELDS
  for (let i = 0; i < dataLength; i++) {          // CHARi
    bitBufferHelper.writeBits(options.data[i], 8);
  }
  bitBufferHelper.flushWithPadding();             // RESERVED (3 filling bits)

  return bearerDataBuffer;
}

function newSmsParcel(cdmaPduHelper, pdu) {
  return newIncomingParcel(-1,
                           RESPONSE_TYPE_UNSOLICITED,
                           UNSOLICITED_RESPONSE_CDMA_NEW_SMS,
                           pduToParcelData(cdmaPduHelper, pdu));
}

/*
 * Helper function to encode PDU into Parcel.
 * See ril_cdma_sms.h for the structure definition of RIL_CDMA_SMS_Message
 *
 * @param teleservice
 *        The Teleservice-Id of this PDU.
 *        See PDU_CDMA_MSG_TELESERIVCIE_ID_XXX in ril_const.js.
 * @param address
 *        The Orginating or Destinating address.
 * @param bearerData
 *        The byte array of the encoded bearer data.
 */
function pduToParcelData(cdmaPduHelper, pdu) {

  let addrInfo = cdmaPduHelper.encodeAddr(pdu.address);
  // Teleservice, isServicePresent, ServiceCategory,
  // addrInfo {digitMode, numberMode, numberType, numberPlan, address.length, address}
  // Sub Address
  // bearerData length, bearerData.
  let dataLength = 4 + 4 + 4
                 + (5 + addrInfo.address.length) * 4
                 + 3 * 4
                 + 4 + pdu.bearerData.length * 4;

  let data = new Uint8Array(dataLength);
  let offset = 0;

  function writeInt(value) {
    data[offset++] = value & 0xFF;
    data[offset++] = (value >>  8) & 0xFF;
    data[offset++] = (value >> 16) & 0xFF;
    data[offset++] = (value >> 24) & 0xFF;
  }

  function writeByte(value) {
    data[offset++] = value & 0xFF;
    data[offset++] = 0;
    data[offset++] = 0;
    data[offset++] = 0;
  }

  // Teleservice
  writeInt(pdu.teleservice);

  // isServicePresent
  writeByte(0);

  // ServiceCategory
  writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);

  // AddrInfo
  writeByte(addrInfo.digitMode);
  writeByte(addrInfo.numberMode);
  writeByte(addrInfo.numberType);
  writeByte(addrInfo.numberPlan);
  let addressLength = addrInfo.address.length;
  writeByte(addressLength);
  for (let i = 0; i < addressLength; i++) {
    writeByte(addrInfo.address[i]);
  }

  // Subaddress
  writeByte(0);
  writeByte(0);
  writeByte(0);

  // Bearer Data Length
  dataLength = pdu.bearerData.length;
  writeByte(dataLength);

  // Bearer Data
  for (let i = 0; i < dataLength; i++) {
    writeByte(pdu.bearerData[i]);
  }

  return data;
}

/**
 * Verify CDMA SMS Delivery ACK Message.
 */
add_test(function test_processCdmaSmsStatusReport() {
  let workerHelper = newInterceptWorker();
  let worker = workerHelper.worker;
  let context = worker.ContextPool._contexts[0];

  function test_StatusReport(errorClass, msgStatus) {
    let msgId = 0;
    let sentSmsMap = context.RIL._pendingSentSmsMap;

    sentSmsMap[msgId] = {};

    let message = {
      SMSC:             "",
      mti:              0,
      udhi:             0,
      sender:           "0987654321",
      recipient:        null,
      pid:              PDU_PID_DEFAULT,
      epid:             PDU_PID_DEFAULT,
      dcs:              0,
      mwi:              null,
      replace:          false,
      header:           null,
      body:             "Status: Sent, Dest: 0987654321",
      data:             null,
      timestamp:        new Date().valueOf(),
      language:         null,
      status:           null,
      scts:             null,
      dt:               null,
      encoding:         PDU_CDMA_MSG_CODING_7BITS_ASCII,
      messageClass:     GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
      messageType:      PDU_CDMA_MSG_TYPE_P2P,
      serviceCategory:  0,
      subMsgType:       PDU_CDMA_MSG_TYPE_DELIVER_ACK,
      msgId:            msgId,
      errorClass:       errorClass,
      msgStatus:        msgStatus
    };

    context.RIL._processCdmaSmsStatusReport(message);

    let postedMessage = workerHelper.postedMessage;

    // Check if pending token is removed.
    ok((errorClass === 2) ? !!sentSmsMap[msgId] : !sentSmsMap[msgId]);

    // Check the response message accordingly.
    if (errorClass === -1) {
      // Check if the report is treated as normal incoming SMS
      equal("sms-received", postedMessage.rilMessageType);
    } else if (errorClass === 2) {
      // Do nothing.
    } else {
      // Check Delivery Status
      if (errorClass === 0) {
        equal(postedMessage.deliveryStatus, GECKO_SMS_DELIVERY_STATUS_SUCCESS);
      } else {
        equal(postedMessage.deliveryStatus, GECKO_SMS_DELIVERY_STATUS_ERROR);
      }
    }
  }

  test_StatusReport(-1, -1); // Message Status Sub-parameter is absent.
  test_StatusReport(0, 0);   // 00|000000: no error|Message accepted
  test_StatusReport(2, 4);   // 10|000100: temporary condition|Network congestion
  test_StatusReport(3, 5);   // 11|000101: permanent condition|Network error

  run_next_test();
});

/**
 * Verify WAP Push over CDMA SMS Message.
 */
add_test(function test_processCdmaSmsWapPush() {
  let workerHelper = newInterceptWorker(),
      worker = workerHelper.worker,
      context = worker.ContextPool._contexts[0],
      bitBufferHelper = context.BitBufferHelper,
      cdmaPduHelper = context.CdmaPDUHelper;

  function test_CdmaSmsWapPdu(wdpData, reversed) {
    let orig_address = "0987654321",
        hexString,
        fullDataHexString = "";

    for (let i = 0; i < wdpData.length; i++) {
      let dataIndex = (reversed) ? (wdpData.length - i - 1) : i;
      hexString  = "00";                                // MSG_TYPE
      hexString += bytesToHexString([wdpData.length]);  // TOTAL_SEG
      hexString += bytesToHexString([dataIndex]);       // SEG_NUM (zero-based)
      if ((dataIndex === 0)) {
        hexString += "23F00B84";                        // SOURCE_PORT, DEST_PORT for 1st segment
      }
      hexString += wdpData[dataIndex];                  // WDP DATA

      do_print("hexString: " + hexString);

      fullDataHexString += wdpData[i];

      let pdu = {
        teleservice: PDU_CDMA_MSG_TELESERIVCIE_ID_WAP,
        address:     orig_address,
        bearerData:  encodeOpaqueUserData(bitBufferHelper,
                                          { msg_type: PDU_CDMA_MSG_TYPE_DELIVER,
                                            data:     hexStringToBytes(hexString) })
      };

      worker.onRILMessage(0, newSmsParcel(cdmaPduHelper, pdu));
    }

    let postedMessage = workerHelper.postedMessage;

    do_print("fullDataHexString: " + fullDataHexString);

    equal("sms-received", postedMessage.rilMessageType);
    equal(PDU_CDMA_MSG_TELESERIVCIE_ID_WAP, postedMessage.teleservice);
    equal(orig_address, postedMessage.sender);
    equal(0x23F0, postedMessage.header.originatorPort);
    equal(0x0B84, postedMessage.header.destinationPort);
    equal(fullDataHexString, bytesToHexString(postedMessage.data));
  }

  // Verify Single WAP PDU
  test_CdmaSmsWapPdu(["000102030405060708090A0B0C0D0E0F"]);

  run_next_test();
});
