/**
 * AIS Parser manages the multiple states associated with AIS message parsing.
 *
 * @file     AISMessage.js
 * @date     2008-10-28 17:06 HST
 * @author   Paul Reuter
 * @version  1.2
 *
 * @include Sixbit
 * @include NMEAMessage
 *
 * Support for ITU-R M.1371-3: complete
 *
 */


function AISMessage(message) {
  NMEAMessage.call(this,message);
  
  this.msgid = null; // 5-letter NMEA code
  this.totno = null; // total number of messages to wait for
  this.msgno = null; // current message number (msgno of totno)
  this.seqno = null; // a sequence identifier for multi-part messages
  this.chan  = null; // channel received on (A or B)
  this.data  = null; // data (6-bit) encoded

  // sixbit remainder in NMEA message after the message is parsed properly.
  this.state = null; // this many bits are garbage at the end of the data.
  this.chksm = null; // NMEA checksum

  this.m_sixbit  = null;
  this.m_counter =  0; // Message part counter

  this.initialize();
  return this;
} // END: constructor AISMessage(message)
AISMessage.prototype = new NMEAMessage;


/**
 * Test to determine whether the message is a supported AIS message.
 *
 * @public
 * @return {boolean} yes or no.
 */
AISMessage.prototype.isAIS = function() {
  if( !this.isNMEA() ) { 
    return false;
  }
  return ( this.getMessageID() == "VDM" );
}; // END: function isAIS()


AISMessage.prototype.isComplete = function() {
  return (this.m_counter == this.totno);
}; // END: function isComplete()

AISMessage.prototype.isFirst = function() { 
  return (this.msgno == 1);
}; // END: function isFirst()

AISMessage.prototype.isLast = function() { 
  return (this.msgno == this.totno);
}; // END: function isLast()

AISMessage.prototype.isSequential = function(aism) { 
  return (this.seqno == aism.seqno && Math.abs(this.msgno-aism.msgno)==1);
}; // END: function isSequential(aism)


/**
 * Decodes the present AIS message as stored in state.  It's up to the
 *  user to ensure that the AISMessage is complete before calling decode.
 *
 * @public
 * @return {mixed} false when ais msg_id is unsupported, an Object literal
 *  upon what appears to be bit parsing.  An unexpected end of bits will
 *  not trigger failure.
 */
AISMessage.prototype.decode = function() { 
  // determine the AIS sub-message type.
  var ais_msgid = this.m_sixbit.getBits(6);

  if( ais_msgid<1 || ais_msgid>24 ) { 
    return false;
  }

  switch(ais_msgid) { 
    case  1: return this.decode_1();
    case  2: return this.decode_2();
    case  3: return this.decode_3();
    case  4: return this.decode_4();
    case  5: return this.decode_5();
    case  6: return this.decode_6();
    case  7: return this.decode_7();
    case  8: return this.decode_8();
    case  9: return this.decode_9();
    case 10: return this.decode_10();
    case 11: return this.decode_11();
    case 12: return this.decode_12();
    case 13: return this.decode_13();
    case 14: return this.decode_14();
    case 15: return this.decode_15();
    case 16: return this.decode_16();
    case 17: return this.decode_17();
    case 18: return this.decode_18();
    case 19: return this.decode_19();
    case 20: return this.decode_20();
    case 21: return this.decode_21();
    case 22: return this.decode_22();
    case 23: return this.decode_23();
    case 24: return this.decode_24();
    case 25: return this.decode_25();
    case 26: return this.decode_26();
  }
  return false;
};


AISMessage.prototype.decode_1 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"      : this.m_sixbit.getBits(6),
    "repeat"     : this.m_sixbit.getBits(2),
    "userid"     : this.m_sixbit.getBits(30),
    "nav_status" : this.m_sixbit.getBits(4),
    "rot"        : this.m_sixbit.getBits(8),
    "sog"        : this.m_sixbit.getBits(10),
    "pos_acc"    : this.m_sixbit.getBits(1),
    "longitude"  : this.m_sixbit.getBits(28),
    "latitude"   : this.m_sixbit.getBits(27),
    "cog"        : this.m_sixbit.getBits(12),
    "heading"    : this.m_sixbit.getBits(9),
    "timestamp"  : this.m_sixbit.getBits(6),
    "special"    : this.m_sixbit.getBits(2),
    "spare"      : this.m_sixbit.getBits(3),
    "raim"       : this.m_sixbit.getBits(1),
    "comm_state" : {
      "sync_state"   : this.m_sixbit.getBits(2),
      "slot_timeout" : this.m_sixbit.getBits(3),
      "sub_message"  : this.m_sixbit.getBits(14)
    }
  };

  // alterations to the values depending on contents
  
  // rate of turn (deg/minute)
  res["rot"] = this.sign_unsigned(res["rot"],8);
  if( res["rot"] == -128 ) {
    res["rot"] = null;
  }

  // speed over ground (1/10 knots)
  res["sog"] = this.paddedFloat(res["sog"]/10,1,1,'0');
 
  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  // course over ground (1/10 degrees N)
  res["cog"] = this.paddedFloat(res["cog"]/10,1,1,'0');

  // direction the ship points in (degrees N)
  res["heading"] = (res["heading"] == 511) ? null : res["heading"];

  // UTC seconds (s)
  res["timestamp"] = (res["timestamp"] == 63) ? null : res["timestamp"];

  return res;  
}; // END: function decode_1()


