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

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

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "gSmsSegmentHelper", function() {
  let ns = {};
  Cu.import("resource://gre/modules/SmsSegmentHelper.jsm", ns);
  return ns.SmsSegmentHelper;
});

const ESCAPE = "\uffff";
const RESCTL = "\ufffe";

function run_test() {
  run_next_test();
}

/**
 * Verify receiving SMS-DELIVERY messages
 */

function hexToNibble(nibble) {
  nibble &= 0x0f;
  if (nibble < 10) {
    nibble += 48; // ASCII '0'
  } else {
    nibble += 55; // ASCII 'A'
  }
  return nibble;
}

function pduToParcelData(pdu) {
  let dataLength = 4 + pdu.length * 4 + 4;
  let data = new Uint8Array(dataLength);
  let offset = 0;

  // String length
  data[offset++] = pdu.length & 0xFF;
  data[offset++] = (pdu.length >> 8) & 0xFF;
  data[offset++] = (pdu.length >> 16) & 0xFF;
  data[offset++] = (pdu.length >> 24) & 0xFF;

  // PDU data
  for (let i = 0; i < pdu.length; i++) {
    let hi = (pdu[i] >>> 4) & 0x0F;
    let lo = pdu[i] & 0x0F;

    data[offset++] = hexToNibble(hi);
    data[offset++] = 0;
    data[offset++] = hexToNibble(lo);
    data[offset++] = 0;
  }

  // String delimitor
  data[offset++] = 0;
  data[offset++] = 0;
  data[offset++] = 0;
  data[offset++] = 0;

  return data;
}

function compose7bitPdu(lst, sst, data, septets) {
  if ((lst == 0) && (sst == 0)) {
    return [0x00,                              // SMSC
            PDU_MTI_SMS_DELIVER,               // firstOctet
            1, 0x00, 0,                        // senderAddress
            0x00,                              // protocolIdentifier
            PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme
            0, 0, 0, 0, 0, 0, 0,               // y m d h m s tz
            septets]                           // userDataLength
           .concat(data);
  }

  return [0x00,                                            // SMSC
          PDU_MTI_SMS_DELIVER | PDU_UDHI,                  // firstOctet
          1, 0x00, 0,                                      // senderAddress
          0x00,                                            // protocolIdentifier
          PDU_DCS_MSG_CODING_7BITS_ALPHABET,               // dataCodingScheme
          0, 0, 0, 0, 0, 0, 0,                             // y m d h m s tz
          8 + septets,                                     // userDataLength
          6,                                               // user data header length
          PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT, 1, lst, // PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT
          PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT, 1, sst]  // PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT
         .concat(data);
}

function composeUcs2Pdu(rawBytes) {
  return [0x00,                               // SMSC
          PDU_MTI_SMS_DELIVER,                // firstOctet
          1, 0x00, 0,                         // senderAddress
          0x00,                               // protocolIdentifier
          PDU_DCS_MSG_CODING_16BITS_ALPHABET, // dataCodingScheme
          0, 0, 0, 0, 0, 0, 0,                // y m d h m s tz
          rawBytes.length]                    // userDataLength
         .concat(rawBytes);
}

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

function removeSpecialChar(str, needle) {
  for (let i = 0; i < needle.length; i++) {
    let pos;
    while ((pos = str.indexOf(needle[i])) >= 0) {
      str = str.substring(0, pos) + str.substring(pos + 1);
    }
  }
  return str;
}

function newWriteHexOctetAsUint8Worker() {
  let worker = newWorker({
    postRILMessage: function(data) {
      // Do nothing
    },
    postMessage: function(message) {
      // Do nothing
    }
  });

  let context = worker.ContextPool._contexts[0];
  context.GsmPDUHelper.writeHexOctet = function(value) {
    context.Buf.writeUint8(value);
  };

  return worker;
}

function add_test_receiving_sms(expected, pdu) {
  add_test(function test_receiving_sms() {
    let worker = newWorker({
      postRILMessage: function(data) {
        // Do nothing
      },
      postMessage: function(message) {
        do_print("fullBody: " + message.fullBody);
        equal(expected, message.fullBody)
      }
    });

    do_print("expect: " + expected);
    do_print("pdu: " + pdu);
    worker.onRILMessage(0, newSmsParcel(pdu));

    run_next_test();
  });
}

