# -*- coding: utf-8 -*-

# Python3 compatiability
import sys
if sys.version_info > (3,):
    xrange = range

"""
Module for writing correlator output to a CASA measurement set.

.. versionadded:: 1.2.1
"""

import os
import gc
import re
import glob
import math
import ephem
import numpy
import shutil
from datetime import datetime

from lsl import astro
from lsl.misc import geodesy
from lsl.common import constants
from lsl.misc.total_sorting import cmp_to_total

try:
    from collections import OrderedDict
except ImportError:
    from lsl.misc.OrderedDict import OrderedDict


__version__ = '0.1'
__revision__ = '$Rev: 2740 $'
__all__ = ['MS', 'StokesCodes', 'NumericStokes', 
           '__version__', '__revision__', '__all__']


StokesCodes = {'I':  1, 'Q':  2, 'U':  3, 'V':  4, 
               'RR': 5, 'RL': 6, 'LR': 7, 'LL': 8,
               'XX': 9, 'XY':10, 'YX':11, 'YY':12}
               

NumericStokes = { 1:'I',   2:'Q',   3:'U',   4:'V', 
                  5:'RR',  6:'RL',  7:'LR',  8:'LL',
                  9:'XX', 10:'XY', 11:'YX', 12:'YY'}

def mergeBaseline(ant1, ant2, shift=16):
    """
    Merge two stand ID numbers into a single baseline using the specified bit 
    shift size.
    """
    
    return (ant1 << shift) | ant2

def splitBaseline(baseline, shift=16):
    """
    Given a baseline, split it into it consistent stand ID numbers.
    """
    
    part = 2**shift - 1
    return (baseline >> shift) & part, baseline & part