AISMessage.prototype.decode_2 = function() { 
  return this.decode_1();
};  // END: decode_2()


AISMessage.prototype.decode_3 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"      : this.m_sixbit.getBits(6),
    "repeat"     : this.m_sixbit.getBits(2),
    "userid"     : this.m_sixbit.getBits(30),
    "nav_status" : this.m_sixbit.getBits(4),
    "rot"        : this.m_sixbit.getBits(8),
    "sog"        : this.m_sixbit.getBits(10),
    "pos_acc"    : this.m_sixbit.getBits(1),
    "longitude"  : this.m_sixbit.getBits(28),
    "latitude"   : this.m_sixbit.getBits(27),
    "cog"        : this.m_sixbit.getBits(12),
    "heading"    : this.m_sixbit.getBits(9),
    "timestamp"  : this.m_sixbit.getBits(6),
    "special"    : this.m_sixbit.getBits(2),
    "spare"      : this.m_sixbit.getBits(3),
    "raim"       : this.m_sixbit.getBits(1),
    "comm_state" : {
      "sync_state" : this.m_sixbit.getBits(2),
      "slot_alloc" : this.m_sixbit.getBits(13),
      "num_slots"  : this.m_sixbit.getBits(3),
      "keep_flag"  : this.m_sixbit.getBits(1)
    }
  };

  // alterations to the values depending on contents

  // rate of turn (deg/minute)
  res["rot"] = this.sign_unsigned(res["rot"],8);
  if( res["rot"] == -128 ) {
    res["rot"] = null;
  }
 
  // speed over ground (1/10 knots)
  res["sog"] = this.paddedFloat(res["sog"]/10,1,1,'0');

  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  // course over ground (1/10 degrees N)
  res["cog"] = this.paddedFloat(res["cog"]/10,1,1,'0');

  // direction the ship points in (degrees N)
  res["heading"] = (res["heading"] == 511) ? null : res["heading"];

  // UTC seconds (s)
  res["timestamp"] = (res["timestamp"] == 63) ? null : res["timestamp"];

  return res;  
}; // END: function decode_3()


AISMessage.prototype.decode_4 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"      : this.m_sixbit.getBits(6),
    "repeat"     : this.m_sixbit.getBits(2),
    "userid"     : this.m_sixbit.getBits(30),
    "utc_year"   : this.m_sixbit.getBits(14),
    "utc_month"  : this.m_sixbit.getBits(4),
    "utc_day"    : this.m_sixbit.getBits(5),
    "utc_hour"   : this.m_sixbit.getBits(5),
    "utc_minute" : this.m_sixbit.getBits(6),
    "utc_second" : this.m_sixbit.getBits(6),
    "pos_acc"    : this.m_sixbit.getBits(1),
    "longitude"  : this.m_sixbit.getBits(28),
    "latitude"   : this.m_sixbit.getBits(27),
    "nav_sensor" : this.m_sixbit.getBits(4),
    "spare_0"    : this.m_sixbit.getBits(10),
    "raim"       : this.m_sixbit.getBits(1),
    "comm_state" : {
      "sync_state"   : this.m_sixbit.getBits(2),
      "slot_timeout" : this.m_sixbit.getBits(3),
      "sub_message"  : this.m_sixbit.getBits(14)
    }
  };

  // alterations to the values depending on contents
 
  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  return res;  
}; // END: function decode_4()


AISMessage.prototype.decode_5 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();

  if( len == 424 ) { 
    return this.decode_5_v2();

  } else if( len == 416 ) { 
    return this.decode_5_v1();

  } else { 
    return false;
  }

}; // END: function decode_5()


AISMessage.prototype.decode_5_v1 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 416 ) {
    return false;
  }

  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "userid"      : this.m_sixbit.getBits(30),
    "version"     : this.m_sixbit.getBits(2),
    "imo"         : this.m_sixbit.getBits(30),
    "callsign"    : this.m_sixbit.getBitsAscii(36),
    "name"        : this.m_sixbit.getBitsAscii(120),
    "ship_type"   : this.m_sixbit.getBits(8),
    "ant_pos"     : {
      "bow"         : this.m_sixbit.getBits(9),
      "stern"       : this.m_sixbit.getBits(9),
      "port"        : this.m_sixbit.getBits(6),
      "starboard"   : this.m_sixbit.getBits(6)
    },
    "nav_sensor"  : this.m_sixbit.getBits(4),
    "eta"         : {
      "month"  : this.m_sixbit.getBits(4),
      "day"    : this.m_sixbit.getBits(4),
      "hour"   : this.m_sixbit.getBits(6),
      "minute" : this.m_sixbit.getBits(6)
    },
    "draught"     : this.m_sixbit.getBits(8),
    "destination" : this.m_sixbit.getBitsAscii(120)
  };


  // alterations to the values depending on contents
  
  // alter draught (1/10 meters, max 25.5m)
  res["draught"] = this.paddedFloat(res["draught"]/10,1,1,'0');
  
  return res;  
}; // END: function decode_5_v1()


