#!/usr/bin/env python

__version__ = '$Revision: 4791 $'.split()[1]
__date__ = '$Date: 2008-01-10 $'.split()[1]
__author__ = 'xmlbinmsg'

__doc__='''

Autogenerated python functions to serialize/deserialize binary messages.

Generated by: ./aisxmlbinmsg2py.py

Need to then wrap these functions with the outer AIS packet and then
convert the whole binary blob to a NMEA string.  Those functions are
not currently provided in this file.

serialize: python to ais binary
deserialize: ais binary to python

The generated code uses translators.py, binary.py, and aisstring.py
which should be packaged with the resulting files.


@requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3
@requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>}

@author: '''+__author__+'''
@version: ''' + __version__ +'''
@var __date__: Date of last svn commit
@undocumented: __version__ __author__ __doc__ parser
@status: under development
@license: Generated code has no license
@todo: FIX: put in a description of the message here with fields and types.
'''

import sys
from decimal import Decimal
from BitVector import BitVector

import binary, aisstring

# FIX: check to see if these will be needed
TrueBV  = BitVector(bitstring="1")
"Why always rebuild the True bit?  This should speed things up a bunch"
FalseBV = BitVector(bitstring="0")
"Why always rebuild the False bit?  This should speed things up a bunch"


fieldList = (
	'MessageID',
	'RepeatIndicator',
	'UserID',
	'Spare',
	'dac',
	'fid',
	'month',
	'day',
	'hour',
	'min',
	'longitude',
	'latitude',
	'timetoexpire',
	'radius',
	'areatype',
)

fieldListPostgres = (
	'MessageID',
	'RepeatIndicator',
	'UserID',
	'Spare',
	'dac',
	'fid',
	'month',
	'day',
	'hour',
	'min',
	'center',	# PostGIS data type
	'timetoexpire',
	'radius',
	'areatype',
)

toPgFields = {
	'longitude':'center',
	'latitude':'center',
}
'''
Go to the Postgis field names from the straight field name
'''

fromPgFields = {
	'center':('longitude','latitude',),
}
'''
Go from the Postgis field names to the straight field name
'''

pgTypes = {
	'center':'POINT',
}
'''
Lookup table for each postgis field name to get its type.
'''

def encode(params, validate=False):
	'''Create a timed_circular_notice binary message payload to pack into an AIS Msg timed_circular_notice.

	Fields in params:
	  - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
	  - RepeatIndicator(uint): Indicated how many times a message has been repeated
	  - UserID(uint): Unique ship identification number (MMSI)
	  - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
	  - dac(uint): Designated Area Code - 366 for the United States (field automatically set to "366")
	  - fid(uint): Functional IDentifier - 63 (field automatically set to "63")
	  - month(uint): Start time of most recent notice  UTC month
	  - day(uint): Start time of most recent notice  UTC day of the month 1..31
	  - hour(uint): Start time of most recent notice  UTC hours 0..23
	  - min(uint): Start time of most recent notice  UTC minutes
	  - longitude(decimal): Center of the area/zone  East West location
	  - latitude(decimal): Center of the area/zone  North South location
	  - timetoexpire(uint): Minutes from the start time until the notice expires.  Max is aprox 23 days
	  - radius(uint): Distance from center of detection zone (lat/lon above)
	  - areatype(uint): What does this circular area represent
	@param params: Dictionary of field names/values.  Throws a ValueError exception if required is missing
	@param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.
	@rtype: BitVector
	@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8
	@note: The returned bits may not be 6 bit aligned.  It is up to you to pad out the bits.
	'''

	bvList = []
	bvList.append(binary.setBitVectorSize(BitVector(intVal=8),6))
	if 'RepeatIndicator' in params:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=params['RepeatIndicator']),2))
	else:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=params['UserID']),30))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=366),10))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=63),6))
	if 'month' in params:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=params['month']),4))
	else:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=0),4))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=params['day']),5))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=params['hour']),5))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=params['min']),6))
	if 'longitude' in params:
		bvList.append(binary.bvFromSignedInt(int(Decimal(params['longitude'])*Decimal('600000')),28))
	else:
		bvList.append(binary.bvFromSignedInt(108600000,28))
	if 'latitude' in params:
		bvList.append(binary.bvFromSignedInt(int(Decimal(params['latitude'])*Decimal('600000')),27))
	else:
		bvList.append(binary.bvFromSignedInt(54600000,27))
	if 'timetoexpire' in params:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=params['timetoexpire']),15))
	else:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=32767),15))
	if 'radius' in params:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=params['radius']),15))
	else:
		bvList.append(binary.setBitVectorSize(BitVector(intVal=32767),15))
	bvList.append(binary.setBitVectorSize(BitVector(intVal=params['areatype']),7))

	return binary.joinBV(bvList)

