from __future__ import absolute_import
import os
import numpy
import traceback
import string
import functools
import re
import abc
import datetime
import contextlib

from casatasks.private.casa_transition import is_CASA6
if is_CASA6:
    from casatools import quanta, table, calibrater, imager
    from casatools import ms as mstool
    from casatools.platform import bytes2str
    from casatasks import casalog
else:
    from casac import casac
    from taskinit import casalog, gentools
    # make CASA5 tools constructors look like CASA6 tools
    from taskinit import qatool as quanta
    from taskinit import tbtool as table
    from taskinit import cbtool as calibrater
    from taskinit import imtool as imager
    from taskinit import mstool

    #import asap as sd
    #from asap import _to_list
    #from asap.scantable import is_scantable, is_ms, scantable
    #import rasterutil

@contextlib.contextmanager
def toolmanager(vis, ctor, *args, **kwargs):
    if is_CASA6:
        # this is the only syntax allowed in CASA6, code in CASA6 should be converted to
        # call this method with a tool constructor directly
        tool = ctor()
    else:
        # CASA5 code can invoke this with a tool name, shared CASA5 and CASA6 source
        # uses the CASA6 syntax - use callable to tell the difference
        if callable(ctor):
            tool = ctor()
        else:
            # assume the argument is string and use it to get the appropriate tool constructor
            # the original argument name here was 'tooltype'
            tool = gentools([ctor])[0]

    tool.open(vis, *args, **kwargs)
    try:
        yield tool
    finally:
        tool.close()

def tbmanager(vis, *args, **kwargs):
    return toolmanager(vis, table, *args, **kwargs)

def cbmanager(vis, *args, **kwargs):
    return toolmanager(vis, calibrater, *args, **kwargs)

def is_ms(filename):
    if (os.path.isdir(filename) and os.path.exists(filename+'/table.info') and os.path.exists(filename+'/table.dat')):
        f = open(filename + '/table.info')
        if is_CASA6:
            l = bytes2str(f.readline( ))
        else:
            l = f.readline()
        f.close()
        if (l.find('Measurement Set') != -1):
            return True
        else:
            return False
    else:
        return False

@contextlib.contextmanager
def table_selector(table, taql, *args, **kwargs):
    with tbmanager(table, *args, **kwargs) as tb:
        tsel = tb.query(taql)
        try:
            yield tsel
        finally:
            tsel.close()