AISMessage.prototype.decode_5_v2 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 424 ) { 
    return false;
  }

  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "userid"      : this.m_sixbit.getBits(30),
    "version"     : this.m_sixbit.getBits(2),
    "imo"         : this.m_sixbit.getBits(30),
    "callsign"    : this.m_sixbit.getBitsAscii(42),
    "name"        : this.m_sixbit.getBitsAscii(120),
    "ship_type"   : this.m_sixbit.getBits(8),
    "ant_pos"     : {
      "bow"         : this.m_sixbit.getBits(9),
      "stern"       : this.m_sixbit.getBits(9),
      "port"        : this.m_sixbit.getBits(6),
      "starboard"   : this.m_sixbit.getBits(6)
    },
    "nav_sensor"  : this.m_sixbit.getBits(4),
    "eta"         : {
      "month"  : this.m_sixbit.getBits(4),
      "day"    : this.m_sixbit.getBits(4),
      "hour"   : this.m_sixbit.getBits(6),
      "minute" : this.m_sixbit.getBits(6)
    },
    "draught"     : this.m_sixbit.getBits(8),
    "destination" : this.m_sixbit.getBitsAscii(120),
    "dte"         : this.m_sixbit.getBits(1),
    "spare_0"     : this.m_sixbit.getBits(1)
  };

  // alterations to the values depending on contents
  
  // alter draught (1/10 meters, max 25.5m)
  res["draught"] = this.paddedFloat(res["draught"]/10,1,1,'0');
  
  return res;  
}; // END: function decode_5_v2()


AISMessage.prototype.decode_6 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 88 || len > 1008 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"      : this.m_sixbit.getBits(6),
    "repeat"     : this.m_sixbit.getBits(2),
    "srcid"      : this.m_sixbit.getBits(30),
    "seqno"      : this.m_sixbit.getBits(2),
    "dstid"      : this.m_sixbit.getBits(30),
    "retransmit" : this.m_sixbit.getBits(1),
    "spare_0"    : this.m_sixbit.getBits(1),
    "app_id"     : {
      "dac"         : this.m_sixbit.getBits(10),
      "function_id" : this.m_sixbit.getBits(6)
    },
    "data"       : this.m_sixbit.getBits(-1)
  };

  // alterations to the values depending on contents
  // None.
   
  return res;  
}; // END: function decode_6()


AISMessage.prototype.decode_7 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 72 || len > 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"   : this.m_sixbit.getBits(6),
    "repeat"  : this.m_sixbit.getBits(2),
    "srcid"   : this.m_sixbit.getBits(30),
    "spare_0" : this.m_sixbit.getBits(2),
    "dstid_1" : this.m_sixbit.getBits(30),
    "seqno_1" : this.m_sixbit.getBits(2),
    "dstid_2" : this.m_sixbit.getBits(30),
    "seqno_2" : this.m_sixbit.getBits(2),
    "dstid_3" : this.m_sixbit.getBits(30),
    "seqno_3" : this.m_sixbit.getBits(2),
    "dstid_4" : this.m_sixbit.getBits(30),
    "seqno_4" : this.m_sixbit.getBits(2)
  };

  // alterations to the values depending on contents

  if( res["dstid_4"] == 0 ) { 
    res["dstid_4"] = null;
  }

  if( res["dstid_3"] == 0 ) { 
    res["dstid_3"] = null;
  }

  if( res["dstid_2"] == 0 ) { 
    res["dstid_2"] = null;
  }

  if( res["dstid_1"] == 0 ) { 
    res["dstid_1"] = null;
  }

  return res;  
}; // END: function decode_7()


AISMessage.prototype.decode_8 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 40 || len > 1008 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"   : this.m_sixbit.getBits(6),
    "repeat"  : this.m_sixbit.getBits(2),
    "srcid"   : this.m_sixbit.getBits(30),
    "spare_0" : this.m_sixbit.getBits(2),
    "app_id"  : {
      "dac"         : this.m_sixbit.getBits(10),
      "function_id" : this.m_sixbit.getBits(6)
    },
    "data"       : this.m_sixbit.getBits(-1)
  };

  // alterations to the values depending on contents
  // None.
  
  return res;  
}; // END: function decode_8()


AISMessage.prototype.decode_9 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "userid"      : this.m_sixbit.getBits(30),
    "altitude"    : this.m_sixbit.getBits(12),
    "sog"         : this.m_sixbit.getBits(10),
    "pos_acc"     : this.m_sixbit.getBits(1),
    "longitude"   : this.m_sixbit.getBits(28),
    "latitude"    : this.m_sixbit.getBits(27),
    "cog"         : this.m_sixbit.getBits(12),
    "timestamp"   : this.m_sixbit.getBits(6),
    "alt_sensor"  : this.m_sixbit.getBits(1),
    "spare_0"     : this.m_sixbit.getBits(7),
    "dte"         : this.m_sixbit.getBits(1),
    "spare_1"     : this.m_sixbit.getBits(3),
    "mode"        : this.m_sixbit.getBits(1),
    "raim"        : this.m_sixbit.getBits(1),
    "comm_select" : this.m_sixbit.getBits(1)
  };

  if( res["comm_select"] == 0 ) { 
    // SOTDMA
    res["comm_state"] = {
      "sync_state"   : this.m_sixbit.getBits(2),
      "slot_timeout" : this.m_sixbit.getBits(3),
      "sub_message"  : this.m_sixbit.getBits(14)
    }
  } else { 
    // ITDMA
    res["comm_state"] = {
      "sync_state" : this.m_sixbit.getBits(2),
      "slot_alloc" : this.m_sixbit.getBits(13),
      "num_slots"  : this.m_sixbit.getBits(3),
      "keep_flag"  : this.m_sixbit.getBits(1)
    }
  }

  // alterations to the values depending on contents
   
  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  // course over ground (1/10 degrees N)
  res["cog"] = this.paddedFloat(res["cog"]/10,1,1,'0');

  // direction the ship points in (degrees N)
  res["heading"] = (res["heading"] == 511) ? null : res["heading"];

  // UTC seconds (s)
  res["timestamp"] = (res["timestamp"] == 63) ? null : res["timestamp"];

  return res;  
}; // END: function decode_9()