def decode(bv, validate=False):
	'''Unpack a timed_circular_notice message 

	Fields in params:
	  - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
	  - RepeatIndicator(uint): Indicated how many times a message has been repeated
	  - UserID(uint): Unique ship identification number (MMSI)
	  - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
	  - dac(uint): Designated Area Code - 366 for the United States (field automatically set to "366")
	  - fid(uint): Functional IDentifier - 63 (field automatically set to "63")
	  - month(uint): Start time of most recent notice  UTC month
	  - day(uint): Start time of most recent notice  UTC day of the month 1..31
	  - hour(uint): Start time of most recent notice  UTC hours 0..23
	  - min(uint): Start time of most recent notice  UTC minutes
	  - longitude(decimal): Center of the area/zone  East West location
	  - latitude(decimal): Center of the area/zone  North South location
	  - timetoexpire(uint): Minutes from the start time until the notice expires.  Max is aprox 23 days
	  - radius(uint): Distance from center of detection zone (lat/lon above)
	  - areatype(uint): What does this circular area represent
	@type bv: BitVector
	@param bv: Bits defining a message
	@param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.
	@rtype: dict
	@return: params
	'''

	#Would be nice to check the bit count here..
	#if validate:
	#	assert (len(bv)==FIX: SOME NUMBER)
	r = {}
	r['MessageID']=8
	r['RepeatIndicator']=int(bv[6:8])
	r['UserID']=int(bv[8:38])
	r['Spare']=0
	r['dac']=366
	r['fid']=63
	r['month']=int(bv[56:60])
	r['day']=int(bv[60:65])
	r['hour']=int(bv[65:70])
	r['min']=int(bv[70:76])
	r['longitude']=Decimal(binary.signedIntFromBV(bv[76:104]))/Decimal('600000')
	r['latitude']=Decimal(binary.signedIntFromBV(bv[104:131]))/Decimal('600000')
	r['timetoexpire']=int(bv[131:146])
	r['radius']=int(bv[146:161])
	r['areatype']=int(bv[161:168])
	return r

def decodeMessageID(bv, validate=False):
	return 8

def decodeRepeatIndicator(bv, validate=False):
	return int(bv[6:8])

def decodeUserID(bv, validate=False):
	return int(bv[8:38])

def decodeSpare(bv, validate=False):
	return 0

def decodedac(bv, validate=False):
	return 366

def decodefid(bv, validate=False):
	return 63

def decodemonth(bv, validate=False):
	return int(bv[56:60])

def decodeday(bv, validate=False):
	return int(bv[60:65])

def decodehour(bv, validate=False):
	return int(bv[65:70])

def decodemin(bv, validate=False):
	return int(bv[70:76])

def decodelongitude(bv, validate=False):
	return Decimal(binary.signedIntFromBV(bv[76:104]))/Decimal('600000')

def decodelatitude(bv, validate=False):
	return Decimal(binary.signedIntFromBV(bv[104:131]))/Decimal('600000')

def decodetimetoexpire(bv, validate=False):
	return int(bv[131:146])

def decoderadius(bv, validate=False):
	return int(bv[146:161])

def decodeareatype(bv, validate=False):
	return int(bv[161:168])