def asaptask_decorator(func):
    """
    This is a decorator function for sd tasks.
    Currently the decorator does:

       1) set origin to the logger
       2) deprecation warning of ASAP task
       3) handle exception
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # set origin
        casalog.origin(func.__name__)
        casalog.post("### DEPRECATION WARNING: task %s will be removed from CASA 5.1. Please refer to documentation for current task information and update your script ###" % func.__name__,'WARN')

        retval = None
        # Any errors are handled outside the task.
        # however, the implementation below is effectively
        # equivalent to handling it inside the task.
        try:
            # execute task
            retval = func(*args, **kwargs)
        except Exception as e:
            traceback_info = format_trace(traceback.format_exc())
            casalog.post(traceback_info,'SEVERE')
            casalog.post(str(e),'ERROR')
            raise
        return retval
    return wrapper

def sdtask_decorator(func):
    """
    This is a decorator function for sd tasks.
    Currently the decorator does:

       1) set origin to the logger
       2) handle exception

    So, you don't need to set origin in the task any more.
    Also, you don't need to write anything about error
    handling in the task. If you have something to do
    at the end of the task execution, those should be
    written in the destructor of worker class, not in
    the 'finally' block.
    """
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # set origin
        casalog.origin(func.__name__)

        retval = None
        # Any errors are handled outside the task.
        # however, the implementation below is effectively
        # equivalent to handling it inside the task.
        try:
            # execute task
            retval = func(*args, **kwargs)
        except Exception as e:
            traceback_info = format_trace(traceback.format_exc())
            casalog.post(traceback_info,'SEVERE')
            casalog.post(str(e),'ERROR')
            raise
        return retval
    return wrapper

def format_trace(s):
    wexists=True
    regex='.*sdutil\.py.*in wrapper.*'
    retval = s
    while wexists:
        ss = retval.split('\n')
        wexists = False
        for i in range(len(ss)):
            if re.match(regex,ss[i]):
                ss = ss[:i] + ss[i+2:]
                wexists = True
                break
        retval = '\n'.join(ss)
    return retval

class sdtask_manager(object):
    def __init__(self, cls, args):
        self.cls = cls
        self.args = args

    def __enter__(self):
        self.obj = self.cls(**self.args)
        return self.obj

    def __exit__(self, exc_type, exc_value, traceback):
        # explicitly call destructure to make sure it is called here
        self.obj.__del__()
        del self.obj
        if exc_type:
            return False
        else:
            return True

class sdtask_interface(object):
    """
    The sdtask_interface defines a common interface for sdtask_worker
    class. All worker classes can be used as follows:

       worker = sdtask_worker(**locals())
       worker.initialize()
       worker.execute()
       worker.finalize()
       del worker

    Derived classes must implement the above three methods: initialize(),
    execute(), and finalize().
    """
    __metaclass__ = abc.ABCMeta

    def __init__(self, **kwargs):
        for (k,v) in kwargs.items():
            setattr(self, k, v)
        # special treatment for selection parameters
        select_params = ['scan', 'pol','beam']
        for param in select_params:
            if hasattr(self, param):
                setattr(self, param+'no', getattr(self, param))
                #print("renaming self.%s -> self.%sno='%s'" % (param, param, getattr(self, param)))
                delattr(self, param)

    def __del__(self):
        pass

    @abc.abstractmethod
    def initialize(self):
        raise NotImplementedError('initialize is abstract method')

    @abc.abstractmethod
    def execute(self):
        raise NotImplementedError('execute is abstract method')

    @abc.abstractmethod
    def finalize(self):
        raise NotImplementedError('finalize is abstract method')

class sdtask_template(sdtask_interface):
    """
    The sdtask_template is a template class for standard worker
    class of non-imaging sdtasks. It partially implement initialize()
    and finalize() using internal methods that must be implemented
    in the derived classes. For initialize(), derived classes
    must implement initialize_scan(), which initializes scantable
    object and set it to self.scan. You can implement paramter_check()
    to do any task specific parameter check in initialize().
    For finalize(), derived classes can implement save() and cleanup().
    """
    __metaclass__ = abc.ABCMeta

    def __init__(self, **kwargs):
        super(sdtask_template,self).__init__(**kwargs)
        if not hasattr(self, 'outform'):
            self.outform = 'undefined'
        ### WORKAROUND to support old telescopeparm parameter
        if hasattr(self, 'telescopeparm'):
            self.telescopeparam = self.telescopeparm
        self.is_disk_storage = True #(sd.rcParams['scantable.storage'] == 'disk')
        # attribute for tasks that return any result
        self.result = None

    def __del__(self, base=sdtask_interface):
        self.cleanup()
        super(sdtask_template,self).__del__()

    def initialize(self):
        if hasattr(self, 'infile'):
            assert_infile_exists(self.infile)
        elif hasattr(self, 'infiles'):
            if isinstance(self.infiles,str):
                assert_infile_exists(self.infiles)
            else:
                for f in self.infiles:
                    assert_infile_exists(f)
        if hasattr(self, 'suffix'):
            if hasattr(self, 'infile'):
                self.project = get_default_outfile_name(self.infile,self.outfile,self.suffix)
            elif hasattr(self, 'infiles'):
                self.project = get_default_outfile_name(self.infiles[0],self.outfile,self.suffix)
        elif hasattr(self, 'outfile') and len(self.outfile) > 0:
            self.project = self.outfile
##         if hasattr(self, 'outfile') and len(self.outfile) > 0:
##             assert_outfile_canoverwrite_or_nonexistent(self.outfile,self.outform,self.overwrite)
        if hasattr(self, 'project'):
            assert_outfile_canoverwrite_or_nonexistent(self.project,self.outform,self.overwrite)

        # task specific parameter check
        self.parameter_check()

        # raster setting
        if hasattr(self, 'rastermode') and hasattr(self, 'raster'):
            if self.rastermode.upper() == "ROW":
                self.rasterrow = self.raster
            else:
                self.rasteriter = self.raster

        # set self.scan
        self.initialize_scan()

    def finalize(self):
        # Save result on disk if necessary
        self.save()

    @abc.abstractmethod
    def initialize_scan(self):
        # initialize scantable object to work with
        raise NotImplementedError('initialize_scan is abstract method')

    def parameter_check(self):
        # do nothing by default
        pass

    def save(self):
        # do nothing by default
        pass

    def cleanup(self):
        # do nothing by default
        pass

    """
    def get_selector(self, scantb=None):

        #Get selector instance that select scan(s), IF(s), polarization(s),
        #beam(s), field(s), and timerange set to this class.
        #This method parses attributes of string selection parameter,
        #scan(no), spw, pol(no), and beam(no), and converts to lists of
        #selected IDs, scanlist, iflist, pollist, and beamlist. The lists
        #are saved as attributes of this class.
        #Available range of IDs and time are obtained from a scantable.
        #
        #Parameter
        #    scantb : input scantable instance to get ID and time range from.
        #             The scantable defined as self.scan is used if scantb
        #             is not defined (default).
        #
        if not scantb:
            if hasattr(self,'scan') and isinstance(self.scan, scantable):
                scantb = self.scan
            else:
                raise Exception, "Internal Error. No valid scantable."

        attributes = ['scanno','spw','polno','beamno','field']
        for a in attributes:
            if not hasattr(self,a): setattr(self,a,"")


        self.scanlist = scantb.parse_idx_selection("SCAN",self.scanno)
        self.iflist = []
        if self.spw != "":
            rfreq = None if not hasattr(self, 'restfreq') else self.restfreq
            frame = None if (not hasattr(self, 'frame') or self.frame == '') else self.frame
            doppler = None if (not hasattr(self, 'doppler') or self.doppler == '') else self.doppler
            masklist = scantb.parse_spw_selection(self.spw, restfreq=rfreq,
                                                  frame=frame, doppler=doppler)
            if len(masklist) == 0:
                raise ValueError, "Invalid spectral window selection. Selection contains no data."
            self.iflist = masklist.keys()
        self.pollist = scantb.parse_idx_selection("POL",self.polno)
        self.beamlist = scantb.parse_idx_selection("BEAM",self.beamno)

        attributes = ['scanlist','iflist','pollist','beamlist',
                      'rowlist','field']
        for a in attributes:
            if not hasattr(self,a): setattr(self,a,None)
        selector = get_selector(in_scans=self.scanlist,
                                in_ifs=self.iflist,
                                in_pols=self.pollist,
                                in_beams=self.beamlist,
                                in_rows=self.rowlist,
                                in_field=self.field)

        # CAS-5496 selection by timerange
        if hasattr(self, 'timerange') and len(self.timerange) > 0:
            # base scantable
            if hasattr(self, 'infile'):
                base_table = self.infile
            elif hasattr(self, 'infiles'):
                base_table = self.infiles[0]
            else:
                base_table = None
            taql_for_timerange = select_by_timerange(base_table, self.timerange)
            query_org = selector.get_query()
            if len(query_org) > 0:
                selector.set_query(' && '.join([query_org, taql_for_timerange]))
            else:
                selector.set_query(taql_for_timerange)
            casalog.post('taql: \'%s\''%(selector.get_query()), priority='DEBUG')

        return selector
    """

    """
    def select_by_raster(self, base_selector, scantb=None):
        if not scantb:
            if hasattr(self,'scan') and isinstance(self.scan, scantable):
                scantb = self.scan
            else:
                raise Exception, "Internal Error. No valid scantable."

        if base_selector is None:
            selector = sd.selector()
        else:
            selector = sd.selector(base_selector)

        row_selection = (hasattr(self, 'rasterrow') and len(self.rasterrow) > 0)
        any_selection = row_selection or \
                        (hasattr(self, 'rasteriter') and len(self.rasteriter) > 0)

        # CAS-6702 selection by raster row
        if any_selection:
            if hasattr(self, 'infile'):
                # nominal pair of science spw and existing polarization
                sel_org = scantb.get_selection()
                sel = sd.selector(types=[0]) + selector # select pson data
                scantb.set_selection(sel)
                ifnos = scantb.getifnos()
                nominal_spw = -1
                for ifno in ifnos:
                    # ignore channel-averaged spw and WVR
                    if scantb.nchan(ifno) > 4:
                        nominal_spw = ifno
                        break
                if nominal_spw > -1:
                    sel.set_ifs(nominal_spw)
                    scantb.set_selection(sel)
                    nominal_pol = scantb.getpolnos()[0]

                    # raster row utility
                    casalog.post('nominal spw and pol = (%s,%s)'%(nominal_spw,nominal_pol))
                    r = rasterutil.Raster(scantb)
                    r.detect(spw=nominal_spw, pol=nominal_pol)
                    if row_selection:
                        casalog.post('raster row=%s (type %s)'%(self.rasterrow,type(self.rasterrow)))
                        raster_list = parse_idx_selection(self.rasterrow, 0, r.ngap-1)
                        if len(raster_list) > 0:
                            query_list = (r.astaql(i,None) for i in raster_list if i >= 0 and i < r.ngap)
                    else:
                        casalog.post('map iteration=%s (type %s)'%(self.rasteriter,type(self.rasteriter)))
                        raster_list = parse_idx_selection(self.rasteriter, 0, r.ngap_raster-1)
                        if len(raster_list) > 0:
                            query_list = (r.astaql(None,i) for i in raster_list if i >= 0 and i < r.ngap_raster)
                    #raster_list = parse_idx_selection(self.rasterrow, 0, r.ngap-1)
                    #casalog.post('raster_list=%s'%(raster_list))
                    if len(raster_list) > 0:
                        #query_list = (r.astaql(i) for i in raster_list if i >= 0 and i < r.ngap)
                        in_parenthesis = lambda x: '('+x+')'
                        taql_for_raster = ' || '.join(map(in_parenthesis, query_list))
                        casalog.post('taql_for_raster=\'%s\''%(taql_for_raster))
                        query_org = selector.get_query()
                        if len(query_org) > 0:
                            selector.set_query(' && '.join(map(in_parenthesis, [query_org, taql_for_raster])))
                        else:
                            selector.set_query(taql_for_raster)
                        casalog.post('taql: \'%s\''%(selector.get_query()), priority='INFO')

        return selector
    """
    """
    def set_selection(self, scantb=None):

        #Set selection that select scan(s), IF(s), polarization(s),
        #beam(s), field(s), and timerange set to this class.
        #This method parses attributes of string selection parameter,
        #scan(no), spw, pol(no), and beam(no), and applies the selection
        #to a scantable.
        #
        #Parameter
        #    scantb : A scantable instance to apply selection.
        #             The scantable defined as self.scan is used if scantb
        #             is not defined (default).

        if not scantb:
            if hasattr(self,'scan') and isinstance(self.scan, scantable):
                scantb = self.scan
            else:
                raise Exception, "Internal Error. No valid scantable."
        in_ifno = scantb.getifnos()
        # apply selection
        scantb.set_selection(self.get_selector(scantb))
        # filter restfreq for future use.
        if self.restfreq not in ("", []) and \
               type(self.restfreq) in (list, tuple) and \
               len(self.restfreq) == len(in_ifno):
            # Per IF rest frequency. Need to filter selected restfreqs for future use.
            sel_ifno = scantb.getifnos()
            rf = []
            for if_idx in sel_ifno:
                rf.append(self.restfreq[in_ifno.index(if_idx)])
            self.selected_restfreq = rf
    """

    def assert_no_channel_selection_in_spw(self, mode='warn'):
        """
        Assert 'spw' does not have channel selection
        Returns True if spw string does not have channel selecton
        Returns False or raises an error if spw has channel selection

        Available modes are
            'result' : just returns the result (true or false)
            'warn'   : warn user if channel selection is set
            'error'  : raise an error if channel seledtion is set
        """
        if not hasattr(self, 'spw'): return True
        # find pattern spw = 'spwID:channelRange'
        has_chan = (self.spw.find(':') > -1)
        ## TODO: also need to do something with "###Hz" and "###km/s"?
        #quantap = re.compile('[a-z]', re.IGNORECASE)
        #has_chan = has_chan or len(quantap.findall(self.spw))
        if has_chan:
            if mode.upper().startswith('E'):
                raise ValueError("spw parameter should not contain channel selection.")
            elif mode.upper().startswith('W'):
                casalog.post("Channel selection found in spw parameter. It would be ignored", priority='WARN')

        return has_chan


    def set_to_scan(self):
        if hasattr(self,'fluxunit'):
            set_fluxunit(self.scan, self.fluxunit, self.telescopeparam)
        if hasattr(self,'frame'):
            set_freqframe(self.scan, self.frame)
        if hasattr(self,'doppler'):
            set_doppler(self.scan,self.doppler)
        if hasattr(self,'specunit'):
            set_spectral_unit(self.scan,self.specunit)
            if hasattr(self,'restfreq'):
                rfset = self.restfreq not in ['',[]]
                if self.specunit == 'km/s':
                    if len(self.scan.get_restfreqs().values()[0]) == 0 and not rfset:
                        raise Exception('Restfreq must be given')
                    if rfset:
                        rf = self.restfreq if not hasattr(self, 'selected_restfreq') else self.selected_restfreq
                        fval = normalise_restfreq(rf)
                        casalog.post( 'Set rest frequency to %s Hz' % str(fval) )
                        self.scan.set_restfreqs(freqs=fval)
        elif hasattr(self, 'spw') and self.spw != '' and \
                hasattr(self,'restfreq'):
           if self.restfreq not in ['',[]]:
               rf = self.restfreq if not hasattr(self, 'selected_restfreq') else self.selected_restfreq
               fval = normalise_restfreq(rf)
               casalog.post( 'Set rest frequency to %s Hz' % str(fval) )
               self.scan.set_restfreqs(freqs=fval)

class sdtask_template_imaging(sdtask_interface):
    """
    The sdtask_template_imaging is a template class for worker
    class of imaging related sdtasks. It partially implement initialize()
    and finalize() using internal methods that must be implemented
    in the derived classes. For initialize(), derived classes
    must implement compile(), which sets up imaging parameters.
    You can implement paramter_check() to do any task specific parameter
    check in initialize().
    For finalize(), derived classes can implement cleanup().
    """
    def __init__(self, **kwargs):
        super(sdtask_template_imaging,self).__init__(**kwargs)
        self.is_table_opened = False
        self.is_imager_opened = False
        self.table = table()
        self.imager = imager()
        # workaround for sdtpimaging
        if not hasattr(self, 'infiles') and hasattr(self, 'infile'):
            self.infiles = [self.infile]

        self.__set_infiles()
        self.__set_subtable_name()

    def __del__(self, base=sdtask_interface):
        # table and imager must be closed when the instance
        # is deleted
        self.close_table()
        self.close_imager()
        self.cleanup()
        super(sdtask_template_imaging,self).__del__()

    def open_table(self, name, nomodify=True):
        if self.is_table_opened:
            casalog.post('Close table before re-open', priority='WARN')
            return
        self.table.open(name, nomodify=nomodify)
        self.is_table_opened = True

    def close_table(self):
        if self.is_table_opened:
            self.table.close()
        self.is_table_opened = False

    def open_imager(self, name=''):
        if self.is_imager_opened:
            casalog.post('Close imager before re-open', priority='WARN')
            return
        self.imager.open(name)
        self.is_imager_opened = True

    def close_imager(self):
        if self.is_imager_opened:
            self.imager.close()
        self.is_imager_opened = False

    def initialize(self):
        # infiles must be MS
        for idx in range(len(self.infiles)):
            if not is_ms(self.infiles[idx]):
                msg='input data sets must be in MS format'
                raise Exception(msg)

        self.parameter_check()
        self.compile()

    def finalize(self):
        pass

    def parameter_check(self):
        pass

    def compile(self):
        pass

    def cleanup(self):
        pass

    def __set_subtable_name(self):
        self.open_table(self.infiles[0])
        keys = self.table.getkeywords()
        self.close_table()
        self.field_table = get_subtable_name(keys['FIELD'])
        self.spw_table = get_subtable_name(keys['SPECTRAL_WINDOW'])
        self.source_table = get_subtable_name(keys['SOURCE'])
        self.antenna_table = get_subtable_name(keys['ANTENNA'])
        self.polarization_table = get_subtable_name(keys['POLARIZATION'])
        self.observation_table = get_subtable_name(keys['OBSERVATION'])
        self.pointing_table = get_subtable_name(keys['POINTING'])
        self.data_desc_table = get_subtable_name(keys['DATA_DESCRIPTION'])
        self.pointing_table = get_subtable_name(keys['POINTING'])

    def __set_infiles(self):
        if type(self.infiles) == str:
            self.infiles = [self.infiles]


class sdtask_engine(sdtask_interface):
    def __init__(self, worker):
        # set worker instance to work with
        self.worker = worker

        # copy worker attributes except scan
        # use worker.scan to access scantable
        for (k,v) in self.worker.__dict__.items():
            if k != 'scan':
                setattr(self, k, v)
        #super(sdtask_engine,self).__init__(**self.worker.__dict__)
        #if hasattr(self,'scan'): del self.scan

def get_abspath(filename):
    return os.path.abspath(expand_path(filename))

def expand_path(filename):
    return os.path.expanduser(os.path.expandvars(filename))

def assert_infile_exists(infile=None):
    if (infile == ""):
        raise Exception("infile is undefined")

    filename = get_abspath(infile)
    if not os.path.exists(filename):
        mesg = "File '%s' not found." % (infile)
        raise Exception(mesg)


def get_default_outfile_name(infile=None, outfile=None, suffix=None):
    if (outfile == ""):
        res = infile.rstrip("/") + suffix
    else:
        res = outfile.rstrip("/")
    return res


def assert_outfile_canoverwrite_or_nonexistent(outfile=None, outform=None, overwrite=None):
    if not overwrite and (outform.upper() != "ASCII"):
        filename = get_abspath(outfile)
        if os.path.exists(filename):
            mesg = "Output file '%s' exists." % (outfile)
            raise Exception(mesg)


def get_listvalue(value):
    return _to_list(value, int) or []

"""
def get_selector(in_scans=None, in_ifs=None, in_pols=None, \
                 in_field=None, in_beams=None, in_rows=None,
                 in_timerange=None):
    scans = get_listvalue(in_scans)
    ifs   = get_listvalue(in_ifs)
    pols  = get_listvalue(in_pols)
    beams = get_listvalue(in_beams)
    rows = get_listvalue(in_rows)
    selector = sd.selector(scans=scans, ifs=ifs, pols=pols, beams=beams,
                           rows=rows)

    if (in_field != ""):
        selector.set_msselection_field(in_field)

    return selector