AISMessage.prototype.decode_10 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 72 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"   : this.m_sixbit.getBits(6),
    "repeat"  : this.m_sixbit.getBits(2),
    "srcid"   : this.m_sixbit.getBits(30),
    "spare_0" : this.m_sixbit.getBits(2),
    "dstid"   : this.m_sixbit.getBits(30),
    "spare_1" : this.m_sixbit.getBits(2)
  };

  // alterations to the values depending on contents
  // none.

  return res;  
}; // END: function decode_10()


AISMessage.prototype.decode_11 = function() { 
  return this.decode_4();
}; // END: function decode_11()


AISMessage.prototype.decode_12 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 88 || len > 1008 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"      : this.m_sixbit.getBits(6),
    "repeat"     : this.m_sixbit.getBits(2),
    "srcid"      : this.m_sixbit.getBits(30),
    "seqno"      : this.m_sixbit.getBits(2),
    "dstid"      : this.m_sixbit.getBits(30),
    "retransmit" : this.m_sixbit.getBits(1),
    "spare_0"    : this.m_sixbit.getBits(1),
    "app_id"     : {
      "dac"         : this.m_sixbit.getBits(10),
      "function_id" : this.m_sixbit.getBits(6)
    },
    "data"       : this.m_sixbit.getBits(-1)
  };

  // alterations to the values depending on contents
  // None.
   
  return res;  
}; // END: function decode_12()


AISMessage.prototype.decode_13 = function() { 
  return this.decode_7();
}; // END: function decode_13()


AISMessage.prototype.decode_14 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 40 || len > 1008 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"   : this.m_sixbit.getBits(6),
    "repeat"  : this.m_sixbit.getBits(2),
    "srcid"   : this.m_sixbit.getBits(30),
    "spare_0" : this.m_sixbit.getBits(2),
    "text"    : this.m_sixbit.getBitsAscii(len-40)
  };

  // alterations to the values depending on contents
  // None.
  
  return res;  
}; // END: function decode_14()


AISMessage.prototype.decode_15 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 88 || len > 160 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"      : this.m_sixbit.getBits(6),
    "repeat"     : this.m_sixbit.getBits(2),
    "srcid"      : this.m_sixbit.getBits(30),
    "spare_0"    : this.m_sixbit.getBits(2),
    "dstid_1"    : this.m_sixbit.getBits(30),
    "msgid_1_1"  : this.m_sixbit.getBits(6),
    "offset_1_1" : this.m_sixbit.getBits(12),
    "spare_1"    : this.m_sixbit.getBits(2),
    "msgid_1_2"  : this.m_sixbit.getBits(6),
    "offset_1_2" : this.m_sixbit.getBits(12),
    "spare_2"    : this.m_sixbit.getBits(2),
    "dstid_2"    : this.m_sixbit.getBits(30),
    "msgid_2"    : this.m_sixbit.getBits(6),
    "offset_2"   : this.m_sixbit.getBits(12),
    "spare_3"    : this.m_sixbit.getBits(2)
  };

  // alterations to the values depending on contents
  // None.
  
  return res;  
}; // END: function decode_15()


AISMessage.prototype.decode_16 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( !(len == 96 || len == 144) ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "srcid"       : this.m_sixbit.getBits(30),
    "spare_0"     : this.m_sixbit.getBits(2),
    "dstid_A"     : this.m_sixbit.getBits(30),
    "offset_A"    : this.m_sixbit.getBits(12),
    "increment_A" : this.m_sixbit.getBits(10)
  };

  if( len == 96 ) { 
    res["spare_1"] = this.m_sixbit.getBits(4);
  } else { 
    res["dstid_B"]     = this.m_sixbit.getBits(30),
    res["offset_B"]    = this.m_sixbit.getBits(12),
    res["increment_B"] = this.m_sixbit.getBits(10)
  }


  // alterations to the values depending on contents
  // None.
  
  return res;  
}; // END: function decode_16()


AISMessage.prototype.decode_17 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 80 || len > 816 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"     : this.m_sixbit.getBits(6),
    "repeat"    : this.m_sixbit.getBits(2),
    "src_id"    : this.m_sixbit.getBits(30),
    "spare_0"   : this.m_sixbit.getBits(2),
    "longitude" : this.m_sixbit.getBits(18),
    "latitude"  : this.m_sixbit.getBits(17),
    "spare_1"   : this.m_sixbit.getBits(5)
  }
  
  if ( len > 80 ) { 
    res["DGNSS"]  = {
      "msg_type"  : this.m_sixbit.getBits(6),
      "sta_id"    : this.m_sixbit.getBits(10),
      "z_count"   : this.m_sixbit.getBits(13),
      "seq_no"    : this.m_sixbit.getBits(3),
      "n_words"   : this.m_sixbit.getBits(5),
      "health"    : this.m_sixbit.getBits(3),
      "words"     : []
    };

    // quick test to make sure length is valid after learned information
    var n_words = res["DGNSS"]["n_words"];
    if( len != (80 + 40 + 24*n_words) ) { 
      return false;
    }
  
    // continue getting raw bits, 24 bits for each satellite record
    for(var i=0; i< n_words; i++ ) { 
      res["DGNSS"]["words"].push( this.m_sixbit.getBits(24) );
    }
  
  }
  
  // alterations to the values depending on contents

  // longitude (1/10 minutes == 1/(60*10) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],18) / 600;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10 minutes == 1/(60*10) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],17) / 600;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  if( len > 80 ) { 
    // z_count (Time value in 0.6s (0-3599.4)
    res["DGNSS"]["z_count"] = 0.6 * parseFloat(res["DGNSS"]["z_count"]);
  }
  
  return res;  
}; // END: function decode_17()