def printHtml(params, out=sys.stdout):
		out.write("<h3>timed_circular_notice</h3>\n")
		out.write("<table border=\"1\">\n")
		out.write("<tr bgcolor=\"orange\">\n")
		out.write("<th align=\"left\">Field Name</th>\n")
		out.write("<th align=\"left\">Type</th>\n")
		out.write("<th align=\"left\">Value</th>\n")
		out.write("<th align=\"left\">Value in Lookup Table</th>\n")
		out.write("<th align=\"left\">Units</th>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>MessageID</td>\n")
		out.write("<td>uint</td>\n")
		if 'MessageID' in params:
			out.write("	<td>"+str(params['MessageID'])+"</td>\n")
			out.write("	<td>"+str(params['MessageID'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>RepeatIndicator</td>\n")
		out.write("<td>uint</td>\n")
		if 'RepeatIndicator' in params:
			out.write("	<td>"+str(params['RepeatIndicator'])+"</td>\n")
			if str(params['RepeatIndicator']) in RepeatIndicatorDecodeLut:
				out.write("<td>"+RepeatIndicatorDecodeLut[str(params['RepeatIndicator'])]+"</td>")
			else:
				out.write("<td><i>Missing LUT entry</i></td>")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>UserID</td>\n")
		out.write("<td>uint</td>\n")
		if 'UserID' in params:
			out.write("	<td>"+str(params['UserID'])+"</td>\n")
			out.write("	<td>"+str(params['UserID'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>Spare</td>\n")
		out.write("<td>uint</td>\n")
		if 'Spare' in params:
			out.write("	<td>"+str(params['Spare'])+"</td>\n")
			out.write("	<td>"+str(params['Spare'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>dac</td>\n")
		out.write("<td>uint</td>\n")
		if 'dac' in params:
			out.write("	<td>"+str(params['dac'])+"</td>\n")
			out.write("	<td>"+str(params['dac'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>fid</td>\n")
		out.write("<td>uint</td>\n")
		if 'fid' in params:
			out.write("	<td>"+str(params['fid'])+"</td>\n")
			out.write("	<td>"+str(params['fid'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>month</td>\n")
		out.write("<td>uint</td>\n")
		if 'month' in params:
			out.write("	<td>"+str(params['month'])+"</td>\n")
			out.write("	<td>"+str(params['month'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>day</td>\n")
		out.write("<td>uint</td>\n")
		if 'day' in params:
			out.write("	<td>"+str(params['day'])+"</td>\n")
			out.write("	<td>"+str(params['day'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>hour</td>\n")
		out.write("<td>uint</td>\n")
		if 'hour' in params:
			out.write("	<td>"+str(params['hour'])+"</td>\n")
			out.write("	<td>"+str(params['hour'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>min</td>\n")
		out.write("<td>uint</td>\n")
		if 'min' in params:
			out.write("	<td>"+str(params['min'])+"</td>\n")
			out.write("	<td>"+str(params['min'])+"</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>longitude</td>\n")
		out.write("<td>decimal</td>\n")
		if 'longitude' in params:
			out.write("	<td>"+str(params['longitude'])+"</td>\n")
			out.write("	<td>"+str(params['longitude'])+"</td>\n")
		out.write("<td>degrees</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>latitude</td>\n")
		out.write("<td>decimal</td>\n")
		if 'latitude' in params:
			out.write("	<td>"+str(params['latitude'])+"</td>\n")
			out.write("	<td>"+str(params['latitude'])+"</td>\n")
		out.write("<td>degrees</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>timetoexpire</td>\n")
		out.write("<td>uint</td>\n")
		if 'timetoexpire' in params:
			out.write("	<td>"+str(params['timetoexpire'])+"</td>\n")
			if str(params['timetoexpire']) in timetoexpireDecodeLut:
				out.write("<td>"+timetoexpireDecodeLut[str(params['timetoexpire'])]+"</td>")
			else:
				out.write("<td><i>Missing LUT entry</i></td>")
		out.write("<td>Minutes</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>radius</td>\n")
		out.write("<td>uint</td>\n")
		if 'radius' in params:
			out.write("	<td>"+str(params['radius'])+"</td>\n")
			out.write("	<td>"+str(params['radius'])+"</td>\n")
		out.write("<td>m</td>\n")
		out.write("</tr>\n")
		out.write("\n")
		out.write("<tr>\n")
		out.write("<td>areatype</td>\n")
		out.write("<td>uint</td>\n")
		if 'areatype' in params:
			out.write("	<td>"+str(params['areatype'])+"</td>\n")
			if str(params['areatype']) in areatypeDecodeLut:
				out.write("<td>"+areatypeDecodeLut[str(params['areatype'])]+"</td>")
			else:
				out.write("<td><i>Missing LUT entry</i></td>")
		out.write("</tr>\n")
		out.write("</table>\n")


def printKml(params, out=sys.stdout):
	'''KML (Keyhole Markup Language) for Google Earth, but without the header/footer'''
	out.write("\	<Placemark>\n")
	out.write("\t	<name>"+str(params['stationsid'])+"</name>\n")
	out.write("\t\t<description>\n")
	import StringIO
	buf = StringIO.StringIO()
	printHtml(params,buf)
	import cgi
	out.write(cgi.escape(buf.getvalue()))
	out.write("\t\t</description>\n")
	out.write("\t\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\n")
	out.write("\t\t<Point>\n")
	out.write("\t\t\t<coordinates>")
	out.write(str(params['longitude']))
	out.write(',')
	out.write(str(params['latitude']))
	out.write(",0</coordinates>\n")
	out.write("\t\t</Point>\n")
	out.write("\t</Placemark>\n")

def printFields(params, out=sys.stdout, format='std', fieldList=None, dbType='postgres'):
	'''Print a timed_circular_notice message to stdout.

	Fields in params:
	  - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
	  - RepeatIndicator(uint): Indicated how many times a message has been repeated
	  - UserID(uint): Unique ship identification number (MMSI)
	  - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
	  - dac(uint): Designated Area Code - 366 for the United States (field automatically set to "366")
	  - fid(uint): Functional IDentifier - 63 (field automatically set to "63")
	  - month(uint): Start time of most recent notice  UTC month
	  - day(uint): Start time of most recent notice  UTC day of the month 1..31
	  - hour(uint): Start time of most recent notice  UTC hours 0..23
	  - min(uint): Start time of most recent notice  UTC minutes
	  - longitude(decimal): Center of the area/zone  East West location
	  - latitude(decimal): Center of the area/zone  North South location
	  - timetoexpire(uint): Minutes from the start time until the notice expires.  Max is aprox 23 days
	  - radius(uint): Distance from center of detection zone (lat/lon above)
	  - areatype(uint): What does this circular area represent
	@param params: Dictionary of field names/values.  
	@param out: File like object to write to
	@rtype: stdout
	@return: text to out
	'''

	if 'std'==format:
		out.write("timed_circular_notice:\n")
		if 'MessageID' in params: out.write("	MessageID:        "+str(params['MessageID'])+"\n")
		if 'RepeatIndicator' in params: out.write("	RepeatIndicator:  "+str(params['RepeatIndicator'])+"\n")
		if 'UserID' in params: out.write("	UserID:           "+str(params['UserID'])+"\n")
		if 'Spare' in params: out.write("	Spare:            "+str(params['Spare'])+"\n")
		if 'dac' in params: out.write("	dac:              "+str(params['dac'])+"\n")
		if 'fid' in params: out.write("	fid:              "+str(params['fid'])+"\n")
		if 'month' in params: out.write("	month:            "+str(params['month'])+"\n")
		if 'day' in params: out.write("	day:              "+str(params['day'])+"\n")
		if 'hour' in params: out.write("	hour:             "+str(params['hour'])+"\n")
		if 'min' in params: out.write("	min:              "+str(params['min'])+"\n")
		if 'longitude' in params: out.write("	longitude:        "+str(params['longitude'])+"\n")
		if 'latitude' in params: out.write("	latitude:         "+str(params['latitude'])+"\n")
		if 'timetoexpire' in params: out.write("	timetoexpire:     "+str(params['timetoexpire'])+"\n")
		if 'radius' in params: out.write("	radius:           "+str(params['radius'])+"\n")
		if 'areatype' in params: out.write("	areatype:         "+str(params['areatype'])+"\n")
	elif 'csv'==format:
		if None == options.fieldList:
			options.fieldList = fieldList
		needComma = False;
		for field in fieldList:
			if needComma: out.write(',')
			needComma = True
			if field in params:
				out.write(str(params[field]))
			# else: leave it empty
		out.write("\n")
	elif 'html'==format:
		printHtml(params,out)
	elif 'sql'==format:
		sqlInsertStr(params,out,dbType=dbType)
	elif 'kml'==format:
		printKml(params,out)
	elif 'kml-full'==format:
		out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
		out.write("<kml xmlns=\"http://earth.google.com/kml/2.1\">\n")
		out.write("<Document>\n")
		out.write("	<name>timed_circular_notice</name>\n")
		printKml(params,out)
		out.write("</Document>\n")
		out.write("</kml>\n")
	else: 
		print "ERROR: unknown format:",format
		assert False

	return # Nothing to return

RepeatIndicatorEncodeLut = {
	'default':'0',
	'do not repeat any more':'3',
	} #RepeatIndicatorEncodeLut

RepeatIndicatorDecodeLut = {
	'0':'default',
	'3':'do not repeat any more',
	} # RepeatIndicatorEncodeLut

timetoexpireEncodeLut = {
	'No detection or notice not active in region':'0',
	'Will not expire until another message has been received to replace the message for that location':'32767',
	} #timetoexpireEncodeLut

timetoexpireDecodeLut = {
	'0':'No detection or notice not active in region',
	'32767':'Will not expire until another message has been received to replace the message for that location',
	} # timetoexpireEncodeLut

areatypeEncodeLut = {
	'Right whale acoustic detection':'0',
	'Restricted Area':'1',
	'Prohibited Area (no unauthorized entry)':'2',
	'Diving operations':'4',
	'Underwater obstruction':'5',
	'Fishing nets':'6',
	'Oil in water':'7',
	'Towing prohibited':'9',
	'Anchorage area (large vessels)':'10',
	'Anchorage area (small vessels)':'11',
	'Anchorage area (general)':'12',
	'Anchorage area (deep water)':'13',
	'Anchorage area (tanker)':'14',
	'Anchorage area (24h max)':'15',
	'Anchorage area (explosives)':'16',
	'Sea-plane landing area':'17',
	'Anchorage area (sea planes)':'18',
	'Anchoring prohibited':'20',
	'Fishing prohibited':'21',
	'Actively dumping explosives':'23',
	'Actively dumping':'24',
	'Firing danger area':'30',
	'Military area, entry prohibited':'31',
	'Mine-laying practice area':'32',
	'Submarine transit and exercise area':'33',
	'Mine field':'34',
	'Fast ice':'61',
	'Sea ice':'62',
	'Logs':'63',
	'Dredging area':'65',
	'Cargo transhipment area':'66',
	'Incineration area':'67',
	'SAR - Region of search (what SAR keys should there be?)':'70',
	'SAR - Man Overboard':'71',
	'Debris, Generic':'80',
	'LNG security zone':'111',
	} #areatypeEncodeLut

areatypeDecodeLut = {
	'0':'Right whale acoustic detection',
	'1':'Restricted Area',
	'2':'Prohibited Area (no unauthorized entry)',
	'4':'Diving operations',
	'5':'Underwater obstruction',
	'6':'Fishing nets',
	'7':'Oil in water',
	'9':'Towing prohibited',
	'10':'Anchorage area (large vessels)',
	'11':'Anchorage area (small vessels)',
	'12':'Anchorage area (general)',
	'13':'Anchorage area (deep water)',
	'14':'Anchorage area (tanker)',
	'15':'Anchorage area (24h max)',
	'16':'Anchorage area (explosives)',
	'17':'Sea-plane landing area',
	'18':'Anchorage area (sea planes)',
	'20':'Anchoring prohibited',
	'21':'Fishing prohibited',
	'23':'Actively dumping explosives',
	'24':'Actively dumping',
	'30':'Firing danger area',
	'31':'Military area, entry prohibited',
	'32':'Mine-laying practice area',
	'33':'Submarine transit and exercise area',
	'34':'Mine field',
	'61':'Fast ice',
	'62':'Sea ice',
	'63':'Logs',
	'65':'Dredging area',
	'66':'Cargo transhipment area',
	'67':'Incineration area',
	'70':'SAR - Region of search (what SAR keys should there be?)',
	'71':'SAR - Man Overboard',
	'80':'Debris, Generic',
	'111':'LNG security zone',
	} # areatypeEncodeLut

######################################################################
# SQL SUPPORT
######################################################################

dbTableName='timed_circular_notice'
'Database table name'

def sqlCreateStr(outfile=sys.stdout, fields=None, extraFields=None
		,addCoastGuardFields=True
		,dbType='postgres'
		):
	'''
	Return the SQL CREATE command for this message type
	@param outfile: file like object to print to.
	@param fields: which fields to put in the create.  Defaults to all.
	@param extraFields: A sequence of tuples containing (name,sql type) for additional fields
	@param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
	@param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
	@type addCoastGuardFields: bool
	@return: sql create string
	@rtype: str

	@see: sqlCreate
	'''
	# FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded?
	outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType)))

def sqlCreate(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'):
	'''
	Return the sqlhelp object to create the table.

	@param fields: which fields to put in the create.  Defaults to all.
	@param extraFields: A sequence of tuples containing (name,sql type) for additional fields
	@param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
	@type addCoastGuardFields: bool
	@param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
	@return: An object that can be used to generate a return
	@rtype: sqlhelp.create
	'''
	if None == fields: fields = fieldList
	import sqlhelp
	c = sqlhelp.create('timed_circular_notice',dbType=dbType)
	c.addPrimaryKey()
	if 'MessageID' in fields: c.addInt ('MessageID')
	if 'RepeatIndicator' in fields: c.addInt ('RepeatIndicator')
	if 'UserID' in fields: c.addInt ('UserID')
	if 'Spare' in fields: c.addInt ('Spare')
	if 'dac' in fields: c.addInt ('dac')
	if 'fid' in fields: c.addInt ('fid')
	if 'month' in fields: c.addInt ('month')
	if 'day' in fields: c.addInt ('day')
	if 'hour' in fields: c.addInt ('hour')
	if 'min' in fields: c.addInt ('min')
	if dbType != 'postgres':
		if 'longitude' in fields: c.addDecimal('longitude',8,5)
	if dbType != 'postgres':
		if 'latitude' in fields: c.addDecimal('latitude',8,5)
	if 'timetoexpire' in fields: c.addInt ('timetoexpire')
	if 'radius' in fields: c.addInt ('radius')
	if 'areatype' in fields: c.addInt ('areatype')

	if addCoastGuardFields:
		# c.addInt('cg_rssi')     # Relative signal strength indicator
		# c.addInt('cg_d')        # dBm receive strength
		# c.addInt('cg_T')        # Receive timestamp from the AIS equipment
		# c.addInt('cg_S')        # Slot received in
		# c.addVarChar('cg_x',10) # Idonno
		c.addVarChar('cg_r',15)   # Receiver station ID  -  should usually be an MMSI, but sometimes is a string
		c.addInt('cg_sec')        # UTC seconds since the epoch

		c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream

	if dbType == 'postgres':
		#--- EPSG 4326 : WGS 84
		#INSERT INTO "spatial_ref_sys" ("srid","auth_name","auth_srid","srtext","proj4text") VALUES (4326,'EPSG',4326,'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ');
		c.addPostGIS('center','POINT',2,SRID=4326);

	return c

def sqlInsertStr(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
	'''
	Return the SQL INSERT command for this message type
	@param params: dictionary of values keyed by field name
	@param outfile: file like object to print to.
	@param extraParams: A sequence of tuples containing (name,sql type) for additional fields
	@return: sql create string
	@rtype: str

	@see: sqlCreate
	'''
	outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))


def sqlInsert(params,extraParams=None,dbType='postgres'):
	'''
	Give the SQL INSERT statement
	@param params: dict keyed by field name of values
	@param extraParams: any extra fields that you have created beyond the normal ais message fields
	@rtype: sqlhelp.insert
	@return: insert class instance
	@todo: allow optional type checking of params?
	@warning: this will take invalid keys happily and do what???
	'''
	import sqlhelp
	i = sqlhelp.insert('timed_circular_notice',dbType=dbType)

	if dbType=='postgres':
		finished = []
		for key in params:
			if key in finished: 
				continue

			if key not in toPgFields and key not in fromPgFields:
				if type(params[key])==Decimal: i.add(key,float(params[key]))
				else: i.add(key,params[key])
			else:
				if key in fromPgFields:
					val = params[key]
				        # Had better be a WKT type like POINT(-88.1 30.321)
					i.addPostGIS(key,val)
					finished.append(key)
				else:
					# Need to construct the type.
					pgName = toPgFields[key]
					#valStr='GeomFromText(\''+pgTypes[pgName]+'('
					valStr=pgTypes[pgName]+'('
					vals = []
					for nonPgKey in fromPgFields[pgName]:
						vals.append(str(params[nonPgKey]))
						finished.append(nonPgKey)
					valStr+=' '.join(vals)+')'
					i.addPostGIS(pgName,valStr)
	else:
		for key in params: 
			if type(params[key])==Decimal: i.add(key,float(params[key]))
			else: i.add(key,params[key])

	if None != extraParams:
		for key in extraParams: 
			i.add(key,extraParams[key])

	return i

######################################################################
# LATEX SUPPORT
######################################################################

def latexDefinitionTable(outfile=sys.stdout
		):
	'''
	Return the LaTeX definition table for this message type
	@param outfile: file like object to print to.
	@type outfile: file obj
	@return: LaTeX table string via the outfile
	@rtype: str

	'''
	o = outfile

	o.write('''
\\begin{table}%[htb]
\\centering
\\begin{tabular}{|l|c|l|}
\\hline
Parameter & Number of bits & Description 
\\\\  \\hline\\hline
MessageID & 6 & AIS message number.  Must be 8 \\\\ \hline 
RepeatIndicator & 2 & Indicated how many times a message has been repeated \\\\ \hline 
UserID & 30 & Unique ship identification number (MMSI) \\\\ \hline 
Spare & 2 & Reserved for definition by a regional authority. \\\\ \hline 
dac & 10 & Designated Area Code - 366 for the United States \\\\ \hline 
fid & 6 & Functional IDentifier - 63 \\\\ \hline 
month & 4 & Start time of most recent notice  UTC month \\\\ \hline 
day & 5 & Start time of most recent notice  UTC day of the month 1..31 \\\\ \hline 
hour & 5 & Start time of most recent notice  UTC hours 0..23 \\\\ \hline 
min & 6 & Start time of most recent notice  UTC minutes \\\\ \hline 
longitude & 28 & Center of the area/zone  East West location \\\\ \hline 
latitude & 27 & Center of the area/zone  North South location \\\\ \hline 
timetoexpire & 15 & Minutes from the start time until the notice expires.  Max is aprox 23 days \\\\ \hline 
radius & 15 & Distance from center of detection zone (lat/lon above) \\\\ \hline 
areatype & 7 & What does this circular area represent\\\\ \\hline \\hline
Total bits & 168 & Appears to take 1 slot \\\\ \\hline
\\end{tabular}
\\caption{AIS message number 8: Timed circular notice}
\\label{tab:timed_circular_notice}
\\end{table}
''')

######################################################################
# Text Definition
######################################################################

def textDefinitionTable(outfile=sys.stdout
		,delim='\t'
		):
	'''
	Return the text definition table for this message type
	@param outfile: file like object to print to.
	@type outfile: file obj
	@return: text table string via the outfile
	@rtype: str

	'''
	o = outfile
	o.write('''Parameter'''+delim+'Number of bits'''+delim+'''Description 
MessageID'''+delim+'''6'''+delim+'''AIS message number.  Must be 8
RepeatIndicator'''+delim+'''2'''+delim+'''Indicated how many times a message has been repeated
UserID'''+delim+'''30'''+delim+'''Unique ship identification number (MMSI)
Spare'''+delim+'''2'''+delim+'''Reserved for definition by a regional authority.
dac'''+delim+'''10'''+delim+'''Designated Area Code - 366 for the United States
fid'''+delim+'''6'''+delim+'''Functional IDentifier - 63
month'''+delim+'''4'''+delim+'''Start time of most recent notice  UTC month
day'''+delim+'''5'''+delim+'''Start time of most recent notice  UTC day of the month 1..31
hour'''+delim+'''5'''+delim+'''Start time of most recent notice  UTC hours 0..23
min'''+delim+'''6'''+delim+'''Start time of most recent notice  UTC minutes
longitude'''+delim+'''28'''+delim+'''Center of the area/zone  East West location
latitude'''+delim+'''27'''+delim+'''Center of the area/zone  North South location
timetoexpire'''+delim+'''15'''+delim+'''Minutes from the start time until the notice expires.  Max is aprox 23 days
radius'''+delim+'''15'''+delim+'''Distance from center of detection zone (lat/lon above)
areatype'''+delim+'''7'''+delim+'''What does this circular area represent
Total bits'''+delim+'''168'''+delim+'''Appears to take 1 slot''')


######################################################################
# UNIT TESTING
######################################################################
import unittest
def testParams():
	'''Return a params file base on the testvalue tags.
	@rtype: dict
	@return: params based on testvalue tags
	'''
	params = {}
	params['MessageID'] = 8
	params['RepeatIndicator'] = 1
	params['UserID'] = 1193046
	params['Spare'] = 0
	params['dac'] = 366
	params['fid'] = 63
	params['month'] = 2
	params['day'] = 28
	params['hour'] = 23
	params['min'] = 45
	params['longitude'] = Decimal('-122.16328055555556')
	params['latitude'] = Decimal('37.424458333333334')
	params['timetoexpire'] = 1
	params['radius'] = 5000
	params['areatype'] = 1

	return params

class Testtimed_circular_notice(unittest.TestCase):
	'''Use testvalue tag text from each type to build test case the timed_circular_notice message'''
	def testEncodeDecode(self):

		params = testParams()
		bits   = encode(params)
		r      = decode(bits)

		# Check that each parameter came through ok.
		self.failUnlessEqual(r['MessageID'],params['MessageID'])
		self.failUnlessEqual(r['RepeatIndicator'],params['RepeatIndicator'])
		self.failUnlessEqual(r['UserID'],params['UserID'])
		self.failUnlessEqual(r['Spare'],params['Spare'])
		self.failUnlessEqual(r['dac'],params['dac'])
		self.failUnlessEqual(r['fid'],params['fid'])
		self.failUnlessEqual(r['month'],params['month'])
		self.failUnlessEqual(r['day'],params['day'])
		self.failUnlessEqual(r['hour'],params['hour'])
		self.failUnlessEqual(r['min'],params['min'])
		self.failUnlessAlmostEqual(r['longitude'],params['longitude'],5)
		self.failUnlessAlmostEqual(r['latitude'],params['latitude'],5)
		self.failUnlessEqual(r['timetoexpire'],params['timetoexpire'])
		self.failUnlessEqual(r['radius'],params['radius'])
		self.failUnlessEqual(r['areatype'],params['areatype'])

def addMsgOptions(parser):
	parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
		help='decode a "timed_circular_notice" AIS message')
	parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
		help='encode a "timed_circular_notice" AIS message')
	parser.add_option('--RepeatIndicator-field', dest='RepeatIndicatorField',default=0,metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--UserID-field', dest='UserIDField',metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--month-field', dest='monthField',default=0,metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--day-field', dest='dayField',metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--hour-field', dest='hourField',metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--min-field', dest='minField',metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--longitude-field', dest='longitudeField',default=Decimal('181'),metavar='decimal',type='string'
		,help='Field parameter value [default: %default]')
	parser.add_option('--latitude-field', dest='latitudeField',default=Decimal('91'),metavar='decimal',type='string'
		,help='Field parameter value [default: %default]')
	parser.add_option('--timetoexpire-field', dest='timetoexpireField',default=32767,metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--radius-field', dest='radiusField',default=32767,metavar='uint',type='int'
		,help='Field parameter value [default: %default]')
	parser.add_option('--areatype-field', dest='areatypeField',metavar='uint',type='int'
		,help='Field parameter value [default: %default]')

############################################################
if __name__=='__main__':

	from optparse import OptionParser
	parser = OptionParser(usage="%prog [options]",
		version="%prog "+__version__)

	parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
		help='run the documentation tests')
	parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
		help='run the unit tests')
	parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
		help='Make the test output verbose')

	# FIX: remove nmea from binary messages.  No way to build the whole packet?
	# FIX: or build the surrounding msg 8 for a broadcast?
	typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message?
	parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType'
		,default='nmeapayload'
		,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')


	outputChoices = ('std','html','csv','sql' , 'kml','kml-full')
	parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType'
		,default='std'
		,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]')

	parser.add_option('-o','--output',dest='outputFileName',default=None,
			  help='Name of the python file to write [default: stdout]')

	parser.add_option('-f','--fields',dest='fieldList',default=None, action='append',
			  choices=fieldList,
			  help='Which fields to include in the output.  Currently only for csv output [default: all]')

	parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true',
			  help='Print the field name for csv')

	parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true',
			  help='Print out an sql create command for the table.')

	parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true',
			  help='Print a LaTeX table of the type')

	parser.add_option('--text-table',dest='textDefinitionTable',default=False,action='store_true',
			  help='Print delimited table of the type (for Word table importing)')
	parser.add_option('--delimt-text-table',dest='delimTextDefinitionTable',default='\t'
			  ,help='Delimiter for text table [default: \'%default\'](for Word table importing)')


	dbChoices = ('sqlite','postgres')
	parser.add_option('-D','--db-type',dest='dbType',default='postgres'
			  ,choices=dbChoices,type='choice'
			  ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]')

	addMsgOptions(parser)

	(options,args) = parser.parse_args()
	success=True

	if options.doctest:
		import os; print os.path.basename(sys.argv[0]), 'doctests ...',
		sys.argv= [sys.argv[0]]
		if options.verbose: sys.argv.append('-v')
		import doctest
		numfail,numtests=doctest.testmod()
		if numfail==0: print 'ok'
		else: 
			print 'FAILED'
			success=False

	if not success: sys.exit('Something Failed')
	del success # Hide success from epydoc

	if options.unittest:
		sys.argv = [sys.argv[0]]
		if options.verbose: sys.argv.append('-v')
		unittest.main()

	outfile = sys.stdout
	if None!=options.outputFileName:
		outfile = file(options.outputFileName,'w')


	if options.doEncode:
		# First make sure all non required options are specified
		if None==options.RepeatIndicatorField: parser.error("missing value for RepeatIndicatorField")
		if None==options.UserIDField: parser.error("missing value for UserIDField")
		if None==options.monthField: parser.error("missing value for monthField")
		if None==options.dayField: parser.error("missing value for dayField")
		if None==options.hourField: parser.error("missing value for hourField")
		if None==options.minField: parser.error("missing value for minField")
		if None==options.longitudeField: parser.error("missing value for longitudeField")
		if None==options.latitudeField: parser.error("missing value for latitudeField")
		if None==options.timetoexpireField: parser.error("missing value for timetoexpireField")
		if None==options.radiusField: parser.error("missing value for radiusField")
		if None==options.areatypeField: parser.error("missing value for areatypeField")
		msgDict={
			'MessageID': '8',
			'RepeatIndicator': options.RepeatIndicatorField,
			'UserID': options.UserIDField,
			'Spare': '0',
			'dac': '366',
			'fid': '63',
			'month': options.monthField,
			'day': options.dayField,
			'hour': options.hourField,
			'min': options.minField,
			'longitude': options.longitudeField,
			'latitude': options.latitudeField,
			'timetoexpire': options.timetoexpireField,
			'radius': options.radiusField,
			'areatype': options.areatypeField,
		}

		bits = encode(msgDict)
		if 'binary'==options.ioType: print str(bits)
		elif 'nmeapayload'==options.ioType:
		        # FIX: figure out if this might be necessary at compile time
			print "bitLen",len(bits)
			bitLen=len(bits)
			if bitLen%6!=0:
			    bits = bits + BitVector(size=(6 - (bitLen%6)))  # Pad out to multiple of 6
			print "result:",binary.bitvectoais6(bits)[0]


		# FIX: Do not emit this option for the binary message payloads.  Does not make sense.
		elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability")
		else: sys.exit('ERROR: unknown ioType.  Help!')


	if options.sqlCreate:
		sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)

	if options.latexDefinitionTable:
		latexDefinitionTable(outfile)

	# For conversion to word tables
	if options.textDefinitionTable:
		textDefinitionTable(outfile,options.delimTextDefinitionTable)

	if options.printCsvfieldList:
		# Make a csv separated list of fields that will be displayed for csv
		if None == options.fieldList: options.fieldList = fieldList
		import StringIO
		buf = StringIO.StringIO()
		for field in options.fieldList:
			buf.write(field+',')
		result = buf.getvalue()
		if result[-1] == ',': print result[:-1]
		else: print result

	if options.doDecode:
		if len(args)==0: args = sys.stdin
		for msg in args:
			bv = None

			if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
				# Found nmea
				# FIX: do checksum
				bv = binary.ais6tobitvec(msg.split(',')[5])
			else: # either binary or nmeapayload... expect mostly nmeapayloads
				# assumes that an all 0 and 1 string can not be a nmeapayload
				binaryMsg=True
				for c in msg:
					if c not in ('0','1'):
						binaryMsg=False
						break
				if binaryMsg:
					bv = BitVector(bitstring=msg)
				else: # nmeapayload
					bv = binary.ais6tobitvec(msg)

			printFields(decode(bv)
				    ,out=outfile
				    ,format=options.outputType
				    ,fieldList=options.fieldList
				    ,dbType=options.dbType
				    )