var test_receiving_7bit_alphabets__worker;
function test_receiving_7bit_alphabets(lst, sst) {
  if (!test_receiving_7bit_alphabets__worker) {
    test_receiving_7bit_alphabets__worker = newWriteHexOctetAsUint8Worker();
  }
  let worker = test_receiving_7bit_alphabets__worker;
  let context = worker.ContextPool._contexts[0];
  let helper = context.GsmPDUHelper;
  let buf = context.Buf;

  function get7bitRawBytes(expected) {
    buf.outgoingIndex = 0;
    helper.writeStringAsSeptets(expected, 0, lst, sst);

    let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex);
    return Array.slice(subArray);
  }

  let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst];
  let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst];

  let text = removeSpecialChar(langTable + langShiftTable, ESCAPE + RESCTL);
  for (let i = 0; i < text.length;) {
    let len = Math.min(70, text.length - i);
    let expected = text.substring(i, i + len);
    let septets =
      gSmsSegmentHelper.countGsm7BitSeptets(expected, langTable, langShiftTable);
    let rawBytes = get7bitRawBytes(expected);
    let pdu = compose7bitPdu(lst, sst, rawBytes, septets);
    add_test_receiving_sms(expected, pdu);

    i += len;
  }
}

function test_receiving_ucs2_alphabets(text) {
  let worker = test_receiving_7bit_alphabets__worker;
  let context = worker.ContextPool._contexts[0];
  let buf = context.Buf;

  function getUCS2RawBytes(expected) {
    buf.outgoingIndex = 0;
    context.GsmPDUHelper.writeUCS2String(expected);

    let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex);
    return Array.slice(subArray);
  }

  for (let i = 0; i < text.length;) {
    let len = Math.min(70, text.length - i);
    let expected = text.substring(i, i + len);
    let rawBytes = getUCS2RawBytes(expected);
    let pdu = composeUcs2Pdu(rawBytes);
    add_test_receiving_sms(expected, pdu);

    i += len;
  }
}

var ucs2str = "";
for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) {
  ucs2str += PDU_NL_LOCKING_SHIFT_TABLES[lst];
  for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) {
    test_receiving_7bit_alphabets(lst, sst);

    if (lst == 0) {
      ucs2str += PDU_NL_SINGLE_SHIFT_TABLES[sst];
    }
  }
}
test_receiving_ucs2_alphabets(ucs2str);

// Bug 820220: B2G SMS: wrong order and truncated content in multi-part messages
add_test(function test_sendSMS_UCS2_without_langIndex_langShiftIndex_defined() {
  let worker = newWriteHexOctetAsUint8Worker();
  let context = worker.ContextPool._contexts[0];

  context.Buf.sendParcel = function() {
    // Each sendParcel() call represents one outgoing segment of a multipart
    // SMS message. Here, we have the first segment send, so it's "Hello "
    // only.
    //
    // 4(parcel size) + 4(request type) + 4(token)
    // + 4(two messages) + 4(null SMSC) + 4(message string length)
    // + 1(first octet) + 1(message reference)
    // + 2(DA len, TOA) + 4(addr)
    // + 1(pid) + 1(dcs)
    // + 1(UDL) + 6(UDHL, type, len, ref, max, seq)
    // + 12(2 * strlen("Hello "))
    // + 4(two delimitors) = 57
    //
    // If we have additional 6(type, len, langIndex, type len, langShiftIndex)
    // octets here, then bug 809553 is not fixed.
    equal(this.outgoingIndex, 57);

    run_next_test();
  };

  context.RIL.sendSMS({
    number: "1",
    segmentMaxSeq: 2,
    fullBody: "Hello World!",
    dcs: PDU_DCS_MSG_CODING_16BITS_ALPHABET,
    segmentRef16Bit: false,
    userDataHeaderLength: 5,
    requestStatusReport: true,
    segments: [
      {
        body: "Hello ",
        encodedBodyLength: 12,
      }, {
        body: "World!",
        encodedBodyLength: 12,
      }
    ],
  });
});