AISMessage.prototype.decode_18 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "userid"      : this.m_sixbit.getBits(30),
    "spare_0"     : this.m_sixbit.getBits(8),
    "sog"         : this.m_sixbit.getBits(10),
    "pos_acc"     : this.m_sixbit.getBits(1),
    "longitude"   : this.m_sixbit.getBits(28),
    "latitude"    : this.m_sixbit.getBits(27),
    "cog"         : this.m_sixbit.getBits(12),
    "heading"     : this.m_sixbit.getBits(9),
    "timestamp"   : this.m_sixbit.getBits(6),
    "spare_1"     : this.m_sixbit.getBits(2),
    "flag_unit"   : this.m_sixbit.getBits(1),
    "flag_display": this.m_sixbit.getBits(1),
    "flag_dsc"    : this.m_sixbit.getBits(1),
    "flag_band"   : this.m_sixbit.getBits(1),
    "flag_msg22"  : this.m_sixbit.getBits(1),
    "mode"        : this.m_sixbit.getBits(1),
    "raim"        : this.m_sixbit.getBits(1),
    "comm_select" : this.m_sixbit.getBits(1)
  };

  if( res["comm_select"] == 0 ) { 
    // SOTDMA
    res["comm_state"] = {
      "sync_state"   : this.m_sixbit.getBits(2),
      "slot_timeout" : this.m_sixbit.getBits(3),
      "sub_message"  : this.m_sixbit.getBits(14)
    }
  } else { 
    // ITDMA
    res["comm_state"] = {
      "sync_state" : this.m_sixbit.getBits(2),
      "slot_alloc" : this.m_sixbit.getBits(13),
      "num_slots"  : this.m_sixbit.getBits(3),
      "keep_flag"  : this.m_sixbit.getBits(1)
    }
  }

  // alterations to the values depending on contents

  // speed over ground (1/10 knots)
  res["sog"] = this.paddedFloat(res["sog"]/10,1,1,'0');

  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  // course over ground (1/10 degrees N)
  res["cog"] = this.paddedFloat(res["cog"]/10,1,1,'0');

  // direction the ship points in (degrees N)
  res["heading"] = (res["heading"] == 511) ? null : res["heading"];

  // UTC seconds (s)
  res["timestamp"] = (res["timestamp"] == 63) ? null : res["timestamp"];

  return res;  
}; // END: function decode_18()


AISMessage.prototype.decode_19 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 312 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "userid"      : this.m_sixbit.getBits(30),
    "spare_0"     : this.m_sixbit.getBits(8),
    "sog"         : this.m_sixbit.getBits(10),
    "pos_acc"     : this.m_sixbit.getBits(1),
    "longitude"   : this.m_sixbit.getBits(28),
    "latitude"    : this.m_sixbit.getBits(27),
    "cog"         : this.m_sixbit.getBits(12),
    "heading"     : this.m_sixbit.getBits(9),
    "timestamp"   : this.m_sixbit.getBits(6),
    "spare_1"     : this.m_sixbit.getBits(4),
    "name"        : this.m_sixbit.getBitsAscii(120),
    "ship_type"   : this.m_sixbit.getBits(8),
    "ant_pos"     : {
      "bow"         : this.m_sixbit.getBits(9),
      "stern"       : this.m_sixbit.getBits(9),
      "port"        : this.m_sixbit.getBits(6),
      "starboard"   : this.m_sixbit.getBits(6)
    },
    "nav_sensor"  : this.m_sixbit.getBits(4),
    "raim"        : this.m_sixbit.getBits(1),
    "dte"         : this.m_sixbit.getBits(1),
    "mode"        : this.m_sixbit.getBits(1),
    "spare_2"     : this.m_sixbit.getBits(4)
  };

  // alterations to the values depending on contents
   
  // speed over ground (1/10 knots)
  res["sog"] = this.paddedFloat(res["sog"]/10,1,1,'0');

  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  // course over ground (1/10 degrees N)
  res["cog"] = this.paddedFloat(res["cog"]/10,1,1,'0');

  // direction the ship points in (degrees N)
  res["heading"] = (res["heading"] == 511) ? null : res["heading"];

  // UTC seconds (s)
  res["timestamp"] = (res["timestamp"] == 63) ? null : res["timestamp"];

  return res;  
}; // END: function decode_19()