try:
    from casacore.tables import table, tableutil
    
    class MS(object):
        """
        Class for storing visibility data and writing the data, along with array
        geometry, frequency setup, etc., to a CASA measurement set.
        """
        
        class _Antenna(object):
            """
            Holds information describing the location and properties of an antenna.
            """
            
            def __init__(self, id, x, y, z, bits=8):
                self.id = id
                self.x = x
                self.y = y
                self.z = z
                self.levels = bits
                self.polA = {'Type': 'X', 'Angle': 0.0, 'Cal': [0.0, 0.0]}
                self.polB = {'Type': 'Y', 'Angle': 90.0, 'Cal': [0.0, 0.0]}
                
            def getName(self):
                return "LWA%03i" % self.id
                
        class _Frequency:
            """
            Holds information about the frequency setup used in the file.
            """

            def __init__(self, offset, channelWidth, bandwidth):
                self.id = 1
                self.bandFreq = offset
                self.chWidth = channelWidth
                self.totalBW = bandwidth
                self.sideBand = 1
                self.baseBand = 0
                
        @cmp_to_total
        class _UVData(object):
            """
            Represents one UV visibility data set for a given observation time.
            """
        
            def __init__(self, obsTime, intTime, baselines, visibilities, pol=StokesCodes['XX'], source='z'):
                self.obsTime = obsTime
                self.intTime = intTime
                self.baselines = baselines
                self.visibilities = visibilities
                self.pol = pol
                self.source = source
                
            def __cmp__(self, y):
                """
                Function to sort the self.data list in order of time and then 
                polarization code.
                """
                
                sID = (self.obsTime, abs(self.pol))
                yID = (y.obsTime,    abs(y.pol)   )
                
                if sID > yID:
                    return 1
                elif sID < yID:
                    return -1
                else:
                    return 0
                    
            def time(self):
                return self.obsTime
                
            def getUVW(self, HA, dec, obs):
                Nbase = len(self.baselines)
                uvw = numpy.zeros((Nbase,3), dtype=numpy.float32)
                
                # Phase center coordinates
                # Convert numbers to radians and, for HA, hours to degrees
                HA2 = HA * 15.0 * numpy.pi/180
                dec2 = dec * numpy.pi/180
                lat2 = obs.lat
                
                # Coordinate transformation matrices
                trans1 = numpy.matrix([[0, -numpy.sin(lat2), numpy.cos(lat2)],
                                [1,  0,               0],
                                [0,  numpy.cos(lat2), numpy.sin(lat2)]])
                trans2 = numpy.matrix([[ numpy.sin(HA2),                  numpy.cos(HA2),                 0],
                                [-numpy.sin(dec2)*numpy.cos(HA2),  numpy.sin(dec2)*numpy.sin(HA2), numpy.cos(dec2)],
                                [ numpy.cos(dec2)*numpy.cos(HA2), -numpy.cos(dec2)*numpy.sin(HA2), numpy.sin(dec2)]])
                        
                for i,(a1,a2) in enumerate(self.baselines):
                    # Go from a east, north, up coordinate system to a celestial equation, 
                    # east, north celestial pole system
                    xyzPrime = a1.stand - a2.stand
                    xyz = trans1*numpy.matrix([[xyzPrime[0]],[xyzPrime[1]],[xyzPrime[2]]])
                    
                    # Go from CE, east, NCP to u, v, w
                    temp = trans2*xyz
                    uvw[i,:] = numpy.squeeze(temp)
                    
                return uvw
                    
            def argsort(self, mapper=None, shift=16):
                packed = []
                for a1,a2 in self.baselines:
                    if mapper is None:
                        s1, s2 = a1.stand.id, a2.stand.id
                    else:
                        s1, s2 = mapper.index(a1.stand.id), mapper.index(a2.stand.id)
                    packed.append( mergeBaseline(s1, s2, shift=shift) )
                packed = numpy.array(packed, dtype=numpy.int32)
                
                return numpy.argsort(packed)
                
        def parseRefTime(self, refTime):
            """
            Given a time as either a integer, float, string, or datetime object, 
            convert it to a string in the formation 'YYYY-MM-DDTHH:MM:SS'.
            """
            
            # Valid time string (modulo the 'T')
            timeRE = re.compile(r'\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(\.\d+)?')
            
            if type(refTime) in (int, long, float):
                refDateTime = datetime.utcfromtimestamp(refTime)
                refTime = refDateTime.strftime("%Y-%m-%dT%H:%M:%S")
            elif type(refTime) == datetime:
                refTime = refTime.strftime("%Y-%m-%dT%H:%M:%S")
            elif type(refTime) == str:
                # Make sure that the string times are of the correct format
                if re.match(timeRE, refTime) is None:
                    raise RuntimeError("Malformed date/time provided: %s" % refTime)
                else:
                    refTime = refTime.replace(' ', 'T', 1)
            else:
                raise RuntimeError("Unknown time format provided.")
                
            return refTime
            
        def refTime2AstroDate(self):
            """
            Convert a reference time string to an :class:`lsl.astro.date` object.
            """
            
            dateStr = self.refTime.replace('T', '-').replace(':', '-').split('-')
            return astro.date(int(dateStr[0]), int(dateStr[1]), int(dateStr[2]), int(dateStr[3]), int(dateStr[4]), float(dateStr[5]))
            
        def __init__(self, filename, refTime=0.0, verbose=False, memmap=None, clobber=False):
            """
            Initialize a new FITS IDI object using a filename and a reference time 
            given in seconds since the UNIX 1970 ephem, a python datetime object, or a 
            string in the format of 'YYYY-MM-DDTHH:MM:SS'.
            
            .. versionchanged:: 1.1.2
                Added the 'memmap' and 'clobber' keywords to control if the file
                is memory mapped and whether or not to overwrite an existing file, 
                respectively.
            """
            
            # File-specific information
            self.filename = filename
            self.verbose = verbose
            
            # Observatory-specific information
            self.siteName = 'Unknown'
            
            # Observation-specific information
            self.refTime = self.parseRefTime(refTime)
            self.nAnt = 0
            self.nChan = 0
            self.nStokes = 0
            self.refVal = 0
            self.refPix = 0
            self.channelWidth = 0
            
            # Parameters that store the meta-data and data
            self.array = []
            self.freq = []
            self.stokes = []
            self.data = []
            
            # Open the file and get going
            if os.path.exists(filename):
                if clobber:
                    shutil.rmtree(filename, ignore_errors=False)
                else:
                    raise IOError("File '%s' already exists" % filename)
            self.basename = filename
            
        def setStokes(self, polList):
            """
            Given a list of Stokes parameters, update the object's parameters.
            """
            
            for pol in polList:
                if type(pol) == str:
                    numericPol = StokesCodes[pol.upper()]
                else:
                    numericPol = pol
                    
                if numericPol not in self.stokes:
                    self.stokes.append(numericPol)
                    
            # Sort into order of 'XX', 'YY', 'XY', and 'YX' or 'I', 'Q', 'U', and 'V'
            self.stokes.sort()
            if self.stokes[0] < 0:
                self.stokes.reverse()
                
            self.nStokes = len(self.stokes)
            
        def setFrequency(self, freq):
            """
            Given a numpy array of frequencies, set the relevant common observation
            parameters and add an entry to the self.freq list.
            """
            
            if self.nChan == 0:
                self.nChan = len(freq)
                self.refVal = freq[0]
                self.refPix = 1
                self.channelWidth = numpy.abs(freq[1] - freq[0])
                offset = 0.0
            else:
                assert(len(freq) == self.nChan)
                offset = freq[0] - self.refVal
            totalWidth = numpy.abs(freq[-1] - freq[0])
            
            freqSetup = self._Frequency(offset, self.channelWidth, totalWidth)

            self.freq.append(freqSetup)
            
        def setGeometry(self, site, antennas, bits=8):
            """
            Given a station and an array of stands, set the relevant common observation
            parameters and add entries to the self.array list.
            
            .. versionchanged:: 0.4.0
                Switched over to passing in Antenna instances generated by the
                :mod:`lsl.common.stations` module instead of a list of stand ID
                numbers.
            """
            
            # Update the observatory-specific information
            self.siteName = site.name
            
            stands = []
            for ant in antennas:
                stands.append(ant.stand.id)
            stands = numpy.array(stands)
            
            arrayX, arrayY, arrayZ = site.getGeocentricLocation()
            
            xyz = numpy.zeros((len(stands),3))
            for i,ant in enumerate(antennas):
                xyz[i,0] = ant.stand.x
                xyz[i,1] = ant.stand.y
                xyz[i,2] = ant.stand.z
                
            # Create the stand mapper
            mapper = []
            ants = []
            topo2eci = site.getECITransform()
            for i in xrange(len(stands)):
                eci = numpy.dot(topo2eci, xyz[i,:])
                ants.append( self._Antenna(stands[i], eci[0], eci[1], eci[2], bits=bits) )
                mapper.append( stands[i] )
                
            self.nAnt = len(ants)
            self.array.append( {'center': [arrayX, arrayY, arrayZ], 'ants': ants, 'mapper': mapper, 'inputAnts': antennas} )
            
        def addDataSet(self, obsTime, intTime, baselines, visibilities, pol='XX', source='z'):
            """
            Create a UVData object to store a collection of visibilities.
            
            .. versionchanged:: 0.4.0
                Switched over to passing in Antenna instances generated by the
                :mod:`lsl.common.stations` module instead of a list of stand ID
                as part of the baselines.
                
            .. versionchanged:: 1.1.0
                Added a new 'source' keyword to set the phase center for the data.
                This can either by 'z' for zenith or a ephem.Body instances for a
                point on the sky.
            """
            
            if type(pol) == str:
                numericPol = StokesCodes[pol.upper()]
            else:
                numericPol = pol
                
            self.data.append( self._UVData(obsTime, intTime, baselines, visibilities, pol=numericPol, source=source) )
            
        def write(self):
            """
            Fill in the FITS-IDI file will all of the tables in the 
            correct order.
            """
            
            # Validate
            if self.nStokes == 0:
                raise RuntimeError("No polarization setups defined")
            if len(self.freq) == 0:
                raise RuntimeError("No frequency setups defined")
            if self.nAnt == 0:
                raise RuntimeError("No array geometry defined")
            if len(self.data) == 0:
                raise RuntimeError("No visibility data defined")
                
            # Sort the data set
            self.data.sort()
            
            # Write the tables
            self._writeMain()
            self._writeAntenna()
            self._writePolarization()
            self._writeObservation()
            self._writeSpectralWindow()
            self._writeMisc()
            
            # Fixup the info and keywords for the main table
            tb = table("%s" % self.basename, readonly=False, ack=False)
            tb.putinfo({'type':'Measurement Set', 
                        'readme':'This is a MeasurementSet Table holding measurements from a Telescope'})
            tb.putkeyword('MS_VERSION', numpy.float32(2.0))
            for filename in sorted(glob.glob('%s/*' % self.basename)):
                if os.path.isdir(filename):
                    tname = os.path.basename(filename)
                    stb = table("%s/%s" % (self.basename, tname), ack=False)
                    tb.putkeyword(tname, stb)
                    stb.close()
            tb.flush()
            tb.close()
                
            # Clear out the data section
            del(self.data[:])
            gc.collect()
            
        def close(self):
            """
            Close out the file.
            """
            
            pass
            
        def _writeAntenna(self):
            """
            Write the antenna table.
            """
            
            col1 = tableutil.makearrcoldesc('OFFSET', 0.0, 1, 
                                            comment='Axes offset of mount to FEED REFERENCE point', 
                                            keywords={'QuantumUnits':['m','m','m'], 
                                                      'MEASINFO':{'type':'position', 'Ref':'ITRF'}
                                            })
            col2 = tableutil.makearrcoldesc('POSITION', 0.0, 1,
                                            comment='Antenna X,Y,Z phase reference position', 
                                            keywords={'QuantumUnits':['m','m','m'], 
                                                      'MEASINFO':{'type':'position', 'Ref':'ITRF'}
                                                      })
            col3 = tableutil.makescacoldesc('TYPE', "ground-based", 
                                            comment='Antenna type (e.g. SPACE-BASED)')
            col4 = tableutil.makescacoldesc('DISH_DIAMETER', 2.0, 
                                            comment='Physical diameter of dish', 
                                            keywords={'QuantumUnits':['m',]})
            col5 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='Flag for this row')
            col6 = tableutil.makescacoldesc('MOUNT', "alt-az", 
                                            comment='Mount type e.g. alt-az, equatorial, etc.')
            col7 = tableutil.makescacoldesc('NAME', "none", 
                                            comment='Antenna name, e.g. VLA22, CA03')
            col8 = tableutil.makescacoldesc('STATION', self.siteName, 
                                            comment='Station (antenna pad) name')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8])
            tb = table("%s/ANTENNA" % self.basename, desc, nrow=self.nAnt, ack=False)
            
            tb.putcol('OFFSET', numpy.zeros((self.nAnt,3)), 0, self.nAnt)
            tb.putcol('TYPE', ['GROUND-BASED,']*self.nAnt, 0, self.nAnt)
            tb.putcol('DISH_DIAMETER', [2.0,]*self.nAnt, 0, self.nAnt)
            tb.putcol('FLAG_ROW', [False,]*self.nAnt, 0, self.nAnt)
            tb.putcol('MOUNT', ['ALT-AZ',]*self.nAnt, 0, self.nAnt)
            tb.putcol('NAME', [ant.getName() for ant in self.array[0]['ants']], 0, self.nAnt)
            tb.putcol('STATION', [self.siteName,]*self.nAnt, 0, self.nAnt)
            
            for i,ant in enumerate(self.array[0]['ants']):
                #tb.putcell('OFFSET', i, [0.0, 0.0, 0.0])
                tb.putcell('POSITION', i, [ant.x + self.array[0]['center'][0],
                                           ant.y + self.array[0]['center'][1], 
                                           ant.z + self.array[0]['center'][2]])
                #tb.putcell('TYPE', i, 'GROUND-BASED')
                #tb.putcell('DISH_DIAMETER', i, 2.0)
                #tb.putcell('FLAG_ROW', i, False)
                #tb.putcell('MOUNT', i, 'ALT-AZ')
                #tb.putcell('NAME', i, ant.getName())
                #tb.putcell('STATION', i, self.siteName)
                
            tb.flush()
            tb.close()
            
        def _writePolarization(self):
            """
            Write the polarization table.
            """
            
            # Polarization
            
            stks = numpy.array(self.stokes)
            prds = numpy.zeros((2,self.nStokes), dtype=numpy.int32)
            for i,stk in enumerate(self.stokes):
                stks[i] = stk
                if stk > 4:
                    prds[0,i] = ((stk-1) % 4) / 2
                    prds[1,i] = ((stk-1) % 4) % 2
                else:
                    prds[0,i] = 1
                    prds[1,i] = 1
                    
            col1 = tableutil.makearrcoldesc('CORR_TYPE', 0, 1, 
                                            comment='The polarization type for each correlation product, as a Stokes enum.')
            col2 = tableutil.makearrcoldesc('CORR_PRODUCT', 0, 2, 
                                            comment='Indices describing receptors of feed going into correlation')
            col3 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='flag')
            col4 = tableutil.makescacoldesc('NUM_CORR', self.nStokes, 
                                            comment='Number of correlation products')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4])
            tb = table("%s/POLARIZATION" % self.basename, desc, nrow=1, ack=False)
            
            tb.putcell('CORR_TYPE', 0, self.stokes)
            tb.putcell('CORR_PRODUCT', 0, prds.T)
            tb.putcell('FLAG_ROW', 0, False)
            tb.putcell('NUM_CORR', 0, self.nStokes)
            
            tb.flush() 
            tb.close()
            
            # Feed
            
            col1  = tableutil.makearrcoldesc('POSITION', 0.0, 1, 
                                             comment='Position of feed relative to feed reference position', 
                                             keywords={'QuantumUnits':['m','m','m'], 
                                                       'MEASINFO':{'type':'position', 'Ref':'ITRF'}
                                                       })
            col2  = tableutil.makearrcoldesc('BEAM_OFFSET', 0.0, 2, 
                                             comment='Beam position offset (on sky but in antennareference frame)', 
                                             keywords={'QuantumUnits':['rad','rad'], 
                                                       'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                       })
            col3  = tableutil.makearrcoldesc('POLARIZATION_TYPE', 'X', 1, 
                                             comment='Type of polarization to which a given RECEPTOR responds')
            col4  = tableutil.makearrcoldesc('POL_RESPONSE', 1j, 2,
                                             valuetype='complex',
                                             comment='D-matrix i.e. leakage between two receptors')
            col5  = tableutil.makearrcoldesc('RECEPTOR_ANGLE', 0.0, 1,  
                                             comment='The reference angle for polarization', 
                                             keywords={'QuantumUnits':['rad',]})
            col6  = tableutil.makescacoldesc('ANTENNA_ID', 0, 
                                             comment='ID of antenna in this array')
            col7  = tableutil.makescacoldesc('BEAM_ID', -1, 
                                             comment='Id for BEAM model')
            col8  = tableutil.makescacoldesc('FEED_ID', 0, 
                                             comment='Feed id')
            col9  = tableutil.makescacoldesc('INTERVAL', 0.0, 
                                             comment='Interval for which this set of parameters is accurate', 
                                             keywords={'QuantumUnits':['s',]})
            col10 = tableutil.makescacoldesc('NUM_RECEPTORS', 2, 
                                             comment='Number of receptors on this feed (probably 1 or 2)')
            col11 = tableutil.makescacoldesc('SPECTRAL_WINDOW_ID', -1, 
                                             comment='ID for this spectral window setup')
            col12 = tableutil.makescacoldesc('TIME', 0.0, 
                                             comment='Midpoint of time for which this set of parameters is accurate', 
                                             keywords={'QuantumUnits':['s',], 
                                                       'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                       })
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, 
                                            col9, col10, col11, col12])
            tb = table("%s/FEED" % self.basename, desc, nrow=self.nAnt, ack=False)
            
            presp = numpy.zeros((self.nAnt,2,2), dtype=numpy.complex64)
            if self.stokes[0] > 8:
                ptype = numpy.tile(['X', 'Y'], (self.nAnt,1))
                presp[:,0,0] = 1.0
                presp[:,0,1] = 0.0
                presp[:,1,0] = 0.0
                presp[:,1,1] = 1.0
            elif self.stokes[0] > 4:
                ptype = numpy.tile(['R', 'L'], (self.nAnt,1))
                presp[:,0,0] = 1.0
                presp[:,0,1] = -1.0j
                presp[:,1,0] = 1.0j
                presp[:,1,1] = 1.0
            else:
                ptype = numpy.tile(['X', 'Y'], (self.nAnt,1))
                presp[:,0,0] = 1.0
                presp[:,0,1] = 0.0
                presp[:,1,0] = 0.0
                presp[:,1,1] = 1.0
                
            tb.putcol('POSITION', numpy.zeros((self.nAnt,3)), 0, self.nAnt)
            tb.putcol('BEAM_OFFSET', numpy.zeros((self.nAnt,2,2)), 0, self.nAnt)
            tb.putcol('POLARIZATION_TYPE', ptype, 0, self.nAnt)
            tb.putcol('POL_RESPONSE', presp, 0, self.nAnt)
            tb.putcol('RECEPTOR_ANGLE', numpy.zeros((self.nAnt,2)), 0, self.nAnt)
            tb.putcol('ANTENNA_ID', list(range(self.nAnt)), 0, self.nAnt)
            tb.putcol('BEAM_ID', [-1,]*self.nAnt, 0, self.nAnt)
            tb.putcol('FEED_ID', [0,]*self.nAnt, 0, self.nAnt)
            tb.putcol('INTERVAL', [0.0,]*self.nAnt, 0, self.nAnt)
            tb.putcol('NUM_RECEPTORS', [2,]*self.nAnt, 0, self.nAnt)
            tb.putcol('SPECTRAL_WINDOW_ID', [-1,]*self.nAnt, 0, self.nAnt)
            tb.putcol('TIME', [0.0,]*self.nAnt, 0, self.nAnt)
            
            tb.flush() 
            tb.close()
            
        def _writeObservation(self):
            """
            Write the observation table.
            """
            
            # Observation
            
            col1 = tableutil.makearrcoldesc('TIME_RANGE', 0.0, 1, 
                                            comment='Start and end of observation', 
                                            keywords={'QuantumUnits':['s',], 
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            col2 = tableutil.makearrcoldesc('LOG', 'none', 1, 
                                            comment='Observing log')
            col3 = tableutil.makearrcoldesc('SCHEDULE', 'none', 1, 
                                            comment='Observing schedule')
            col4 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='Row flag')
            col5 = tableutil.makescacoldesc('OBSERVER', 'ZASKY', 
                                            comment='Name of observer(s)')
            col6 = tableutil.makescacoldesc('PROJECT', 'ZASKY', 
                                            comment='Project identification string')
            col7 = tableutil.makescacoldesc('RELEASE_DATE', 0.0, 
                                            comment='Release date when data becomes public', 
                                            keywords={'QuantumUnits':['s',], 
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            col8 = tableutil.makescacoldesc('SCHEDULE_TYPE', 'none', 
                                            comment='Observing schedule type')
            col9 = tableutil.makescacoldesc('TELESCOPE_NAME', self.siteName, 
                                            comment='Telescope Name (e.g. WSRT, VLBA)')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9])
            tb = table("%s/OBSERVATION" % self.basename, desc, nrow=1, ack=False)
            
            tStart = astro.taimjd_to_utcjd(self.data[ 0].obsTime) - astro.MJD_OFFSET
            tStop  = astro.taimjd_to_utcjd(self.data[-1].obsTime) - astro.MJD_OFFSET
            
            tb.putcell('TIME_RANGE', 0, [tStart*86400, tStop*86400])
            tb.putcell('LOG', 0, 'Not provided')
            tb.putcell('SCHEDULE', 0, 'Not provided')
            tb.putcell('FLAG_ROW', 0, False)
            tb.putcell('OBSERVER', 0, 'ZASKY')
            tb.putcell('PROJECT', 0, 'ZASKY')
            tb.putcell('RELEASE_DATE', 0, tStop*86400)
            tb.putcell('SCHEDULE_TYPE', 0, 'None')
            tb.putcell('TELESCOPE_NAME', 0, self.siteName)
            
            tb.flush() 
            tb.close()
            
            # Source
            
            arrayGeo = astro.rect_posn(*self.array[0]['center'])
            arrayGeo = astro.get_geo_from_rect(arrayGeo)
            
            obs = ephem.Observer()
            obs.lat = arrayGeo.lat * numpy.pi/180
            obs.lon = arrayGeo.lng * numpy.pi/180
            obs.elev = arrayGeo.elv * numpy.pi/180
            obs.pressure = 0
            
            nameList = []
            posList = []
            sourceID = 0
            for dataSet in self.data:
                if dataSet.pol == self.stokes[0]:
                    utc = astro.taimjd_to_utcjd(dataSet.obsTime)
                    date = astro.get_date(utc)
                    date.hours = 0
                    date.minutes = 0
                    date.seconds = 0
                    utc0 = date.to_jd()
                    
                    obs.date = utc - astro.DJD_OFFSET
                    
                    try:
                        currSourceName = dataSet.source.name
                    except AttributeError:
                        currSourceName = dataSet.source
                    
                    if currSourceName not in nameList:
                        sourceID += 1
                        
                        if dataSet.source == 'z':
                            ## Zenith pointings
                            equ = astro.equ_posn( obs.sidereal_time()*180/numpy.pi, obs.lat*180/numpy.pi )
                            
                            # format 'source' name based on local sidereal time
                            raHms = astro.deg_to_hms(equ.ra)
                            (tsecs, secs) = math.modf(raHms.seconds)
                            
                            name = "ZA%02d%02d%02d%01d" % (raHms.hours, raHms.minutes, int(secs), int(tsecs * 10.0))
                            equPo = astro.get_equ_prec2(equ, utc, astro.J2000_UTC_JD)
                            
                        else:
                            ## Real-live sources (ephem.Body instances)
                            name = dataSet.source.name
                            equ = astro.equ_posn(dataSet.source.ra*180/numpy.pi, dataSet.source.dec*180/numpy.pi)
                            equPo = astro.equ_posn(dataSet.source.a_ra*180/numpy.pi, dataSet.source.a_dec*180/numpy.pi)
                            
                        # J2000 zenith equatorial coordinates
                        posList.append( [equPo.ra*numpy.pi/180, equPo.dec*numpy.pi/180] )
                        
                        # name
                        nameList.append(name)
                        
            nSource = len(nameList)
            
            # Save these for later since we might need them
            self._sourceTable = nameList
            
            col1  = tableutil.makearrcoldesc('DIRECTION', 0.0, 1, 
                                             comment='Direction (e.g. RA, DEC).', 
                                             keywords={'QuantumUnits':['rad','rad'], 
                                                       'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                       })
            col2  = tableutil.makearrcoldesc('PROPER_MOTION', 0.0, 1, 
                                             comment='Proper motion', 
                                             keywords={'QuantumUnits':['rad/s',]})
            col3  = tableutil.makescacoldesc('CALIBRATION_GROUP', 0, 
                                             comment='Number of grouping for calibration purpose.')
            col4  = tableutil.makescacoldesc('CODE', "none", 
                                             comment='Special characteristics of source, e.g. Bandpass calibrator')
            col5  = tableutil.makescacoldesc('INTERVAL', 0.0, 
                                             comment='Interval of time for which this set of parameters is accurate', 
                                             keywords={'QuantumUnits':['s',]})
            col6  = tableutil.makescacoldesc('NAME', "none", 
                                             comment='Name of source as given during observations')
            col7  = tableutil.makescacoldesc('NUM_LINES', 0, 
                                             comment='Number of spectral lines')
            col8  = tableutil.makescacoldesc('SOURCE_ID', 0, 
                                             comment='Source id')
            col9  = tableutil.makescacoldesc('SPECTRAL_WINDOW_ID', -1, 
                                             comment='ID for this spectral window setup')
            col10 = tableutil.makescacoldesc('TIME', 0.0,
                                             comment='Midpoint of time for which this set of parameters is accurate.', 
                                             keywords={'QuantumUnits':['s',], 
                                                       'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                       })
            col11 = tableutil.makearrcoldesc('TRANSITION', 'none', 1, 
                                             comment='Line Transition name')
            col12 = tableutil.makearrcoldesc('REST_FREQUENCY', 1.0, 1, 
                                             comment='Line rest frequency', 
                                             keywords={'QuantumUnits':['Hz',], 
                                                       'MEASINFO':{'type':'frequency', 
                                                                   'Ref':'LSRK'}
                                                       })
            col13 = tableutil.makearrcoldesc('SYSVEL', 1.0, 1, 
                                             comment='Systemic velocity at reference', 
                                             keywords={'QuantumUnits':['m/s',], 
                                                       'MEASINFO':{'type':'radialvelocity', 
                                                                   'Ref':'LSRK'}
                                                       })
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9, 
                                          col10, col11, col12, col13])
            tb = table("%s/SOURCE" % self.basename, desc, nrow=nSource, ack=False)
            
            for i in xrange(nSource):
                tb.putcell('DIRECTION', i, posList[i])
                tb.putcell('PROPER_MOTION', i, [0.0, 0.0])
                tb.putcell('CALIBRATION_GROUP', i, 0)
                tb.putcell('CODE', i, 'none')
                tb.putcell('INTERVAL', i, 0.0)
                tb.putcell('NAME', i, nameList[i])
                tb.putcell('NUM_LINES', i, 0)
                tb.putcell('SOURCE_ID', i, i)
                tb.putcell('SPECTRAL_WINDOW_ID', i, -1)
                tb.putcell('TIME', i, (tStart+tStop)/2*86400)
                #tb.putcell('TRANSITION', i, [])
                #tb.putcell('REST_FREQUENCY', i, [])
                #tb.putcell('SYSVEL', i, [])
                
            tb.flush()
            tb.close()
            
            # Field
            
            col1 = tableutil.makearrcoldesc('DELAY_DIR', 0.0, 2, 
                                            comment='Direction of delay center (e.g. RA, DEC)as polynomial in time.', 
                                            keywords={'QuantumUnits':['rad','rad'], 
                                                      'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                      })
            col2 = tableutil.makearrcoldesc('PHASE_DIR', 0.0, 2, 
                                            comment='Direction of phase center (e.g. RA, DEC).', 
                                            keywords={'QuantumUnits':['rad','rad'], 
                                                      'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                      })
            col3 = tableutil.makearrcoldesc('REFERENCE_DIR', 0.0, 2, 
                                            comment='Direction of REFERENCE center (e.g. RA, DEC).as polynomial in time.', 
                                            keywords={'QuantumUnits':['rad','rad'], 
                                                      'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                      })
            col4 = tableutil.makescacoldesc('CODE', "none", 
                                            comment='Special characteristics of field, e.g. Bandpass calibrator')
            col5 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='Row Flag')
            col6 = tableutil.makescacoldesc('NAME', "none", 
                                            comment='Name of this field')
            col7 = tableutil.makescacoldesc('NUM_POLY', 0, 
                                            comment='Polynomial order of _DIR columns')
            col8 = tableutil.makescacoldesc('SOURCE_ID', 0, 
                                            comment='Source id')
            col9 = tableutil.makescacoldesc('TIME', (tStart+tStop)/2, 
                                            comment='Time origin for direction and rate', 
                                            keywords={'QuantumUnits':['s',],
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9])
            tb = table("%s/FIELD" % self.basename, desc, nrow=nSource, ack=False)
            
            for i in xrange(nSource):
                tb.putcell('DELAY_DIR', i, numpy.array([posList[i],]))
                tb.putcell('PHASE_DIR', i, numpy.array([posList[i],]))
                tb.putcell('REFERENCE_DIR', i, numpy.array([posList[i],]))
                tb.putcell('CODE', i, 'None')
                tb.putcell('FLAG_ROW', i, False)
                tb.putcell('NAME', i, nameList[i])
                tb.putcell('NUM_POLY', i, 0)
                tb.putcell('SOURCE_ID', i, i)
                tb.putcell('TIME', i, (tStart+tStop)/2*86400)
                
            tb.flush()
            tb.close()
            
        def _writeSpectralWindow(self):
            """
            Write the spectral window table.
            """
            
            # Spectral Window
            
            nBand = len(self.freq)
            
            col1  = tableutil.makescacoldesc('MEAS_FREQ_REF', 0, 
                                             comment='Frequency Measure reference')
            col2  = tableutil.makearrcoldesc('CHAN_FREQ', 0.0, 1, 
                                             comment='Center frequencies for each channel in the data matrix', 
                                             keywords={'QuantumUnits':['Hz',], 
                                                       'MEASINFO':{'type':'frequency', 
                                                                   'VarRefCol':'MEAS_FREQ_REF', 
                                                                   'TabRefTypes':['REST','LSRK','LSRD','BARY','GEO','TOPO','GALACTO','LGROUP','CMB','Undefined'],
                                                                   'TabRefCodes':[0,1,2,3,4,5,6,7,8,64]}
                                                       })
            col3  = tableutil.makescacoldesc('REF_FREQUENCY', self.refVal, 
                                             comment='The reference frequency', 
                                             keywords={'QuantumUnits':['Hz',], 
                                                       'MEASINFO':{'type':'frequency', 
                                                                   'VarRefCol':'MEAS_FREQ_REF', 
                                                                   'TabRefTypes':['REST','LSRK','LSRD','BARY','GEO','TOPO','GALACTO','LGROUP','CMB','Undefined'],
                                                                   'TabRefCodes':[0,1,2,3,4,5,6,7,8,64]}
                                                       })
            col4  = tableutil.makearrcoldesc('CHAN_WIDTH', 0.0, 1, 
                                             comment='Channel width for each channel', 
                                             keywords={'QuantumUnits':['Hz',]})
            col5  = tableutil.makearrcoldesc('EFFECTIVE_BW', 0.0, 1, 
                                             comment='Effective noise bandwidth of each channel', 
                                             keywords={'QuantumUnits':['Hz',]})
            col6  = tableutil.makearrcoldesc('RESOLUTION', 0.0, 1, 
                                             comment='The effective noise bandwidth for each channel', 
                                             keywords={'QuantumUnits':['Hz',]})
            col7  = tableutil.makescacoldesc('FLAG_ROW', False, 
                                             comment='flag')
            col8  = tableutil.makescacoldesc('FREQ_GROUP', 1, 
                                             comment='Frequency group')
            col9  = tableutil.makescacoldesc('FREQ_GROUP_NAME', "group1", 
                                             comment='Frequency group name')
            col10 = tableutil.makescacoldesc('IF_CONV_CHAIN', 0, 
                                             comment='The IF conversion chain number')
            col11 = tableutil.makescacoldesc('NAME', "%i channels" % self.nChan, 
                                             comment='Spectral window name')
            col12 = tableutil.makescacoldesc('NET_SIDEBAND', 0, 
                                             comment='Net sideband')
            col13 = tableutil.makescacoldesc('NUM_CHAN', 0, 
                                             comment='Number of spectral channels')
            col14 = tableutil.makescacoldesc('TOTAL_BANDWIDTH', 0.0, 
                                             comment='The total bandwidth for this window', 
                                             keywords={'QuantumUnits':['Hz',]})
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9, 
                                            col10, col11, col12, col13, col14])
            tb = table("%s/SPECTRAL_WINDOW" % self.basename, desc, nrow=nBand, ack=False)
            
            for i,freq in enumerate(self.freq):
                tb.putcell('MEAS_FREQ_REF', i, 0)
                tb.putcell('CHAN_FREQ', i, self.refVal + freq.bandFreq + numpy.arange(self.nChan)*self.channelWidth)
                tb.putcell('REF_FREQUENCY', i, self.refVal)
                tb.putcell('CHAN_WIDTH', i, [freq.chWidth for j in xrange(self.nChan)])
                tb.putcell('EFFECTIVE_BW', i, [freq.chWidth for j in xrange(self.nChan)])
                tb.putcell('RESOLUTION', i, [freq.chWidth for j in xrange(self.nChan)])
                tb.putcell('FLAG_ROW', i, False)
                tb.putcell('FREQ_GROUP', i, i+1)
                tb.putcell('FREQ_GROUP_NAME', i, 'group%i' % (i+1))
                tb.putcell('IF_CONV_CHAIN', i, i)
                tb.putcell('NAME', i, "IF %i, %i channels" % (i+1, self.nChan))
                tb.putcell('NET_SIDEBAND', i, 0)
                tb.putcell('NUM_CHAN', i, self.nChan)
                tb.putcell('TOTAL_BANDWIDTH', i, freq.totalBW)
                
            tb.flush()
            tb.close()
            
        def _writeMain(self):
            """
            Write the main table.
            """
            
            # Main
            
            nBand = len(self.freq)
            
            arrayGeo = astro.rect_posn(*self.array[0]['center'])
            arrayGeo = astro.get_geo_from_rect(arrayGeo)
            
            obs = ephem.Observer()
            obs.lat = arrayGeo.lat * numpy.pi/180
            obs.lon = arrayGeo.lng * numpy.pi/180
            obs.elev = arrayGeo.elv * numpy.pi/180
            obs.pressure = 0
            
            mapper = self.array[0]['mapper']
            
            col1  = tableutil.makearrcoldesc('UVW', 0.0, 1, 
                                             comment='Vector with uvw coordinates (in meters)', 
                                             keywords={'QuantumUnits':['m','m','m'], 
                                                       'MEASINFO':{'type':'uvw', 'Ref':'ITRF'}
                                                       })
            col2  = tableutil.makearrcoldesc('FLAG', False, 2, 
                                             comment='The data flags, array of bools with same shape as data')
            col3  = tableutil.makearrcoldesc('FLAG_CATEGORY', False, 3,  
                                             comment='The flag category, NUM_CAT flags for each datum', 
                                             keywords={'CATEGORY':['',]})
            col4  = tableutil.makearrcoldesc('WEIGHT', 1.0, 1, 
                                             valuetype='float', 
                                             comment='Weight for each polarization spectrum')
            col5  = tableutil.makearrcoldesc('SIGMA', 9999., 1, 
                                             valuetype='float', 
                                             comment='Estimated rms noise for channel with unity bandpass response')
            col6  = tableutil.makescacoldesc('ANTENNA1', 0, 
                                             comment='ID of first antenna in interferometer')
            col7  = tableutil.makescacoldesc('ANTENNA2', 0, 
                                             comment='ID of second antenna in interferometer')
            col8  = tableutil.makescacoldesc('ARRAY_ID', 0, 
                                             comment='ID of array or subarray')
            col9  = tableutil.makescacoldesc('DATA_DESC_ID', 0, 
                                             comment='The data description table index')
            col10 = tableutil.makescacoldesc('EXPOSURE', 0.0, 
                                             comment='he effective integration time', 
                                             keywords={'QuantumUnits':['s',]})
            col11 = tableutil.makescacoldesc('FEED1', 0, 
                                             comment='The feed index for ANTENNA1')
            col12 = tableutil.makescacoldesc('FEED2', 0, 
                                             comment='The feed index for ANTENNA2')
            col13 = tableutil.makescacoldesc('FIELD_ID', 0, 
                                             comment='Unique id for this pointing')
            col14 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                             comment='Row flag - flag all data in this row if True')
            col15 = tableutil.makescacoldesc('INTERVAL', 0.0, 
                                             comment='The sampling interval', 
                                             keywords={'QuantumUnits':['s',]})
            col16 = tableutil.makescacoldesc('OBSERVATION_ID', 0, 
                                             comment='ID for this observation, index in OBSERVATION table')
            col17 = tableutil.makescacoldesc('PROCESSOR_ID', -1, 
                                             comment='Id for backend processor, index in PROCESSOR table')
            col18 = tableutil.makescacoldesc('SCAN_NUMBER', 1, 
                                             comment='Sequential scan number from on-line system')
            col19 = tableutil.makescacoldesc('STATE_ID', -1, 
                                             comment='ID for this observing state')
            col20 = tableutil.makescacoldesc('TIME', 0.0, 
                                             comment='Modified Julian Day', 
                                             keywords={'QuantumUnits':['s',],
                                                       'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                       })
            col21 = tableutil.makescacoldesc('TIME_CENTROID', 0.0, 
                                             comment='Modified Julian Day', 
                                             keywords={'QuantumUnits':['s',],
                                                       'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                       })
            col22 = tableutil.makearrcoldesc("DATA", 0j, 2, 
                                             valuetype='complex',
                                             comment='The data column')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9, 
                                            col10, col11, col12, col13, col14, col15, col16, 
                                            col17, col18, col19, col20, col21, col22])
            tb = table("%s" % self.basename, desc, nrow=0, ack=False)
            
            i = 0
            s = 1
            _sourceTable = []
            for dataSet in self.data:
                # Sort the data by packed baseline
                try:
                    order
                except NameError:
                    order = dataSet.argsort(mapper=mapper, shift=16)
                    
                # Deal with defininig the values of the new data set
                if dataSet.pol == self.stokes[0]:
                    ## Figure out the new date/time for the observation
                    utc = astro.taimjd_to_utcjd(dataSet.obsTime)
                    date = astro.get_date(utc)
                    date.hours = 0
                    date.minutes = 0
                    date.seconds = 0
                    utc0 = date.to_jd()
                    
                    ## Update the observer so we can figure out where the source is
                    obs.date = utc - astro.DJD_OFFSET
                    if dataSet.source == 'z':
                        ### Zenith pointings
                        equ = astro.equ_posn( obs.sidereal_time()*180/numpy.pi, obs.lat*180/numpy.pi )
                        
                        ### format 'source' name based on local sidereal time
                        raHms = astro.deg_to_hms(equ.ra)
                        (tsecs, secs) = math.modf(raHms.seconds)
                        name = "ZA%02d%02d%02d%01d" % (raHms.hours, raHms.minutes, int(secs), int(tsecs * 10.0))
                    else:
                        ### Real-live sources (ephem.Body instances)
                        name = dataSet.source.name
                        
                    ## Update the source ID
                    try:
                        sourceID = _sourceTable.index(name)
                    except ValueError:
                        _sourceTable.append(name)
                        sourceID = _sourceTable.index(name)
                        
                    ## Compute the uvw coordinates of all baselines
                    if dataSet.source == 'z':
                        HA = 0.0
                        dec = equ.dec
                    else:
                        HA = (obs.sidereal_time() - dataSet.source.ra) * 12/numpy.pi
                        dec = dataSet.source.dec * 180/numpy.pi
                    uvwCoords = dataSet.getUVW(HA, dec, obs)
                    
                    ## Populate the metadata
                    ### Add in the baselines
                    try:
                        ant1List
                        ant2List
                    except NameError:
                        a1List, a2List = [], []
                        for o in order:
                            antenna1, antenna2 = dataSet.baselines[o]
                            a1 = mapper.index(antenna1.stand.id)
                            a2 = mapper.index(antenna2.stand.id)
                            a1List.append( a1 )
                            a2List.append( a2 )
                        ant1List = a1List
                        ant2List = a2List
                        
                    ### Add in the new u, v, and w coordinates
                    uvwList = uvwCoords[order,:]
                    
                    ### Add in the new date/time and integration time
                    #timeList = [utc - astro.MJD_OFFSET for bl in dataSet.baselines]
                    intTimeList = [dataSet.intTime for bl in dataSet.baselines]
                    timeList = [(utc - astro.MJD_OFFSET)*86400 + dataSet.intTime/2.0 for bl in dataSet.baselines]
                    
                    ### Add in the new new source ID and name
                    sourceList = [sourceID for bl in dataSet.baselines]
                     
                    ### Zero out the visibility data
                    try:
                        matrix.shape = (len(order), self.nStokes, nBand*self.nChan)
                        matrix *= 0.0
                    except NameError:
                        matrix = numpy.zeros((len(order), self.nStokes, self.nChan*nBand), dtype=numpy.complex64)
                        
                # Save the visibility data in the right order
                matrix[:,self.stokes.index(dataSet.pol),:] = dataSet.visibilities[order,:]
                
                # Deal with saving the data once all of the polarizations have been added to 'matrix'
                if dataSet.pol == self.stokes[-1]:
                    nBL = uvwList.shape[0]
                    tb.addrows(nBand*nBL)
                    
                    matrix.shape = (len(order), self.nStokes, nBand, self.nChan)
                    
                    for j in xrange(nBand):
                        fg = numpy.zeros((nBL,self.nStokes,self.nChan), dtype=numpy.bool)
                        fc = numpy.zeros((nBL,self.nStokes,self.nChan,1), dtype=numpy.bool)
                        wg = numpy.ones((nBL,self.nStokes))
                        sg = numpy.ones((nBL,self.nStokes))*9999
                        
                        tb.putcol('UVW', uvwList, i, nBL)
                        tb.putcol('FLAG', fg.transpose(0,2,1), i, nBL)
                        tb.putcol('FLAG_CATEGORY', fc.transpose(0,3,2,1), i, nBL)
                        tb.putcol('WEIGHT', wg, i, nBL)
                        tb.putcol('SIGMA', sg, i, nBL)
                        tb.putcol('ANTENNA1', ant1List, i, nBL)
                        tb.putcol('ANTENNA2', ant2List, i, nBL)
                        tb.putcol('ARRAY_ID', [0,]*nBL, i, nBL)
                        tb.putcol('DATA_DESC_ID', [j,]*nBL, i, nBL)
                        tb.putcol('EXPOSURE', intTimeList, i, nBL)
                        tb.putcol('FEED1', [0,]*nBL, i, nBL)
                        tb.putcol('FEED2', [0,]*nBL, i, nBL)
                        tb.putcol('FIELD_ID', sourceList, i, nBL)
                        tb.putcol('FLAG_ROW', [False,]*nBL, i, nBL)
                        tb.putcol('INTERVAL', intTimeList, i, nBL)
                        tb.putcol('OBSERVATION_ID', [0,]*nBL, i, nBL)
                        tb.putcol('PROCESSOR_ID', [-1,]*nBL, i, nBL)
                        tb.putcol('SCAN_NUMBER', [s,]*nBL, i, nBL)
                        tb.putcol('STATE_ID', [-1,]*nBL, i, nBL)
                        tb.putcol('TIME', timeList, i, nBL)
                        tb.putcol('TIME_CENTROID', timeList, i, nBL)
                        tb.putcol('DATA', matrix[...,j,:].transpose(0,2,1), i, nBL)
                        i += nBL
                    s += 1
                    
            tb.flush()
            tb.close()
            
            # Data description
            
            col1 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='Flag this row')
            col2 = tableutil.makescacoldesc('POLARIZATION_ID', 0, 
                                            comment='Pointer to polarization table')
            col3 = tableutil.makescacoldesc('SPECTRAL_WINDOW_ID', 0, 
                                            comment='Pointer to spectralwindow table')
            
            desc = tableutil.maketabdesc([col1, col2, col3])
            tb = table("%s/DATA_DESCRIPTION" % self.basename, desc, nrow=nBand, ack=False)
            
            for i in xrange(nBand):
                tb.putcell('FLAG_ROW', i, False)
                tb.putcell('POLARIZATION_ID', i, 0)
                tb.putcell('SPECTRAL_WINDOW_ID', i, i)
                
            tb.flush()
            tb.close()
            
        def _writeMisc(self):
            """
            Write the other tables that are part of the measurement set but 
            don't contain anything by default.
            """
            
            # Flag command
            
            col1 = tableutil.makescacoldesc('TIME', 0.0, 
                                            comment='Midpoint of interval for which this flag is valid', 
                                            keywords={'QuantumUnits':['s',], 
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            col2 = tableutil.makescacoldesc('INTERVAL', 0.0, 
                                            comment='Time interval for which this flag is valid', 
                                            keywords={'QuantumUnits':['s',]})
            col3 = tableutil.makescacoldesc('TYPE', 'flag', 
                                            comment='Type of flag (FLAG or UNFLAG)')
            col4 = tableutil.makescacoldesc('REASON', 'reason', 
                                            comment='Flag reason')
            col5 = tableutil.makescacoldesc('LEVEL', 0, 
                                            comment='Flag level - revision level')
            col6 = tableutil.makescacoldesc('SEVERITY', 0, 
                                            comment='Severity code (0-10)')
            col7 = tableutil.makescacoldesc('APPLIED', False, 
                                            comment='True if flag has been applied to main table')
            col8 = tableutil.makescacoldesc('COMMAND', 'command', 
                                            comment='Flagging command')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8])
            tb = table("%s/FLAG_CMD" % self.basename, desc, nrow=0, ack=False)
            
            tb.flush()
            tb.close()
            
            # History
            
            col1 = tableutil.makescacoldesc('TIME', 0.0, 
                                            comment='Timestamp of message', 
                                            keywords={'QuantumUnits':['s',], 
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            col2 = tableutil.makescacoldesc('OBSERVATION_ID', 0, 
                                            comment='Observation id (index in OBSERVATION table)')
            col3 = tableutil.makescacoldesc('MESSAGE', 'message', 
                                            comment='Log message')
            col4 = tableutil.makescacoldesc('PRIORITY', 'NORMAL', 
                                            comment='Message priority')
            col5 = tableutil.makescacoldesc('ORIGIN', 'origin', 
                                            comment='(Source code) origin from which message originated')
            col6 = tableutil.makescacoldesc('OBJECT_ID', 0, 
                                            comment='Originating ObjectID')
            col7 = tableutil.makescacoldesc('APPLICATION', 'application', 
                                            comment='Application name')
            col8 = tableutil.makearrcoldesc('CLI_COMMAND', 'command', 1, 
                                            comment='CLI command sequence')
            col9 = tableutil.makearrcoldesc('APP_PARAMS', 'params', 1, 
                                            comment='Application parameters')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9])
            tb = table("%s/HISTORY" % self.basename, desc, nrow=0, ack=False)
            
            tb.flush()
            tb.close()
            
            # POINTING
            
            col1 = tableutil.makescacoldesc('ANTENNA_ID', 0, 
                                            comment='Antenna Id')
            col2 = tableutil.makescacoldesc('TIME', 0.0, 
                                            comment='Time interval midpoint', 
                                            keywords={'QuantumUnits':['s',], 
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            col3 = tableutil.makescacoldesc('INTERVAL', 0.0, 
                                            comment='Time interval', 
                                            keywords={'QuantumUnits':['s',]})
            col4 = tableutil.makescacoldesc('NAME', 'name', 
                                            comment='Pointing position name')
            col5 = tableutil.makescacoldesc('NUM_POLY', 0, 
                                            comment='Series order')
            col6 = tableutil.makescacoldesc('TIME_ORIGIN', 0.0, 
                                            comment='Time origin for direction', 
                                            keywords={'QuantumUnits':['s',], 
                                                      'MEASINFO':{'type':'epoch', 'Ref':'UTC'}
                                                      })
            col7 = tableutil.makearrcoldesc('DIRECTION', 0.0, 2, 
                                            comment='Antenna pointing direction as polynomial in time', 
                                            keywords={'QuantumUnits':['rad','rad'], 
                                                      'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                      })
            col8 = tableutil.makearrcoldesc('TARGET', 0.0, 2, 
                                            comment='target direction as polynomial in time',
                                            keywords={'QuantumUnits':['rad','rad'], 
                                                      'MEASINFO':{'type':'direction', 'Ref':'J2000'}
                                                      })
            col9 = tableutil.makescacoldesc('TRACKING', True, 
                                            comment='Tracking flag - True if on position')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7, col8, col9])
            tb = table("%s/POINTING" % self.basename, desc, nrow=0, ack=False)
            
            tb.flush()
            tb.close()
            
            # Processor
            
            col1 = tableutil.makescacoldesc('TYPE', 'type', 
                                            comment='Processor type')
            col2 = tableutil.makescacoldesc('SUB_TYPE', 'subtype', 
                                            comment='Processor sub type')
            col3 = tableutil.makescacoldesc('TYPE_ID', 0, 
                                            comment='Processor type id')
            col4 = tableutil.makescacoldesc('MODE_ID', 0, 
                                            comment='Processor mode id')
            col5 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='flag')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5])
            tb = table("%s/PROCESSOR" % self.basename, desc, nrow=0, ack=False)
            
            tb.flush()
            tb.close()
            
            # State
            
            col1 = tableutil.makescacoldesc('SIG', True, 
                                            comment='True for a source observation')
            col2 = tableutil.makescacoldesc('REF', False, 
                                            comment='True for a reference observation')
            col3 = tableutil.makescacoldesc('CAL', 0.0, 
                                            comment='Noise calibration temperature', 
                                            keywords={'QuantumUnits':['K',]})
            col4 = tableutil.makescacoldesc('LOAD', 0.0, 
                                            comment='Load temperature', 
                                            keywords={'QuantumUnits':['K',]})
            col5 = tableutil.makescacoldesc('SUB_SCAN', 0, 
                                            comment='Sub scan number, relative to scan number')
            col6 = tableutil.makescacoldesc('OBS_MODE', 'mode', 
                                            comment='Observing mode, e.g., OFF_SPECTRUM')
            col7 = tableutil.makescacoldesc('FLAG_ROW', False, 
                                            comment='Row flag')
            
            desc = tableutil.maketabdesc([col1, col2, col3, col4, col5, col6, col7])
            tb = table("%s/STATE" % self.basename, desc, nrow=0, ack=False)
            
            tb.flush()
            tb.close()
            
except ImportError:
    import warnings
    warnings.warn('Cannot import casacore.tables, MS support disabled', ImportWarning)
    
    class MS(object):
        """
        Class for storing visibility data and writing the data, along with array
        geometry, frequency setup, etc., to a CASA measurement set.
        """
        
        def __init__(self, filename, refTime=0.0, verbose=False, memmap=None, clobber=False):
            """
            Initialize a new FITS IDI object using a filename and a reference time 
            given in seconds since the UNIX 1970 ephem, a python datetime object, or a 
            string in the format of 'YYYY-MM-DDTHH:MM:SS'.
            
            .. versionchanged:: 1.1.2
                Added the 'memmap' and 'clobber' keywords to control if the file
                is memory mapped and whether or not to overwrite an existing file, 
                respectively.
            """
            
            raise RuntimeError("Cannot import casacore.tables, MS support disabled")