"""

def combine_masklist(masklist1, masklist2, mode='and'):
    """
    merge two masklists into one following given mode.
    mode should be binary logical operator. currently
    implemented for 'and', 'or' and 'xor'.

    sample: masklist1 = [[10,20],[100,120]]
            masklist2 = [[15,140],[200,220]]
            the result with the available modes will be as follows:
            [[15,20],[100,120]]  for mode='and',
            [[10,140],[200,220]] for mode='or' and
            [[10,14],[21,99],[121,140],[200,220]] for mode='xor'.
    """
    max_idx = 0
    for i in range(len(masklist1)):
        max_elem = int(max(masklist1[i][0], masklist1[i][1]))
        if max_elem > max_idx: max_idx = max_elem
    for i in range(len(masklist2)):
        max_elem = int(max(masklist2[i][0], masklist2[i][1]))
        if max_elem > max_idx: max_idx = max_elem
    numblist = max_idx + 1
    blist1 = [False]*numblist
    for i in range(len(masklist1)):
        min_elem = int(min(masklist1[i][0], masklist1[i][1]))
        max_elem = int(max(masklist1[i][0], masklist1[i][1]))
        for j in range(min_elem, max_elem+1):
            blist1[j] = True
    blist2 = [False]*numblist
    for i in range(len(masklist2)):
        min_elem = int(min(masklist2[i][0], masklist2[i][1]))
        max_elem = int(max(masklist2[i][0], masklist2[i][1]))
        for j in range(min_elem, max_elem+1):
            blist2[j] = True
    blist3 = []
    if mode == 'and':
        for i in range(len(blist1)):
            blist3.append(blist1[i] and blist2[i])
    elif mode == 'or':
        for i in range(len(blist1)):
            blist3.append(blist1[i] or blist2[i])
    elif mode == 'xor':
        for i in range(len(blist1)):
            blist3.append(blist1[i] ^ blist2[i])
    heads = []
    tails = []
    for i in range(len(blist3)):
        if (i == 0):
            if blist3[i]: heads.append(0)
        else:
            if (not blist3[i-1]) and blist3[i]:
                heads.append(i)
            elif blist3[i-1] and not blist3[i]:
                tails.append(i-1)
        if (i == len(blist3)-1) and blist3[i]:
            tails.append(i)
    if len(heads) != len(tails):
        raise Exception("Internal error: heads and tails of resulting masklist have different length.")
    res = []
    for i in range(len(heads)):
        res.append([heads[i], tails[i]])

    return res

def get_restfreq_in_Hz(s_restfreq):
    qatl = quanta()
    if not qatl.isquantity(s_restfreq):
        mesg = "Input value is not a quantity: %s" % (str(s_restfreq))
        raise Exception(mesg)
    if qatl.compare(s_restfreq,'Hz'):
        return qatl.convert(s_restfreq, 'Hz')['value']
    elif qatl.quantity(s_restfreq)['unit'] == '':
        return float(s_restfreq)
    else:
        mesg = "wrong unit of restfreq."
        raise Exception(mesg)
###############################################################
# def get_restfreq_in_Hz(s_restfreq):
#     value = 0.0
#     unit = ""
#     s = s_restfreq.replace(" ","")
#
#     for i in range(len(s))[::-1]:
#         if s[i].isalpha():
#             unit = s[i] + unit
#         else:
#             value = float(s[0:i+1])
#             break
#
#     if (unit == "") or (unit.lower() == "hz"):
#         return value
#     elif (len(unit) == 3) and (unit[1:3].lower() == "hz"):
#         unitprefix = unit[0]
#         factor = 1.0
#
#         if (unitprefix == "a"):
#             factor = 1.0e-18
#         elif (unitprefix == "f"):
#             factor = 1.0e-15
#         elif (unitprefix == "p"):
#             factor = 1.0e-12
#         elif (unitprefix == "n"):
#             factor = 1.0e-9
#         elif (unitprefix == "u"):
#             factor = 1.0e-6
#         elif (unitprefix == "m"):
#             factor = 1.0e-3
#         elif (unitprefix == "k"):
#             factor = 1.0e+3
#         elif (unitprefix == "M"):
#             factor = 1.0e+6
#         elif (unitprefix == "G"):
#             factor = 1.0e+9
#         elif (unitprefix == "T"):
#             factor = 1.0e+12
#         elif (unitprefix == "P"):
#             factor = 1.0e+15
#         elif (unitprefix == "E"):
#             factor = 1.0e+18
#
#         return value*factor
#     else:
#         mesg = "wrong unit of restfreq."
#         raise Exception, mesg
###############################################################

def normalise_restfreq(in_restfreq):
    if isinstance(in_restfreq, float):
        return in_restfreq
    elif isinstance(in_restfreq, int) or isinstance(in_restfreq, long):
        return float(in_restfreq)
    elif isinstance(in_restfreq, str):
        return get_restfreq_in_Hz(in_restfreq)
    elif isinstance(in_restfreq, list) or isinstance(in_restfreq, numpy.ndarray):
        if isinstance(in_restfreq, numpy.ndarray):
            if len(in_restfreq.shape) > 1:
                mesg = "given in numpy.ndarray, in_restfreq must be 1-D."
                raise Exception(mesg)

        res = []
        for i in range(len(in_restfreq)):
            elem = in_restfreq[i]
            if isinstance(elem, float):
                res.append(elem)
            elif isinstance(elem, int) or isinstance(elem, long):
                res.append(float(elem))
            elif isinstance(elem, str):
                res.append(get_restfreq_in_Hz(elem))
            elif isinstance(elem, dict):
                if isinstance(elem["value"], float):
                    res.append(elem)
                elif isinstance(elem["value"], int):
                    dictelem = {}
                    dictelem["name"]  = elem["name"]
                    dictelem["value"] = float(elem["value"])
                    res.append(dictelem)
                elif isinstance(elem["value"], str):
                    dictelem = {}
                    dictelem["name"]  = elem["name"]
                    dictelem["value"] = get_restfreq_in_Hz(elem["value"])
                    res.append(dictelem)
            else:
                mesg = "restfreq elements must be float, int, or string."
                raise Exception(mesg)
        return res
    else:
        mesg = "wrong type of restfreq given."
        raise Exception(mesg)

def set_restfreq(s, restfreq):
    rfset = (restfreq != '') and (restfreq != [])
    if rfset:
        s.set_restfreqs(normalise_restfreq(restfreq))

def set_spectral_unit(s, specunit):
    if (specunit != ''):
        s.set_unit(specunit)

def set_doppler(s, doppler):
    if (doppler != ''):
        if (doppler in ['radio', 'optical', 'z']):
            ddoppler = doppler.upper()
        else:
            ddoppler = doppler
        s.set_doppler(ddoppler)
    else:
        casalog.post('Using current doppler conversion')

def set_freqframe(s, frame):
    if (frame != ''):
        s.set_freqframe(frame)
    else:
        casalog.post('Using current frequency frame')

def set_fluxunit(s, fluxunit, telescopeparam, insitu=True):
    ret = None

    # check current fluxunit
    # for GBT if not set, set assumed fluxunit, Kelvin
    antennaname = s.get_antennaname()
    fluxunit_now = s.get_fluxunit()
    if (antennaname == 'GBT'):
        if (fluxunit_now == ''):
            casalog.post('No fluxunit in the data. Set to Kelvin.')
            s.set_fluxunit('K')
            fluxunit_now = s.get_fluxunit()
    casalog.post('Current fluxunit = %s'%(fluxunit_now))

    # convert flux
    # set flux unit string (be more permissive than ASAP)
    if (fluxunit == 'k'):
        fluxunit_local = 'K'
    elif (fluxunit.upper() == 'JY'):
        fluxunit_local = 'Jy'
    else:
        fluxunit_local = fluxunit


    # fix the fluxunit if necessary
    if ( telescopeparam == 'FIX' or telescopeparam == 'fix' ):
        if ( fluxunit_local != '' ):
            if ( fluxunit_local == fluxunit_now ):
                #print "No need to change default fluxunits"
                casalog.post( "No need to change default fluxunits" )
            else:
                s.set_fluxunit(fluxunit_local)
                #print "Reset default fluxunit to "+fluxunit
                casalog.post( "Reset default fluxunit to "+fluxunit_local )
                fluxunit_now = s.get_fluxunit()
        else:
            #print "Warning - no fluxunit for set_fluxunit"
            casalog.post( "no fluxunit for set_fluxunit", priority = 'WARN' )


    elif ( fluxunit_local=='' or fluxunit_local==fluxunit_now ):
        if ( fluxunit_local==fluxunit_now ):
            #print "No need to convert fluxunits"
            casalog.post( "No need to convert fluxunits" )

    elif ( type(telescopeparam) == list ):
        # User input telescope params
        if ( len(telescopeparam) > 1 ):
            D = telescopeparam[0]
            eta = telescopeparam[1]
            #print "Use phys.diam D = %5.1f m" % (D)
            #print "Use ap.eff. eta = %5.3f " % (eta)
            casalog.post( "Use phys.diam D = %5.1f m" % (D) )
            casalog.post( "Use ap.eff. eta = %5.3f " % (eta) )
            ret = s.convert_flux(eta=eta,d=D,insitu=insitu)
        elif ( len(telescopeparam) > 0 ):
            jypk = telescopeparam[0]
            #print "Use gain = %6.4f Jy/K " % (jypk)
            casalog.post( "Use gain = %6.4f Jy/K " % (jypk) )
            ret = s.convert_flux(jyperk=jypk,insitu=insitu)
        else:
            #print "Empty telescope list"
            casalog.post( "Empty telescope list" )

    elif ( telescopeparam=='' ):
        if ( antennaname == 'GBT'):
            # needs eventually to be in ASAP source code
            #print "Convert fluxunit to "+fluxunit
            casalog.post( "Convert fluxunit to "+fluxunit_local )
            # THIS IS THE CHEESY PART
            # Calculate ap.eff eta at rest freq
            # Use Ruze law
            #   eta=eta_0*exp(-(4pi*eps/lambda)**2)
            # with
            #print "Using GBT parameters"
            casalog.post( "Using GBT parameters" )
            eps = 0.390  # mm
            eta_0 = 0.71 # at infinite wavelength
            # Ideally would use a freq in center of
            # band, but rest freq is what I have
            rf = s.get_restfreqs()[0][0]*1.0e-9 # GHz
            eta = eta_0*numpy.exp(-0.001757*(eps*rf)**2)
            #print "Calculated ap.eff. eta = %5.3f " % (eta)
            #print "At rest frequency %5.3f GHz" % (rf)
            casalog.post( "Calculated ap.eff. eta = %5.3f " % (eta) )
            casalog.post( "At rest frequency %5.3f GHz" % (rf) )
            D = 104.9 # 100m x 110m
            #print "Assume phys.diam D = %5.1f m" % (D)
            casalog.post( "Assume phys.diam D = %5.1f m" % (D) )
            ret = s.convert_flux(eta=eta,d=D,insitu=insitu)

            #print "Successfully converted fluxunit to "+fluxunit
            casalog.post( "Successfully converted fluxunit to "+fluxunit_local )
        elif ( antennaname in ['AT','ATPKSMB', 'ATPKSHOH', 'ATMOPRA', 'DSS-43', 'CEDUNA', 'HOBART']):
            ret = s.convert_flux(insitu=insitu)

        else:
            # Unknown telescope type
            #print "Unknown telescope - cannot convert"
            casalog.post( "Unknown telescope - cannot convert", priority = 'WARN' )

    return ret

def save(s, outfile, outform, overwrite):
    assert_outfile_canoverwrite_or_nonexistent(outfile,
                                               outform,
                                               overwrite)
    outform_local = outform.upper()
    if outform_local == 'MS': outform_local = 'MS2'
    if outform_local not in ['ASAP','ASCII','MS2','SDFITS']:
        outform_local = 'ASAP'

    outfilename = get_abspath(outfile)
    if overwrite and os.path.exists(outfilename):
        os.system('rm -rf %s' % outfilename)

    s.save(outfile, outform_local, overwrite)

    if outform_local!='ASCII':
        casalog.post('Wrote output %s file %s'%(outform_local,outfile))

def doopacity(s, tau):
    antennaname = s.get_antennaname()
    if (tau > 0.0):
        if (antennaname != 'GBT'):
            s.recalc_azel()
        s.opacity(tau, insitu=True)

def dochannelrange(s, channelrange):
    # channel splitting
    if ( channelrange != [] ):
        if ( len(channelrange) == 1 ):
            #print "Split spectrum in the range [%d, %d]" % (0, channelrange[0])
            casalog.post( "Split spectrum in the range [%d, %d]" % (0, channelrange[0]) )
            s._reshape( 0, int(channelrange[0]) )
        else:
            #print "Split spectrum in the range [%d, %d]" % (channelrange[0], channelrange[1])
            casalog.post( "Split spectrum in the range [%d, %d]" % (channelrange[0], channelrange[1]) )
            s._reshape( int(channelrange[0]), int(channelrange[1]) )


def convert_antenna_spec_autocorr(antenna):
    """Convert antenna (baseline) specification(s) to include autocorr data.

    Args:
        antenna (str): antenna specification

    Returns:
        str: tweaked antenna specification
    """
    if len(antenna) == 0:
        return antenna
    elif antenna.find(';') >= 0:
        # antenna selection is semi-colon separated list of baseline
        # specifications: 'SEL1;SEL2...'
        return ';'.join(map(convert_antenna_spec_autocorr, antenna.split(';')))
    elif antenna.find('&') < 0:
        # no '&' in the selection string
        #  -> 'ANT&&&'
        return antenna + '&&&'
    elif antenna.endswith('&&'):
        # 'ANT&&' or 'ANT&&&'
        #  -> as is
        return antenna
    elif antenna.endswith('&'):
        # 'ANT&'
        #  -> 'ANT&&&'
        return antenna.strip('&') + '&&&'
    else:
        # 'ANT1&ANT2' or 'ANT1&&ANT2'
        #  -> 'ANT1&&&;ANT2&&&'
        specs = [a for a in antenna.split('&') if len(a) > 0]
        return ';'.join(map(convert_antenna_spec_autocorr, specs))


def get_antenna_selection_include_autocorr(msname, antenna):
    """Get antenna selection string that includes autocorr data.

    Args:
        msname (str): name of MS
        antenna (str): antenna selection string

    Raises:
        RuntimeError: failed to handle antenna selection string

    Returns:
        str: antenna selection string including autocorr data
    """
    if len(antenna) == 0:
        # if no selection is specified, do nothing
        return antenna

    # test if given antenna selection is valid and if contains any autocorr data
    ms = mstool()
    sel = ms.msseltoindex(msname, baseline=antenna)
    if any([b[0] == b[1] for b in sel['baselines']]):
        antenna_autocorr = antenna
    else:
        antenna_autocorr = convert_antenna_spec_autocorr(antenna)
        casalog.post(
            'Tweaked antenna selection to include autocorr data: original "{}" tweaked "{}"'.format(
                antenna, antenna_autocorr
            )
        )
        # test if tweaked selection is valid
        sel = ms.msseltoindex(msname, baseline=antenna_autocorr)
        if all([b[0] != b[1] for b in sel['baselines']]):
            raise RuntimeError('Cannot handle antenna selection properly. Abort.')
    return antenna_autocorr


"""
def doaverage(s, scanaverage, timeaverage, tweight, polaverage, pweight,
              averageall=False, docopy=False):
    # Average in time if desired
    sret = None
    if ( timeaverage ):
        if tweight=='none':
            errmsg = "Please specify weight type of time averaging"
            raise Exception,errmsg
        stave=sd.average_time(s,weight=tweight,scanav=scanaverage,compel=averageall)
        # Now average over polarizations;
        if ( polaverage ):
            if pweight=='none':
                errmsg = "Please specify weight type of polarization averaging"
                raise Exception,errmsg
            np = len(stave.getpolnos())
            if ( np > 1 ):
                sret=stave.average_pol(weight=pweight)
            else:
                # only single polarization
                #print "Single polarization data - no need to average"
                casalog.post( "Single polarization data - no need to average" )
                sret = stave
        else:
            sret = stave
        #    spave=stave.copy()

    else:
        #if ( scanaverage ):
        #        # scan average if the input is a scantable
        #        spave=sd.average_time(scal,weight=pweight,scanav=True)
        #        scal=spave.copy()
        if ( polaverage ):
            if pweight=='none':
                errmsg = "Please specify weight type of polarization averaging"
                raise Exception,errmsg
            np = s.npol()
            if ( np > 1 ):
                if not scanaverage:
                    sret = sd.average_time(s,weight=pweight)
                else:
                    sret = s
                sret=sret.average_pol(weight=pweight)
            else:
                # only single polarization
                #print "Single polarization data - no need to average"
                casalog.post( "Single polarization data - no need to average" )
                #spave=scal.copy()
                sret = s
        else:
            if scanaverage:
                sret=sd.average_time(s,scanav=True)
            else:
                #spave=scal.copy()
                sret = s
    if docopy and (sret == s):
        sret = s.copy()
    return sret