AISMessage.prototype.decode_20 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 72 || len > 160 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "srcid"       : this.m_sixbit.getBits(30),
    "spare_0"      : this.m_sixbit.getBits(2),
    
    "offset_1"    : this.m_sixbit.getBits(12),
    "num_slots_1" : this.m_sixbit.getBits(4),
    "timeout_1"   : this.m_sixbit.getBits(3),
    "increment_1" : this.m_sixbit.getBits(11),
    
    "offset_2"    : this.m_sixbit.getBits(12),
    "num_slots_2" : this.m_sixbit.getBits(4),
    "timeout_2"   : this.m_sixbit.getBits(3),
    "increment_2" : this.m_sixbit.getBits(11),
    
    "offset_3"    : this.m_sixbit.getBits(12),
    "num_slots_3" : this.m_sixbit.getBits(4),
    "timeout_3"   : this.m_sixbit.getBits(3),
    "increment_3" : this.m_sixbit.getBits(11),
    
    "offset_4"    : this.m_sixbit.getBits(12),
    "num_slots_4" : this.m_sixbit.getBits(4),
    "timeout_4"   : this.m_sixbit.getBits(3),
    "increment_4" : this.m_sixbit.getBits(11),
    
    "spare_1"     : this.m_sixbit.getBits(6)
  };

  // alterations to the values depending on contents
  // None.
  
  return res;
}; // END: function decode_20()


AISMessage.prototype.decode_21 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 272 || len > 360 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "id"          : this.m_sixbit.getBits(30),
    "aton_type"   : this.m_sixbit.getBits(5),
    "aton_name"   : this.m_sixbit.getBitsAscii(120),
    "pos_acc"     : this.m_sixbit.getBits(1),
    "longitude"   : this.m_sixbit.getBits(28),
    "latitude"    : this.m_sixbit.getBits(27),
    "ant_pos"     : {
      "bow"         : this.m_sixbit.getBits(9),
      "stern"       : this.m_sixbit.getBits(9),
      "port"        : this.m_sixbit.getBits(6),
      "starboard"   : this.m_sixbit.getBits(6)
    },
    "nav_sensor"  : this.m_sixbit.getBits(4),
    "timestamp"   : this.m_sixbit.getBits(6),
    "offpos_ind"  : this.m_sixbit.getBits(1),
    "aton_status" : this.m_sixbit.getBits(8),
    "raim"        : this.m_sixbit.getBits(1),
    "virtual_aton": this.m_sixbit.getBits(1),
    "mode"        : this.m_sixbit.getBits(1),
    "spare_0"     : this.m_sixbit.getBits(1)
  };

  var rem = 0;
  if( len > 272 ) {
    rem = (len-272)%6;
    res["aton_name_ext"] = this.m_sixbit.getBitsAscii(len-272-rem);
  }
  res["spare_1"] = this.m_sixbit.getBits(rem);


  // alterations to the values depending on contents

  // longitude (1/10000 minutes == 1/(60*10000) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],28) / 600000;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10000 minutes == 1/(60*10000) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],27) / 600000;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }
  
  return res;
}; // END: function decode_21()


AISMessage.prototype.decode_22 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"        : this.m_sixbit.getBits(6),
    "repeat"       : this.m_sixbit.getBits(2),
    "staid"        : this.m_sixbit.getBits(30),
    "spare_0"      : this.m_sixbit.getBits(2),
    "channel_A"    : this.m_sixbit.getBits(12),
    "channel_B"    : this.m_sixbit.getBits(12),
    "power_mode"   : this.m_sixbit.getBits(4),
    "power_level"  : this.m_sixbit.getBits(1),
    "longitude_1"  : this.m_sixbit.getBits(18),
    "latitude_1"   : this.m_sixbit.getBits(17),
    "longitude_2"  : this.m_sixbit.getBits(18),
    "latitude_2"   : this.m_sixbit.getBits(17),
    "address_ind"  : this.m_sixbit.getBits(1),
    "channel_A_bw" : this.m_sixbit.getBits(1),
    "channel_B_bw" : this.m_sixbit.getBits(1),
    "transition"   : this.m_sixbit.getBits(3),
    "spare_1"      : this.m_sixbit.getBits(23)
  };
  
  // alterations to the values depending on contents
  if( res["address_ind"] ) { 
    res["address_1"] = (res["longitude_1"]<<12)|(res["latitude_1"]>>5);
    delete(res["longitude_1"]);
    delete(res["latitude_1"]);
    
    res["address_2"] = (res["longitude_2"]<<12)|(res["latitude_2"]>>5);
    delete(res["longitude_2"]);
    delete(res["latitude_2"]);

  } else { 
    // longitude (1/10 minutes == 1/(60*10) degrees)
    res["longitude"] = this.sign_unsigned(res["longitude"],18) / 600;
    if( Math.abs(res["longitude"]) > 180 ) { 
      res["longitude"] = null;
    } else {
      res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
    }
  
    // latitude (1/10 minutes == 1/(60*10) degrees)
    res["latitude"]  = this.sign_unsigned(res["latitude"],17) / 600;
    if( Math.abs(res["latitude"]) > 90 ) { 
      res["latitude"] = null;
    } else { 
      res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
    }  
  }
  
  return res;  
}; // END: function decode_22()


AISMessage.prototype.decode_23 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 160 ) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"        : this.m_sixbit.getBits(6),
    "repeat"       : this.m_sixbit.getBits(2),
    "srcid"        : this.m_sixbit.getBits(30),
    "spare_0"      : this.m_sixbit.getBits(2),
    "longitude_1"  : this.m_sixbit.getBits(18),
    "latitude_1"   : this.m_sixbit.getBits(17),
    "longitude_2"  : this.m_sixbit.getBits(18),
    "latitude_2"   : this.m_sixbit.getBits(17),
    "station_type" : this.m_sixbit.getBits(4),
    "ship_type"    : this.m_sixbit.getBits(8),
    "spare_1"      : this.m_sixbit.getBits(22),
    "rxtx_mode"    : this.m_sixbit.getBits(2),
    "report_int"   : this.m_sixbit.getBits(4),
    "quiet_time"   : this.m_sixbit.getBits(4),
    "spare_2"      : this.m_sixbit.getBits(6)
  };
  
  // alterations to the values depending on contents

  // longitude (1/10 minutes == 1/(60*10) degrees)
  res["longitude"] = this.sign_unsigned(res["longitude"],18) / 600;
  if( Math.abs(res["longitude"]) > 180 ) { 
    res["longitude"] = null;
  } else {
    res["longitude"] = this.paddedFloat(res["longitude"], 1, 5, '0');
  }
  
  // latitude (1/10 minutes == 1/(60*10) degrees)
  res["latitude"]  = this.sign_unsigned(res["latitude"],17) / 600;
  if( Math.abs(res["latitude"]) > 90 ) { 
    res["latitude"] = null;
  } else { 
    res["latitude"]  = this.paddedFloat(res["latitude"], 1, 5, '0');
  }  

  return res;  
}; // END: function decode_23()


AISMessage.prototype.decode_24 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  
  if( len == 160 ) { 
    return this.decode_24A();

  } else if( len == 168 ) { 
    return this.decode_24B();

  } else { 
    return false;
  }
}; // END: function decode_24()


AISMessage.prototype.decode_24A = function() {   
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 160 ) {
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"        : this.m_sixbit.getBits(6),
    "repeat"       : this.m_sixbit.getBits(2),
    "userid"       : this.m_sixbit.getBits(30),
    "part_no"      : this.m_sixbit.getBits(2),
    "name"         : this.m_sixbit.getBitsAscii(120)
  };
  
  // alterations to the values depending on contents
  // None.
  
  return res;
}; // END: function decode_24A


AISMessage.prototype.decode_24B = function() {   
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len != 168 ) {
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"        : this.m_sixbit.getBits(6),
    "repeat"       : this.m_sixbit.getBits(2),
    "userid"       : this.m_sixbit.getBits(30),
    "part_no"      : this.m_sixbit.getBits(2),
    "ship_type"    : this.m_sixbit.getBits(8),
    "vendor_id"    : this.m_sixbit.getBitsAscii(42),
    "callsign"     : this.m_sixbit.getBitsAscii(42),
    "mmsi_xor"     : this.m_sixbit.getBits(30),
    "spare_0"      : this.m_sixbit.getBits(6)
  };
  
  // alterations to the values depending on contents

  var mmsi = res["mmsi_xor"];
  res["ant_pos"] = {
    "bow"         : ((mmsi>>21) & 0x1ff),
    "stern"       : ((mmsi>>12) & 0x1ff),
    "port"        : ((mmsi>>6 ) &  0x3f),
    "starboard"   : ((mmsi    ) &  0x3f)
  };

  return res;
}; // END: function decode_24B


AISMessage.prototype.decode_25 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 40 || len > 168) { 
    return false;
  }
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "srcid"       : this.m_sixbit.getBits(30),
    "dest_ind"    : this.m_sixbit.getBits(1),
    "binary_flag" : this.m_sixbit.getBits(1)
  };
  
  if( res["dest_ind"] ) { 
    res["dstid"] = this.m_sixbit.getBits(30);
  }
  
  if( res["binary_flag"] ) { 
    res["app_id"] = {
      "dac"         : this.m_sixbit.getBits(10),
      "function_id" : this.m_sixbit.getBits(6)
    };
  }  
  res["data"] = this.m_sixbit.getBits(-1)

  return res;  
}; // END: function decode_25()


AISMessage.prototype.decode_26 = function() { 
  // Start from the beginning, no matter where we may have left off.
  this.m_sixbit.reset();

  var len = this.m_sixbit.bitsRemaining();
  if( len < 40 || len > 1064) { 
    return false;
  }
  
  
  // physical message shape, raw bits
  var res = {
    "msgid"       : this.m_sixbit.getBits(6),
    "repeat"      : this.m_sixbit.getBits(2),
    "srcid"       : this.m_sixbit.getBits(30),
    "dest_ind"    : this.m_sixbit.getBits(1),
    "binary_flag" : this.m_sixbit.getBits(1)
  };
  var used_bits = 40;
  
  if( res["dest_ind"] ) { 
    res["dstid"] = this.m_sixbit.getBits(30);
    used_bits += 30;
  }
  
  if( res["binary_flag"] ) { 
    res["app_id"] = {
      "dac"         : this.m_sixbit.getBits(10),
      "function_id" : this.m_sixbit.getBits(6)
    };
    used_bits += 16;
  }
  
  res["data"] = this.m_sixbit.getBits(len - used_bits - 20)
  res["comm_select"] = this.m_sixbit.getBits(1);
  
  if( res["comm_select"] == 0 ) { 
    // SOTDMA
    res["comm_state"] = {
      "sync_state"   : this.m_sixbit.getBits(2),
      "slot_timeout" : this.m_sixbit.getBits(3),
      "sub_message"  : this.m_sixbit.getBits(14)
    }
  } else { 
    // ITDMA
    res["comm_state"] = {
      "sync_state" : this.m_sixbit.getBits(2),
      "slot_alloc" : this.m_sixbit.getBits(13),
      "num_slots"  : this.m_sixbit.getBits(3),
      "keep_flag"  : this.m_sixbit.getBits(1)
    }
  }
  
  return res;  
}; // END: function decode_26()