"""
"""
def plot_scantable(s, pltfile, plotlevel, comment=None):
    # reset plotter
    if sd.plotter._plotter:
        sd.plotter._plotter.quit()
    visible = sd.plotter._visible
    sd.plotter.__init__(visible=visible)
    # each IF is separate panel, pols stacked
    sd.plotter.set_mode(stacking='p',panelling='i',refresh=False)
    sd.plotter.set_histogram(hist=True,linewidth=1,refresh=False)
    sd.plotter.plot(s)
    if comment is not None:
        # somehow I need to put text() twice in order to the second
        # text() actually displays on the plot...
        sd.plotter.text(0.0, 1.0,'',coords='relative')
        #sd.plotter.text(0.0, 1.0,'Raw spectra', coords='relative')
        sd.plotter.text(0.0, 1.0,comment, coords='relative')
    #sd.plotter.axhline(color='r',linewidth=2)
    if ( plotlevel < 0 ):
        # Hardcopy - currently no way w/o screen display first
        #pltfile=project+'_'+suffix+'.eps'
        sd.plotter.save(pltfile)
"""
"""
def scantable_restore_factory(s, infile, fluxunit, specunit, frame, doppler, restfreq=''):
    storage = sd.rcParams['scantable.storage']
    isscantable = is_scantable(infile)
    if storage == 'memory' or isscantable == False:
        return scantable_restore_null(s, fluxunit, specunit, frame, doppler, restfreq)
    else:
        return scantable_restore_impl(s, fluxunit, specunit, frame, doppler, restfreq)
"""
"""
class scantable_restore_interface(object):
    def __init__(self, s=None, fluxunit=None, specunit=None, frame=None, doppler=None, restfreq=None):
        pass

    def restore(self):
        raise NotImplementedError('scantable_restore.restore() is not implemented')

class scantable_restore_null(scantable_restore_interface):
    def __init__(self, s, fluxunit, specunit, frame, doppler, restfreq=''):
        super(scantable_restore_null,self).__init__()

    def restore(self):
        pass


class scantable_restore_impl(scantable_restore_interface):
    def __init__(self, s, fluxunit, specunit, frame, doppler, restfreq=''):
        super(scantable_restore_impl,self).__init__()
        self.scntab = s
        self.fluxunit = s.get_fluxunit()
        self.specunit = s.get_unit()
        self.coord = s._getcoordinfo()
        self.frame = self.coord[1]
        self.doppler = self.coord[2]
        self.molids = s._getmolidcol_list()
        self.rfset = ((restfreq != '') and (restfreq != []))
        self.frameset = frame != '' or frame != self.frame
        self.dopplerset = doppler != '' or doppler != self.doppler
        self.fluxset = self.fluxunit != '' and \
                       (fluxunit != '' or fluxunit != self.fluxunit)
        self.specset = specunit != '' or specunit != self.specunit
        self.restore_not_done = True

    def __del__(self):
        # do restore when the instance is deleted
        self.restore()

    def restore(self):
        if self.restore_not_done:
            self.scntab.set_selection()

            casalog.post('Restoreing header information in input scantable')
            self._restore()

        self.restore_not_done = False

    def _restore(self):
        if self.fluxset:
            self.scntab.set_fluxunit(self.fluxunit)
        if self.specset:
            self.scntab.set_unit(self.specunit)
        if self.dopplerset:
            self.scntab.set_doppler(self.doppler)
        if self.frameset:
            self.scntab.set_freqframe(self.frame)
        if self.rfset:
            self.scntab._setmolidcol_list(self.molids)