/**
 * Append the data portion of one AIS message to this one.
 *
 * @public
 * @param {AISMessage} aism Another AIS Message, the part after this one.
 * @return {boolean} success or failure.
 */
AISMessage.prototype.append = function(aism) { 
  if( !this.m_sixbit.append(new Sixbit(aism.data,aism.state)) ) { 
    return false;
  }

  this.msgno = aism.msgno;
  this.m_counter++;
  return true;
}; // END: function append(aism)


/**
 * Format a floating point number for display as ASCII.
 *
 * @private
 * @param {}
 * @return {string} An ASCII representation of the floating point number,
 *  as formatted by the parameters supplied.
 */
AISMessage.prototype.paddedFloat = function(value,n_left,n_right,ch) { 
  ch = (!ch) ? '0' : ch;
  var sign = (value<0) ? '-' : '';
  var pts = String(Math.abs(value)).split("\.");
  if( pts.length > 2 ) { 
    return value;
  }
  while( pts.length < 2 ) { 
    pts.push('');
  }

  // handle the whole number portion
  var delta = (n_left  - pts[0].length);
  if( delta>=0 ) {
    // needs padding
    for(var i=0;i<delta;i++) {
      pts[0] = ch + '' + pts[0];
    }
  }
  // handle fractional portion
  delta = (n_right - pts[1].length);
  if( delta >= 0 ) {
    for(var i=0;i<delta;i++) {
      pts[1] = pts[1] + '' + ch;
    }
  } else {
    pts[1] = pts[1].substr(0,n_right);
  }
  return sign+pts.join('.');
};  // END: function paddedFloat(value,n_left,n_right,ch)


/**
  * Apply 2's complement to an n_bit number.
  *
  * @private
  * @param {int} num The number to sign
  * @param {int} n_bit The bit length of the number (for sign-bit manip).
  */
AISMessage.prototype.sign_unsigned = function(num,n_bit) { 
  n_bit = (!n_bit) ? 28 : n_bit;

  if(n_bit>7 && n_bit < 31) { 
    bit = 0x1<<(n_bit-1);
    inv = 0x0;
    for(i=0;i<n_bit;i++) { 
      inv = inv<<1 | 0x1;
    }
    if( num & bit ) {
      num = 0 - (((0xffffffff^num)&inv)+1);
    }
  }

  return num;
}; // END: function sign_unsigned(num,n_bits)


/**
 * Initialize an AIS Message.  This involves a quick test for NMEA form 
 *  and checksum, followed by some NMEA message splitting.
 *
 * @return {boolean} success or failure... depending on NMEA goodness.
 */
AISMessage.prototype.initialize = function() { 
  if( !this.isNMEA() || !this.hasValidChecksum() ) {    
    return false;
  }

  this.msgid = this.getField(0,',').substr(1);
  this.totno = Number(this.getField(1));
  this.msgno = Number(this.getField(2));
  this.seqno = this.getField(3);
  this.chan  = this.getField(4);
  this.data  = this.getField(5);
  this.state = Number(this.getField(6).split('*')[0]);
  this.chksm = this.getField(6).split('*')[1];

  this.m_sixbit = new Sixbit(this.data,this.state);
  this.m_counter = 1;

  return true;
}; // END: function initialize()


/**
 * Get a description of the message type.
 *
 * @public
 * @param {int} ais_msgid The unpacked ID for this message.
 * @return {string} Description of message.
 */
AISMessage.prototype.getMessageTitle = function(ais_msgid) { 
  switch(ais_msgid) { 
    case  1: return "Position Report (SOTDMA)";
    case  2: return "Position Report (SOTDMA)";
    case  3: return "Position Report (ITDMA)";
    case  4: return "Base Station Report (SOTDMA)";
    case  5: return "Static and Voyage Related Data";
    case  6: return "Binary Addressed Message";
    case  7: return "Binary Acknowledge";
    case  8: return "Binary Broadcast Message";
    case  9: return "Standard SAR Aircraft Position Report";
    case 10: return "UTC/Date Inquiry";
    case 11: return "UTC/Date Response";
    case 12: return "Addressed Safety Related Message";
    case 13: return "Safety Related Acknowledge";
    case 14: return "Safety Related Broadcast Message";
    case 15: return "Interrogation";
    case 16: return "Assigned Mode Command";
    case 17: return "DGNSS Broadcast Binary Message";
    case 18: return "Standard Class B Equipment Position Report";
    case 19: return "Extended Class B Equipment Position Report";
    case 20: return "Data Link Management Message";
    case 21: return "Aids-to-Navigation Report";
    case 22: return "Channel Management";
    case 23: return "Group Assignment Command";
    case 24: return "Static Data Report";
    case 25: return "Single Slot Binary Message";
    case 26: return "Multiple Slot Binary Message with Communications State";
    default: return false;
  }
  return false;
}; // END: function getMessageTitle(ais_msgid)