"""
"""
def interactive_mask(s, masklist, invert=False, purpose=None):
    new_mask = init_interactive_mask(s, masklist, invert)
    msk = get_interactive_mask(new_mask, purpose)
    finalize_interactive_mask(new_mask)
    del new_mask
    return msk

def init_interactive_mask(s, masklist, invert=False):
    new_mask = sd.interactivemask(scan=s)
    if (len(masklist) > 0):
        new_mask.set_basemask(masklist=masklist,invert=invert)
    new_mask.select_mask(once=False,showmask=True)
    return new_mask

def get_interactive_mask(obj, purpose=None):
    # Wait for user to finish mask selection
    finish=raw_input("Press return %s.\n"%(purpose))
    return obj.get_mask()

def finalize_interactive_mask(obj):
    obj.finish_selection()
"""

"""
def get_plotter(plotlevel=0):
    from matplotlib import rc as rcp
    rcp('lines', linewidth=1)
    from asap.asapplotter import new_asaplot
    visible = sd.rcParams['plotter.gui']
    if plotlevel > 0 and (not visible):
        casalog.post("GUI plot not available", priority = "ERROR")
##     visible = (plotlevel > 0) if plotlevel else sd.rcParams['plotter.gui']
    plotter = new_asaplot(visible=visible)
    return plotter
"""

def get_nx_ny(n):
    nl = _to_list(n, int)
    if not nl: # check for numpy int types
        nl = _to_list(n, numpy.integer)
    if len(nl) == 1:
        nx = ny = nl[0]
    else:
        nx = nl[0]
        ny = nl[1]
    return (nx,ny)

def get_cellx_celly(c,unit='arcsec'):
    if isinstance(c, str):
        cellx = celly = c
    #elif isinstance(c, list) or isinstance(c, numpy.ndarray):
    elif type(c) in (list, tuple, numpy.ndarray):
        if len(c) == 1:
            cellx = celly = __to_quantity_string(c[0],unit)
        elif len(c) > 1:
            cellx = __to_quantity_string(c[0],unit)
            celly = __to_quantity_string(c[1],unit)
        else:
            cellx = celly = ''
    else:
        cellx = celly = __to_quantity_string(c,unit)
    return (cellx, celly)

def get_map_center(c,frame='J2000',unit='rad'):
    map_center = ''
    if isinstance(c, str):
        if len(c) > 0:
            s = c.split()
            if len(s) == 2:
                map_center = 'J2000 '+c
            elif len(s) == 3:
                if s[0].upper() != 'J2000':
                    raise ValueError('Currently only J2000 is supported')
                map_center = c
            else:
                raise ValueError('Invalid map center: %s'%(c))
    else:
        l = [frame]
        for i in range(2):
            if isinstance(c[i], str):
                l.append(c[i])
            else:
                l.append('%s%s'%(c[i],unit))
        map_center = string.join(l)
    return map_center

def __to_quantity_string(v,unit='arcsec'):
    if isinstance(v, str):
        return v
    else:
        return '%s%s'%(v,unit)

def get_subtable_name(v):
    return v.replace('Table:','').strip()

def read_factor_file(filename):
    factor_list = []
    with open(filename, 'r') as f:
        for line in f:
            split_line = line.split()
            nelem = len(split_line)
            factor = [0] * nelem
            for j in range(nelem):
                factor[j] = float(split_line[j])
            factor_list.append(factor)
    return factor_list


#
# The following functions are for timerange selection (CAS-5496)
#
def split_timerange(timerange, separator):
    return [s.strip() for s in timerange.split(separator)
            if not (s.isspace() or len(s) == 0)]

def split_date_string(date_string, full=True):
    split_by_slash = date_string.split('/')
    split_by_colon = split_by_slash[-1].split(':')
    if full:
        split_by_comma = split_by_colon[-1].split('.')
    else:
        split_by_comma = [split_by_colon[-1]]
    elements_list = [element for element in
                     split_by_slash[:-1] + split_by_colon[:-1] + split_by_comma
                     if len(element) > 0]
    if full and len(elements_list) > 1 and date_string.find('.') == -1:
        elements_list += ['0']
    return elements_list

def get_full_description(date_string, year='YYYY', month='MM', day='DD', hour='hh', minute='mm', second='ss', subsecond='ff', default=None):
    number_of_slashes = date_string.count('/')
    number_of_colons = date_string.count(':')

    elements_list = split_date_string(date_string)

    template = string.Template('$year/$month/$day/$hour:$min:$sec.$subsec')
    keys = ['year', 'month', 'day', 'hour', 'min', 'sec', 'subsec']

    if default is None:
        default_values = [year, month, day, hour, minute, second, subsecond]
    else:
        default_values = split_date_string(default)
        if len(default_values) < 7:
            default_values = default_values + ['00']

    values = default_values[:7-len(elements_list)] + elements_list

    return template.safe_substitute(**dict(zip(keys, values)))

def to_datetime(date):
    date_elements_list = split_date_string(date, full=False)
    date_list = map(int, date_elements_list[:-1]) + [float(date_elements_list[-1])]
    t = datetime.datetime(date_list[0], date_list[1], date_list[2],
                          date_list[3], date_list[4], int(date_list[5]),
                          int((date_list[5]-int(date_list[5]))*1e6))
    return t

def to_timedelta(delta):
    delta_elements_list = split_date_string(delta, full=False)
    delta_list = map(int, delta_elements_list[:-1]) + [float(delta_elements_list[-1])]
    while len(delta_list) < 3:
        delta_list.insert(0, 0)
    dummy1 = datetime.datetime(1999, 1, 1)
    dummy2 = datetime.datetime(1999, 1, 1,
                               delta_list[0], delta_list[1], int(delta_list[2]),
                               int((delta_list[2]-int(delta_list[2]))*1e6))
    dt = dummy2 - dummy1
    return dt

def add_time(date, delta):
    t = to_datetime(date)
    dt = to_timedelta(delta)
    return t+dt

def sub_time(date, delta):
    t = to_datetime(date)
    dt = to_timedelta(delta)
    return t-dt

def select_by_timerange(data, timerange):
    tb = table()
    qa = quanta()

    # first get default time and interval
    if data is not None:
        tb.open(data)
        irow = 0
        while (irow < tb.nrows() and
               (tb.getcell('FLAGROW', irow) != 0
               or all(tb.getcell('FLAGTRA', irow) != 0))):
            irow = irow + 1
        irow %= tb.nrows()
        default_mjd = tb.getcell('TIME', irow)
        default_interval = tb.getcell('INTERVAL', irow)
        tb.close()
    else:
        default_mjd = 0.0
        defalut_interval = 0.0

    qdate = qa.quantity(default_mjd, 'd')
    date_dict = qa.splitdate(qdate)
    parameters = {'year': str(date_dict['year']),
                  'month': str(date_dict['month']),
                  'day': str(date_dict['monthday']),
                  'hour': str(date_dict['hour']),
                  'minute': str(date_dict['min']),
                  'second': str(date_dict['sec']),
                  'subsecond': str(date_dict['usec'])}

    if re.match('.+~.+', timerange):
        # This is case 1: 'T0~T1'
        dates_list = split_timerange(timerange, '~')
        first_date = get_full_description(dates_list[0], **parameters)
        second_date = get_full_description(dates_list[1], default=first_date)
        taql = 'TIME >= MJD(DATETIME("%s")) && TIME <= MJD(DATETIME("%s"))'%(first_date, second_date)
    elif re.match('.+\+.+', timerange):
        # This is case 3: 'T0+dT'
        dates_list = split_timerange(timerange, '+')
        first_date = get_full_description(dates_list[0], **parameters)
        delta_time = dates_list[1]
        second_date_datetime = add_time(first_date, delta_time)
        second_date = second_date_datetime.strftime('%Y/%m/%d/%H:%M:%S')
        microsec = '%s'%(second_date_datetime.microsecond/1e6)
        second_date += microsec.lstrip('0')
        taql = 'TIME >= MJD(DATETIME("%s")) && TIME <= MJD(DATETIME("%s"))'%(first_date, second_date)
    elif re.match('^ *>.+', timerange):
        # This is case 4: '>T0'
        dates_list = split_timerange(timerange, '>')
        first_date = get_full_description(dates_list[0], **parameters)
        taql = 'TIME > MJD(DATETIME("%s"))'%(first_date)
    elif re.match('^ *<.+', timerange):
        # This is case 5: '<T0'
        dates_list = split_timerange(timerange, '<')
        first_date = get_full_description(dates_list[0], **parameters)
        taql = 'TIME < MJD(DATETIME("%s"))'%(first_date)
    elif re.match('^[0-9/:.]+$', timerange):
        # This is case 2: 'T0'
        middle_date = get_full_description(timerange, **parameters)
        hours = int(0.5 * default_interval / 3600.0)
        minutes = int((0.5 * default_interval - hours * 3600.0) / 60.0)
        seconds = (0.5 * default_interval) % 60.0
        delta_time = '%d:%d:%s'%(hours, minutes, seconds)
        first_date_datetime = sub_time(middle_date, delta_time)
        second_date_datetime = add_time(middle_date, delta_time)
        first_date = first_date_datetime.strftime('%Y/%m/%d/%H:%M:%S')
        microsec = '%s'%(first_date_datetime.microsecond/1e6)
        first_date += microsec.lstrip('0')
        second_date = second_date_datetime.strftime('%Y/%m/%d/%H:%M:%S')
        microsec = '%s'%(second_date_datetime.microsecond/1e6)
        second_date += microsec.lstrip('0')
        taql = 'TIME >= MJD(DATETIME("%s")) && TIME <= MJD(DATETIME("%s"))'%(first_date, second_date)
    else:
        # invalid format
        casalog.post('WARNING: timerange="%s" is invalid'%(timerange), priority='WARN')
        taql = ''

    casalog.post('taql for timerange: \'%s\''%(taql), priority='DEBUG')

    return taql

def parse_idx_selection(s, id_min, id_max):
    items = s.split(',')
    l = []
    for item in items:
        if item.isdigit():
            v = int(item)
            if v >= id_min and v <= id_max:
                l.append(v)
        elif item.startswith('>'):
            v = int(item[1:])
            if v <= id_max:
                l.extend(range(v,id_max+1))
        elif item.startswith('<'):
            v = int(item[1:])
            if v >= id_min:
                l.extend(range(id_min,v+1))
        elif re.match('^[0-9]+~[0-9]+$', item):
            v = map(int, item.split('~'))
            id_from = max(id_min, v[0])
            id_to = min(id_max, v[1])
            if id_from <= id_to:
                l.extend(range(id_from,id_to+1))
    return l

def get_spwids(selection, infile=None):
    # return a comma-separated string of spw IDs.
    # selection should be an output of ms.msseltoindex()

    spw_list = selection['spw']
    if len(spw_list) == 0:
        if infile is None:
            raise Exception("infile is needed when selection['spw'] is empty.")
        with tbmanager(os.path.join(infile, 'DATA_DESCRIPTION')) as tb:
            spw_list = tb.getcol('SPECTRAL_WINDOW_ID')

    l= []
    for item in spw_list:
        l.append(str(item))
    return ','.join(l)

def get_spwchs(selection, infile):
    # return a string containing spw IDs, nchans and edge
    # indices of selected regions.
    # parameters:
    #     selection: an output of ms.msseltoindex()
    # format of returned value:
    #     one or more 'IFNO:NCHAN:IDX' strings connected
    #     by comma. 'IDX' contains edge indices of selected
    #     channel regions connected by semicolon.
    # example:
    #     if two channel regions from 100 to 200 and from
    #     250 to 350 are selected for IF=3 (nchan=1024) and
    #     all channels are selected for IF=4 (nchan=2048),
    #     the returned value will be
    #     '3:1024:100;200;250;350,4:2048:0;2047'.

    with tbmanager(os.path.join(infile, 'SPECTRAL_WINDOW')) as tb:
        nchanmap = dict(((str(i),str(tb.getcell('NUM_CHAN',i))) for i in range(tb.nrows())))

    ch_info = selection['channel']
    exist_spw = selection['spw'].tolist()
    d = {}
    for item in ch_info:
        try:
            exist_spw.index(item[0])
            spwid = str(item[0])
            try:
                d.keys().index(spwid)
                d[spwid].append(str(item[1]))
            except:
                d[spwid] = [str(item[1])]
            d[spwid].append(str(item[2]))
        except:
            pass
    l = []
    for key in d.keys():
        l.append(':'.join([key, nchanmap[key], ';'.join(d[key])]))
    return ','.join(l)

##### OBSOLETE METHOD #####
"""
def get_ms_sampling_arcsec(msname, spw='', antenna='', field='',
                           intent='ON_SOURCE', scan='',#timerange='',
                           outref=''):
    if spw=='': spw='*'
    if antenna=='': antenna='*&&&'
    (ms_loc, msmd_loc, tb_loc,me_loc) = gentools(['ms','msmd','tb','me'])
    qa_loc = quanta()
    selected_idx = ms_loc.msseltoindex(vis=msname,spw=spw,baseline=antenna,
                                       field=field,scan=scan)#,time=timerange)
    tb_loc.open(msname+'/STATE')
    intents_all = tb_loc.getcol("OBS_MODE")
    selected_intent = []
    for (idx, obmode) in enumerate(intents_all):
        if obmode.find(intent)>=0: selected_intent.append(idx)
    tb_loc.close()
    ddid0 = selected_idx['spwdd'][0]
    spw0=selected_idx['spw'][0]
    ant0 = selected_idx['antenna1'][0]
    bl0 = '%d&&&' % ant0
    if len(selected_idx['spw']) > 1 or len(selected_idx['antenna1']) > 1:
        casalog.post("Using only spw=%d and antenna=%s in %s to get pointing sampling" % (spw0, bl0, msname), priority='warn')
    tb_loc.open(msname)
    nrow_org = tb_loc.nrows()
    taqlstr = 'DATA_DESC_ID==%d && ANTENNA1==%d && ANTENNA2==%d' % \
              (ddid0,ant0,ant0)
    if len(selected_idx['field'])>0:
        taqlstr += (' && FIELD_ID IN %s' % str(list(selected_idx['field'])))
    if len(selected_idx['scan']) > 0:
        taqlstr += (' && SCAN_NUMBER IN %s' % str(list(selected_idx['scan'])))
    if len(selected_intent) > 0:
        taqlstr += (' && STATE_ID IN %s' % str(list(selected_intent)))
    seltb = tb_loc.query(query=taqlstr,sortlist='TIME',columns='TIME')
    nrow_sel = seltb.nrows()
    row_idx = seltb.rownumbers()
    times = seltb.getcol("TIME")
    tb_loc.close()
    seltb.close()
    tb_loc.open(msname+'/POINTING')
    nrow_ptg = tb_loc.nrows()
    tb_loc.close()
    #initial_guess = (nrow_org==nrow_ptg) #indicates MS converted back from ASAP.
    initial_guess = (nrow_sel==nrow_ptg) #indicates MS converted back from ASAP.
    #ms_loc.open(msname)
    #ms_loc.msselect(items=dict(spw=str(spw0),baseline=bl0,field=field,
    #                       scan=scan,time=timerange,scanintent=intent))
    #ms_loc.selectinit(datadescid=ddid0)
    ## get selected rowid and time stamps (unique in time)
    #retval = ms_loc.ngetdata(['rows','time'])
    #ms_loc.close()
    #row_idx = retval['rows']
    #times = retval['time']
    # unit time stamp
    times, idx = numpy.unique(times,return_index=True)
    row_idx = row_idx[idx]
    del idx
    # sort by time
    row_gap = rasterutil._detect_gap(times)
    # reduce the number of pointings to use when there are too many
    if len(row_gap) > 100:
        casalog.post("Detected more than 100 raster rows. Using the first 100 raster rows to define separation between rows.")
        times = times[:row_gap[100]]
        row_idx = row_idx[:row_gap[100]]
        row_gap = row_gap[:101]
    casalog.post("Using %d pointings to define sampling interval" % len(row_idx))
    times = times.tolist()
    # get pointing direction of the time
    msmd_loc.open(msname)
    badtime_idx = [] # id of rows with out corresponding pointing
    direction_raw = []
    for i in range(len(row_idx)):
        try:
            pointing = msmd_loc.pointingdirection(row_idx[i],True,(row_idx[i] if initial_guess else 0))
        except:
            badtime_idx.append(i)
            casalog.post("Coud not get pointings of row %d. skipping the row" % row_idx[i], priority="DEBUG")
            continue
        direction_raw.append(pointing['antenna1']['pointingdirection'])
    msmd_loc.close()
    inframe = direction_raw[0]['refer']
    # adjust time and row_gap for rows without valid pointing
    for i in badtime_idx:
        times.pop(i)
        for j in range(len(row_gap)):
            if i <= row_gap[j]:
                row_gap[j] -=1
    #direction_raw = [ msmd_loc.pointingdirection(idx,True,(idx if initial_guess else 0))['antenna1']['pointingdirection'] for idx in row_idx ]
    if inframe==outref:
        ra_rad = [ dir['m0']['value'] for dir in direction_raw ]
        dec_rad = [ dir['m1']['value'] for dir in direction_raw ]
    else:
        msmd_loc.open(msname)
        me_loc.doframe(msmd_loc.antennaposition(ant0))
        msmd_loc.close()
        ra_rad = []
        dec_rad = []
        for idx in range(len(times)):
            me_loc.doframe(me_loc.epoch('mjd',qa_loc.quantity(times[idx]/86400.,'d')))
            dir_in = me_loc.direction(inframe, direction_raw[idx]['m0'],
                                     direction_raw[idx]['m1'])
            dir_conv = me_loc.measure(dir_in,outref)
            ra_rad.append(dir_conv['m0']['value'])
            dec_rad.append(dir_conv['m1']['value'])
    direction_rad = [ra_rad, dec_rad]
    dx_rad, dy_rad, pa = rasterutil._get_sampling(direction_rad,row_gap)
    rad_to_asec = 180./numpy.pi*3600
    return dx_rad*rad_to_asec, dy_rad*rad_to_asec, pa
"""

def parse_wavenumber_param(wn):
    if isinstance(wn, list):
        _check_positive_or_zero(wn)
        wn.sort()
        return ','.join(_get_strlist(wn))
    elif isinstance(wn, tuple):
        _check_positive_or_zero(wn)
        wn_list = list(wn)
        wn_list.sort()
        return ','.join(_get_strlist(wn_list))
    elif isinstance(wn, int):
        _check_positive_or_zero(wn)
        return str(wn)
    elif isinstance(wn, str):
        if ',' in wn:                            # cases 'a,b,c,...'
            val0 = wn.split(',')
            _check_positive_or_zero(val0)
            val = []
            for v in val0: val.append(int(v))
            val.sort()
            res = list(set(val)) # uniq
        elif '-' in wn:                          # case 'a-b' : return [a,a+1,...,b-1,b]
            val = wn.split('-')
            _check_positive_or_zero(val)
            val = [int(val[0]), int(val[1])]
            val.sort()
            res = [i for i in range(val[0], val[1]+1)]
        elif '~' in wn:                          # case 'a~b' : return [a,a+1,...,b-1,b]
            val = wn.split('~')
            _check_positive_or_zero(val)
            val = [int(val[0]), int(val[1])]
            val.sort()
            res = [i for i in range(val[0], val[1]+1)]
        elif wn[:2] == '<=' or wn[:2] == '=<':   # cases '<=a','=<a' : return [0,1,...,a-1,a]
            val = wn[2:]
            _check_positive_or_zero(val)
            res = [i for i in range(int(val)+1)]
        elif wn[-2:] == '>=' or wn[-2:] == '=>': # cases 'a>=','a=>' : return [0,1,...,a-1,a]
            val = wn[:-2]
            _check_positive_or_zero(val)
            res = [i for i in range(int(val)+1)]
        elif wn[0] == '<':                       # case '<a' :         return [0,1,...,a-2,a-1]
            val = wn[1:]
            _check_positive_or_zero(val, False)
            res = [i for i in range(int(val))]
        elif wn[-1] == '>':                      # case 'a>' :         return [0,1,...,a-2,a-1]
            val = wn[:-1]
            _check_positive_or_zero(val, False)
            res = [i for i in range(int(val))]
        elif wn[:2] == '>=' or wn[:2] == '=>':   # cases '>=a','=>a' : return [a,-999], which is
                                                 #                     then interpreted in C++
                                                 #                     side as [a,a+1,...,a_nyq]
                                                 #                     (CAS-3759)
            val = wn[2:]
            _check_positive_or_zero(val)
            res = [int(val), -999]
        elif wn[-2:] == '<=' or wn[-2:] == '=<': # cases 'a<=','a=<' : return [a,-999], which is
                                                 #                     then interpreted in C++
                                                 #                     side as [a,a+1,...,a_nyq]
                                                 #                     (CAS-3759)
            val = wn[:-2]
            _check_positive_or_zero(val)
            res = [int(val), -999]
        elif wn[0] == '>':                       # case '>a' :         return [a+1,-999], which is
                                                 #                     then interpreted in C++
                                                 #                     side as [a+1,a+2,...,a_nyq]
                                                 #                     (CAS-3759)
            val0 = wn[1:]
            val = int(val0)+1
            _check_positive_or_zero(val)
            res = [val, -999]
        elif wn[-1] == '<':                      # case 'a<' :         return [a+1,-999], which is
                                                 #                     then interpreted in C++
                                                 #                     side as [a+1,a+2,...,a_nyq]
                                                 #                     (CAS-3759)
            val0 = wn[:-1]
            val = int(val0)+1
            _check_positive_or_zero(val)
            res = [val, -999]
        else:
            _check_positive_or_zero(wn)
            res = [int(wn)]

        #return res
        return ','.join(_get_strlist(res))
    else:
        msg = 'wrong value given for addwn/rejwn'
        raise RuntimeError(msg)


def _check_positive_or_zero(param, allowzero=True):
    msg = 'wrong value given for addwn/rejwn'
    try:
        if isinstance(param, list) or isinstance(param, tuple):
            for i in range(len(param)):
                _do_check_positive_or_zero(int(param[i]), allowzero)
        elif isinstance(param, int):
            _do_check_positive_or_zero(param, allowzero)
        elif isinstance(param, str):
            _do_check_positive_or_zero(int(param), allowzero)
        else:
            raise RuntimeError(msg)
    except:
        raise RuntimeError(msg)

def _get_strlist(param):
    res = []
    for i in range(len(param)): res.append(str(param[i]))
    return res

def _do_check_positive_or_zero(param, allowzero):
    msg = 'wrong value given for addwn/rejwn'
    if (param < 0) or ((param == 0) and not allowzero):
        raise RuntimeError(msg)

def _is_sequence_or_number(param, ptype=int):
    """
    Returns true if input is an array type or a number with a give data type.
    Arguments
        param : an array or number to test
        ptype : the data type that param should be.
    """
    if hasattr(param, '__iter__'):
        out = True
        for p in param:
            out &= isinstance(p, ptype)
        return out
    else:
        return isinstance(param, ptype)

def _to_list(param, ptype=int, convert=False):
    """
    Convert a number, an array type or a string to a list.
    The function returns None if input values are not ptype and convert=False.
    When convert is True, force converting input values to a list of ptype.
    """
    if isinstance(param, ptype): # a string or a number
        if ptype is str: return param.split()
        elif convert:
            return [ ptype(param) ]
        else: return [ param ]
    if _is_sequence_or_number(param, ptype):
        return list(param)
    elif convert:
        return [ ptype(p) for p in param]
    return None