# 
#  Copyright (C) 2010-2019,2021-2022  Smithsonian Astrophysical Observatory
#
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License along
#  with this program; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#


from pycrates.io import *
import pycrates as cr
import cxcdm as dm
from collections import OrderedDict
from numpy import array
from numpy import ndarray
from numpy import pi
from numpy import arange
from numpy import empty
from numpy import chararray
from numpy import append
from numpy import reshape
from numpy import string_
from numpy import double
import pytransform as tr
from cxcdm._cxcdm import _dmSetVerbose
import os
import warnings
from subprocess import check_output
from subprocess import CalledProcessError
from math import *
from numpy import unpackbits
from numpy import packbits
from history import *
from region import *
from cxcstdhdr import *

warnings.simplefilter("always", UserWarning)
warnings.simplefilter("ignore", DeprecationWarning)
warnings.simplefilter("ignore", PendingDeprecationWarning)
warnings.simplefilter("ignore", ImportWarning)


class DMIO(CrateIO):

    def __init__(self): 
        self.__clear__()
        CrateIO.__init__(self)
        self.name = "CXC DataModel"
        self.__block = None

    def __repr__(self):
        retstr  = "Crate I/O Interface: " + self.name
        return retstr

    def __str__(self):
        return self.__repr__()

    def __rm_func_name(self, funcName, tmp_str ):
        try:
            tmp_str = tmp_str.replace(funcName,"")
        except:
            pass
        return tmp_str


    def canHandle(self, input ):
        """
        Check if the input object is of a type that the class
        is capable of processing.

        Returns True: dmBlock, dmDataset, dmDescriptor
        """
        retval = False

        if (( type( input ) is dm.dmBlock ) or
            ( type( input ) is dm.dmDataset ) ):
            retval = True

        return retval


    def create(self, input=None):
        self.__init__()

        if type( input ) is str:
            try:
                self.__fptr = dm.dmDatasetCreate(input) 
            except:
                raise IOError("Unable to create file " + input + ".")


    def open(self, input=None, blktype="", mode='rw'):
        """
        Open provided object and set fptr attribute.
        """

        if ( (mode != "rw") and (mode != "r") and (mode != "w") ):
            raise AttributeError("Bad value for argument 'mode': %s"% mode)

        if input is None:
            return

        if type( input ) is str:
            flag = False

            # turn off update mode if there is a filter on the input file or
            # if user indicates file is not writeable
            if ('w' in mode):
                fname = input

                if ( input.find('[') == -1 ):
                    file_is_filtered = False
                else:
                    file_is_filtered = True
                    fname = input.split('[')[0]

                file_is_gzipped = False
                if not os.path.isfile( fname ):
                    if os.path.isfile( fname + ".gz" ):
                        fname = fname + ".gz"
                    else:
                        raise IOError("Input file does not exist, '" + fname + "'." )

                file_is_gzipped = "gzip" in str(check_output("file "+fname, shell=True))
                file_is_readonly = not os.access( fname, os.W_OK )

                if (file_is_filtered) or (file_is_gzipped) or (file_is_readonly):

                    mode = mode.strip('w')

                    if file_is_filtered:
                        warnings.warn("File '" + input + "' is not writeable.  Changing to read-only mode.")
                    elif file_is_readonly:
                        warnings.warn("File '" + fname + "' does not have write permission.  Changing to read-only mode.")
                    elif file_is_gzipped:
                        warnings.warn("File '" + fname + "' is gzipped.  Changing to read-only mode.")
                else:
                    flag = True  # file is definitely writeable


            if 'r' not in mode:  # check for readability happens earlier in _file_exists()
                mode += 'r'

            try:
                self.__block = dm.dmBlockOpen( input, update=flag )
                btype = dm.dmBlockGetType( self.__block )

                if (blktype == "TABLE" and btype != dm.dmTABLE) or (blktype == "IMAGE" and btype != dm.dmIMAGE):
                    dm.dmBlockClose( self.__block )
                    raise TypeError("Incorrect file type.");

                self.__fptr = dm.dmBlockGetDataset( self.__block )

            except IOError:
                self.__fptr = dm.dmDatasetCreate(input)
            except:
                raise 

        elif type( input ) is dm.dmBlock:
            try: 
                self.__fptr = dm.dmBlockGetDataset(input)
            except:
                raise

        return mode


    def _file_exists(self, input):
        if isinstance(input, str):
            retval = False
            blk = None
            infile = input

            try:
                blk = dm.dmBlockOpen( input )
            except IOError as ex:
                errmsg = str(ex)                

                pos = errmsg.find("\n")
                infilename = input.split('[')
		# check for empty file
                if len(infilename[0]) == 0:
                    raise IOError("Failed to open file '"+ input + "'; filename is an empty string.")

                # clean-up DM error string for crates output
                elif "DM " in errmsg and "error: " in errmsg: 
                    errstr = errmsg.partition("error: ")
                    raise IOError("Failed to open file '"+ input + "'; " + errstr[2].rstrip("\n") + ".\n")
 
                elif pos > 0 and pos < len(errmsg):
                    errmsg = errmsg.split("\n")[1]  # if two line message show only second line
                    raise IOError(errmsg)

                elif "file does not exist" in errmsg or "failed to open file" in errmsg:
                    if os.path.exists(infilename[0]) and os.access(input, os.R_OK) == False:
                        raise IOError("File '"+ input + "' exists but is not readable.")
                    pass
                else:
                    raise ex

            if blk:
                dm.dmBlockClose( blk )
                retval = True
                
        return retval


    def _is_rmf(self, input):
        # possible RMF block names: MATRIX, SPECRESP MATRIX, AXAF_RMF
        if self.__fptr is not None:
            try:
                rmf_block = dm.dmDatasetGetBlock( self.__fptr, "MATRIX" )
            except ValueError as ex:
                try:
                    rmf_block = dm.dmDatasetGetBlock( self.__fptr, "SPECRESP MATRIX" )
                except ValueError as ex:
                    try:
                        rmf_block = dm.dmDatasetGetBlock( self.__fptr, "AXAF_RMF" )
                    except ValueError as ex:
                        try:
                            rmf_block = dm.dmDatasetGetBlock( self.__fptr, 2 )
                        except ValueError as ex:
                            return False
                        pass
                    pass
                pass

        # TELESCOP == ROSAT - skipping the next checks ROSAT may not have these 
        try:
            (key0, key0_val) = dm.dmKeyRead( rmf_block, "TELESCOP")
            if "ROSAT" in str(key0_val):
                return True
        except:
            pass

        # HDUCLAS1 = RESPONSE
        (key1, key1_val) = dm.dmKeyRead( rmf_block, "HDUCLAS1")
        if "RESPONSE" not in str(key1_val):
            return False

        # HDUCLAS2 = RSP_MATRIX        
        (key2, key2_val) = dm.dmKeyRead( rmf_block, "HDUCLAS2")
        if "RSP_MATRIX" not in str(key2_val):
            return False

        return True


    def _is_pha(self, input):
        if self.__fptr is not None:
            try:
                pha_block = dm.dmDatasetGetBlock( self.__fptr, "SPECTRUM" )
            except ValueError as ex:
                try:
                    pha_block = dm.dmDatasetGetBlock( self.__fptr, 2 )
                except ValueError as ex:
                    return False                
                pass

        (key1, key1_val) = dm.dmKeyRead( pha_block, "HDUCLAS1")
        if "SPECTRUM" not in str(key1_val):
            return False
        
        return True


    def _is_pha_type1(self):
        if self.__fptr is not None:
            try:
                pha_block = dm.dmDatasetGetBlock( self.__fptr, 2 )
            except ValueError as ex:
                return False

        type1 = False
        key1 = None
        key1_val = None

        try:
            (key1, key1_val) = dm.dmKeyRead( pha_block, "HDUCLAS4")
        except ValueError as ex:
            pass

        if "TYPE:II" not in str(key1_val):
            type1 = True

        if key1 is None:
            try:
                col_dd = dm.dmTableOpenColumn( pha_block, "CHANNEL" )
                arrdim = dm.dmGetArrayDimensions(col_dd)
                if arrdim.size == 0:
                    type1 = True

            except RuntimeError as ex:
                pass

        return type1


    def _close_block(self, blockno):
        if self.__fptr is None:
            return
 
        # check if block number is within range before closing
        nblocks = dm.dmDatasetGetNoBlocks( self.__fptr )
        if blockno > 0 and blockno <= nblocks:
            block = dm.dmDatasetGetBlock( self.__fptr, blockno)
            dm.dmBlockClose( block )


    def close(self):
        """
        Close out the fptr object.
        """
        if self.__fptr is None:
            return

        try:
            # close the dataset
            dm.dmDatasetClose( self.__fptr )
            self.__fptr = None

        except:
            raise


    def load( self, obj, input=None ):
        """
        Load values of input Crate object.
        obj   = CrateDataset
                loop through blocks, create and append appropriate Crate object
                for each to the dataset.
        input = TABLECrate,IMAGECrate
                assigns attributes from current DM Block
        """
        if isinstance(obj, cr.CrateDataset ):
            self.__load_dataset(obj, input)

        elif isinstance(obj, cr.TABLECrate ):
            self.__load_table(obj)

        elif isinstance(obj, cr.IMAGECrate ):
            self.__load_image(obj)
        else:
            raise TypeError("Unable to load input object.")


    def _load_dataset(self, obj):
        """
          Load Dataset definitions to input Crate object.
          obj = CrateDataset
                loop through blocks, create and append appropriate Crate object
                for each to the dataset.
          obj = TABLECrate,IMAGECrate
                create parent CrateDataset
                'current' block is loaded to input Crate object.
                appropriate Crates are created and loaded for other blocks
                appends Crates to Dataset.
        """

        if isinstance(obj, cr.CrateDataset ):
            ds = obj
            inblock = None
        else:
            if isinstance(obj, cr.TABLECrate) or isinstance(obj, cr.IMAGECrate):
                ds = cr.CrateDataset()
                inblock = obj
            else:
                raise TypeError("Input must be a Crate or CrateDataset.")

        ds.__clear__()
        ds._set_backend( self )

        nblocks = dm.dmDatasetGetNoBlocks( self.__fptr )
        if nblocks == 0:
            return

        curblock = dm.dmBlockGetNo( self.__block )
        ds.set_current_crate( curblock )

        for ii in range( 1, nblocks+1 ):
            if ii == dm.dmBlockGetNo( self.__block ):
                btype = dm.dmBlockGetType( self.__block )
            else:
                btype  = dm.dmDatasetGetBlockType( self.__fptr, ii )

            if (( inblock is not None) and (ii == curblock) ):
                item = inblock._get_base_crate()
            else:
                item = cr.Crate()
                   
            if item is not None:
                if btype == dm.dmTABLE:
                    item._crtype = "TABLE"
                    self.__load_table( item, blockno=ii )

                elif btype == dm.dmIMAGE:
                    item._crtype = "IMAGE"
                    self.__load_image( item, blockno=ii  )

                ds.add_crate(item)
             
        if inblock is not None:
            inblock._set_parent_dataset(ds) 
         
        # Return to current.
        block = dm.dmDatasetGetBlock( self.__fptr, curblock )


    def __load_table(self, obj, blockno=0):
        if blockno == 0:
            blockno = dm.dmDatasetGetCurrentBlockNo( self.__fptr )
        block = dm.dmDatasetGetBlock( self.__fptr, blockno )

        if ( block is None ):
            raise RuntimeError("Unable to load table.")
        elif  ( dm.dmBlockGetType( block ) != dm.dmTABLE ):
            raise RuntimeError()
        else:
            obj.name = dm.dmBlockGetName(block)
            obj._set_number( blockno )
            obj._set_nrows( dm.dmTableGetNoRows(block) )
            obj._set_ncols( dm.dmTableGetNoCols(block) )


    def __load_image(self, obj, blockno=0):
        if blockno == 0:
            blockno = dm.dmDatasetGetCurrentBlockNo( self.__fptr )

        block = dm.dmDatasetGetBlock( self.__fptr, blockno )

        if ( block is None ):
            raise RuntimeError("Unable to load image.")
        elif ( dm.dmBlockGetType( block ) != dm.dmIMAGE ):
            raise RuntimeError()
        else:
            obj.name = dm.dmBlockGetName(block)
            obj._set_number( blockno )


    def __dm_get_name(self, input):

        """ 
        handle RuntimeError exception on blank descriptor name string, as ticketed in SL-232,
        othwerwise, continue normal 'get'ed or any excpetion thrown.
        """

        try:
            name  = dm.dmGetName(input)
        except RuntimeError as ex:
            #
            # the 'blank' error, only error,  returned from datamodel/descriptor/dmdescriptror.c:dmGetName()
            # the RunTimeError, raised from datamodel/modules/pymodule/cxcdm_descriptor.c:_dmDescriptorGetName()
            #
            # SL-232 allows the dd-name blank, so the process goes as 
            #  leave the name blank and let it returned normally 
            #  if the exception-error messsage string match to the hard-code string here
            #

            exmsg = str(ex).lower() 
            if ( exmsg.find("blank descriptor name string") >=0 ):
                # if the string matched, leave name blank
                name="" 
                pass 
            else:
                # if the string not matched, keep the exception to continue raise
                raise                       
        # except Exception
        #   all, other than RuntimeError, continue to throw

        return name


    def __load_key(self, obj, input=None):
        if input is None:
            return

        if isinstance(input, dm.dmDescriptor):
            obj.name  = self.__dm_get_name(input)
            obj.desc  = dm.dmGetDesc(input)
            obj.unit  = dm.dmGetUnit(input)
            try:
                obj.value = dm.dmGetData(input)
            except:
                obj.value = ""


    def __load_data(self, obj, input=None, nsets=1, colname="", block=None):
        if input is None:
            return


        if isinstance(input, dm.dmDescriptor):
            if colname:
                obj.name  = colname
            else:
                obj.name  = self.__dm_get_name(input)
            obj.desc  = dm.dmGetDesc(input)
            obj.unit  = dm.dmGetUnit(input)
            obj.vdim  = dm.dmGetElementDim(input)

            try:
                (tlmin, tlmax) = dm.dmDescriptorGetRange(input)
                obj._set_tlmin( tlmin )
                obj._set_tlmax( tlmax )
            except:
                pass

            try:
                nullval = dm.dmDescriptorGetNull(input)
                obj._set_nullval( nullval )
            except (RuntimeError,TypeError) as ex:
                pass

            if obj.parent is None and obj.source is None:
                type = dm.dmGetDataType(input)
                length = dm.dmDescriptorGetLength(input)

                if "string" in str(type) or "bytes" in str(type):
                    type = "S" + str(length)

                if nsets > 0:
                    obj.values = dm.dmGetData(input, nrows=nsets)

                    if nsets == 1: 
                        if "uint8" in str(type):
                            obj._values = array([obj.values.flatten()], dtype=type)

                        elif "object" not in str(obj.values.dtype):
                            obj.values = array([obj.values], dtype=type)

                    if "object"in str(obj.values.dtype):
                        obj._varlen_itemsize = dm.dmGetArraySize(input)

                else:  
                    # create empty arrays with the correct array dimensions
                    dims = dm.dmGetArrayDimensions(input)
                    shape = [nsets]                                # scalar

                    if dims.size != 0:
                        if obj.vdim > 1:
                            shape.append(obj.vdim)                 # vector array
                            shape = shape + dims.tolist()
                        else:
                            shape = shape + dims.tolist()          # array
                    else:
                        if obj.vdim > 1:
                            shape.append(obj.vdim)                 # vector

                    obj.values = empty(shape=shape, dtype=type)

                if "uint8" in str(type) and length > 0:
                    obj.convert_bytes_to_bits(length)


            if obj.vdim > 1:
                cpt_list = OrderedDict()

                for jj in range(0, obj.vdim):
                    cpt = dm.dmGetCpt(input, jj+1)
                    cd_cpt = cr.CrateData()
                    cd_cpt.parent = obj
                    
                    self.__load_data(cd_cpt, cpt)
                    cd_cpt.vdim = 1
                    
                    if cd_cpt.parent.is_virtual():
                        cd_cpt._set_eltype(cr.VIRTUAL)

                    if cd_cpt is not None:
                        # add component name to the cpts list
                        cpt_list[cd_cpt.name] = cd_cpt
                        
                        # add component as column attribute
                        obj.__dict__[ cd_cpt.name ] = cd_cpt

                obj._set_cptslist(cpt_list)


    def __load_image_data(self, obj, input=None):
        
        if input is None:
            return

        if isinstance(input, dm.dmDescriptor):
            obj.name  = self.__dm_get_name(input)
            obj.desc  = dm.dmGetDesc(input)
            obj.unit  = dm.dmGetUnit(input)

            try:
                nullval = dm.dmDescriptorGetNull(input)
                obj._set_nullval( nullval )
            except (RuntimeError,TypeError) as ex:
                pass

            if obj.is_virtual() == False:
                type = dm.dmGetDataType(input)
                obj.values = dm.dmImageGetData(input)
                dims = dm.dmGetArrayDimensions(input) 
                
                if len(obj._values) == 1 and obj._values[0] is None:
                    if len(dims) >= 2: 
                        obj.values = empty( shape=dims, dtype=type )
                    else:
                        obj.values = empty( shape=(0), dtype=type )


    def read_header(self, crate):
        if isinstance(crate, cr.TABLECrate ) or isinstance(crate, cr.IMAGECrate ) :
            blockno = crate._number

            try:
                block = dm.dmDatasetGetBlock(self.__fptr, blockno)
            except:
                raise RuntimeError("Unable to read header from block.")

            crate._set_key_flag(True)

            # get keys from block and populate header list
            keylist = OrderedDict()
            history = crate._get_history()
            stdhdr_dict = CXCStandardHeader()

            for xx in range(1, dm.dmBlockGetNoKeys(block)+1):  
                dmkey = dm.dmBlockMoveToKey(block, xx) 
                done = False
                
                while (not done):
                    (tag, content) = dm.dmBlockReadComment( block )
                    if tag is None:
                        key = cr.CrateKey()
                        self.__load_key( key, dmkey ) 

                        try:
                            key.groups = stdhdr_dict.get_key_groups(key.name)
                        except:
                            pass

                        keylist[key.name.upper()] = key
                        done = True

                    else:
                        name = self.__dm_get_name(dmkey)
                        history.append( [tag, content, name] )


            crate._set_keylist(keylist)

        else:
            raise TypeError("Input must be a TABLECrate or an IMAGECrate object.")


    def write_key(self, key, blockno):
        try:
            block = dm.dmDatasetGetBlock( self.__fptr, blockno )
        except:
            raise RuntimeError("Unable to write key to block.")

        k = dm.dmKeyWrite(block, key.name, key.value, key.unit, key.desc)
        self._close_block( blockno )


    def _clobber(self, outfile, clobber=False):
        outfile = outfile.split('[')

        if clobber == False and outfile:
            if os.path.exists(outfile[0]):
                raise IOError("Unable to write to file; clobber is set to False and file " + 
                              outfile[0] + " exists.")
 
        if os.path.exists(outfile[0]):
            dm.dmDatasetDestroy( outfile[0] )
    

    def write(self, obj, outfile=None, history=True):

        if isinstance(obj, cr.CrateDataset) or isinstance(obj, cr.TABLECrate) or isinstance(obj, cr.IMAGECrate):

            #check if image in text format
            if isinstance(obj, cr.IMAGECrate) and outfile is not None:
                outfilename_attr = outfile.split('[')

                if len(outfilename_attr) > 1:
                    item_lc = outfilename_attr[1].lower()
                    if "kernel=text/" in item_lc and "dtf" not in item_lc:
                        raise IOError(
                            "Unable to create an image file in text/simple format. Use text/dtf or FITS instead.")

            close_flag = True   # lets the function know whether or not to 
                                # close the file after writing

            if outfile is None:
                outfile = obj.get_filename()

                if outfile is None:
                    raise IOError("Unable to write file without a specified destination.")

            if self.__fptr is None:
                try:
                    self.__fptr = dm.dmDatasetCreate( outfile )
                except IOError as ex:
                    try:
                        self.__fptr = dm.dmDatasetOpen( outfile )
                    except:
                        raise
                    pass
                except:
                    raise
            else:
                close_flag = False   # leave file open

            if isinstance( obj, cr.TABLECrate ) or isinstance(obj, cr.IMAGECrate):
                crate = obj
                self.__write_block( crate, close_flag, history )

            elif isinstance( obj, cr.CrateDataset ):
                ncrates = obj.get_ncrates()

                for ii in range(1, ncrates+1):
                    crate = obj.get_crate( ii )
                    self.__write_block( crate, close_flag, history )

                self.__remove_trash( obj )

            if close_flag:
                dm.dmDatasetClose( self.__fptr )

        else:
            raise TypeError("Input must be a CrateDataset or a Crate object.")


    def __write_block( self, crate, close_flag, history):
        block = None

        try: 
            block = dm.dmDatasetGetBlock( self.__fptr, crate.name )

        except ValueError as ex:
            try:
                if crate.is_image():
                    try:
                        block = dm.dmDatasetCreateImage( self.__fptr, crate.name, 
                                                         crate.image._values.dtype.type,
                                                         array(crate.get_shape()) )
                    except AttributeError as ex:
                        if "NoneType" in str(ex):
                            raise ValueError("Unable to create an image without any values.")
                        else:
                            raise 
                else:
                    block = dm.dmDatasetCreateTable( self.__fptr, crate.name )

            except:
                raise
            pass
        except:
            raise IOError("Unable to write crate " + crate.name + " to file.")

        self.__remove_trash(crate)

        self.__write_header(block, crate, history)

        self.__write_data(block, crate)
        self.__write_subspace(block, crate)

        if close_flag:
            dm.dmBlockClose( block ) 


    def __write_header(self, block, crate, history):
        if not isinstance(crate, cr.TABLECrate ) and not isinstance(crate, cr.IMAGECrate):
            raise TypeError("Input must be a TABLECrate or an IMAGECrate object.")                      
        records = crate.get_all_records()
        keylist = crate._get_keylist()

        if crate.get_nkeys() == 0 and len(records) == 0:
            return

        unicode_found = False

        update_mode = (dm.dmDatasetGetName(self.__fptr) == crate.get_filename())

        for name in list(keylist.keys()):
            if name == "HISTNUM":
                if history is False or len(crate._get_history()) == 0:
                    continue
                else:
                    keylist[name].value = crate.get_histnum()

            key = keylist[name]
            try:
                key_dd = dm.dmKeyWrite(block, key.name, key.value, unit=key.unit, desc=key.desc)
            except RuntimeError as ex:
                warnings.warn("RuntimeWarning: Unable to write key '" + key.name + "' to block.  Invalid key value.")
                pass

            if history is True:
                for rec in records:
                    # if in update mode and history/comment key has an after_key, skip writing out record 
                    if update_mode is True and len(rec[2]) > 0:
                        continue

                    # else only write out comment records attached to key name
                    if name == rec[2] and ("COMMENT" == rec[0] or "BLANK" == rec[0]):
                        try:
                            rec[1].encode('ascii')
                            dm.dmBlockWriteComment(block, "COMMENT", rec[1])
                        except UnicodeEncodeError:
                            unicode_found = True


        # write all History keys at the end of the header file
        # also write Comment key if there are no normal keys
        if  history is True:
            for rec in records:
                # if in update mode and history/comment key has an after_key, skip writing out record 
                if update_mode is True and len(rec[2]) > 0:
                    continue

                if "HISTORY" == rec[0] or (("COMMENT" == rec[0] or "BLANK" == rec[0]) and ((crate.get_nkeys() == 0) or len(rec[2]) == 0)):
                    try:
                        rec[1].encode('ascii')
                        if "BLANK" == rec[0]:
                            dm.dmBlockWriteComment(block, "COMMENT", rec[1])
                        else:
                            dm.dmBlockWriteComment(block, rec[0], rec[1])
                    except UnicodeEncodeError:
                            unicode_found = True

        if unicode_found:
            warnings.warn("Unicode detected in history/comment; those records have been omitted.")


    def __write_subspace(self, block, crate):
        if not isinstance(crate, cr.TABLECrate ) and not isinstance(crate, cr.IMAGECrate):
            raise TypeError("Input must be a TABLECrate or an IMAGECrate object.")                      

        # skip writing subspace if in update mode
        try:
            if crate.get_rw_mode() == "rw":
                
                ds = dm.dmBlockGetDataset(block)
                fname = crate.get_filename().split('[')

                if dm.dmDatasetGetName(ds) == fname[0]:
                    return
        except:
            return

        sslist = crate._get_subspace()

        for ii in range(0, len(sslist) ):
            ssdatadict = sslist[ii].get_subspace_datadict()

            for name in list(ssdatadict.keys()):
                ssitem = ssdatadict[name]
                sscol = None
                try:
                    sscol = dm.dmSubspaceColOpen(block, ssitem.name)
                    if len(ssitem.range_min) > 0:
                        dm.dmSubspaceColSet(sscol, ssitem.range_min, ssitem.range_max )

                except:
                    try: 
                        if len(ssitem.range_min) > 0:
                            sscol = dm.dmSubspaceColCreate( block, None, ssitem.name, ssitem.cpts,
                                                            ssitem.unit, ssitem.range_min, ssitem.range_max )
                        elif len(ssitem.cpts) > 0:
                            # only create a subspace vector if one or both components have ranges to write out
                            cptname1 = ssitem.cpts[0]
                            cptname2 = ssitem.cpts[1]

                            if (len(ssdatadict[cptname1].range_min) > 0) or (len(ssdatadict[cptname2].range_min) > 0):
                                sscol = dm.dmSubspaceColCreate( block, None, ssitem.name, ssitem.cpts,
                                                                ssitem.unit, 0.0, 0.0)
                    except:
                        pass
                try:
                    if sscol is not None:
                        if ssitem.region is not None:
                            if len(ssitem.table_name) > 0:
                                dm.dmSubspaceColSetRegion( sscol, ssitem.region, ssitem.table_name)
                            else:
                                dm.dmSubspaceColSetRegion( sscol, ssitem.region)
                        else:
                            if ssitem.table_name != "":
                                dm.dmSubspaceColSetTableName(sscol, ssitem.table_name)

                except:
                    raise

            # create next empty subspace in output file, unless this is the last iteration
            if ii+1 < len(sslist):
                dm.dmBlockCreateEmptySubspaceCpt( block )


    def __write_data(self, block, crate):
        if not isinstance(crate, cr.TABLECrate ) and not isinstance(crate, cr.IMAGECrate ):
            raise TypeError("Input must be a TABLECrate or an IMAGECrate object.")              

        datalist = crate._get_datalist()
        if datalist is None or len(datalist) == 0:
            return

        if crate.is_image():
            # IMAGE array
            img = datalist['image']
            self.__write_image( block, crate, img )
        else:
            # TABLE columns  
            dd_list = []
            dd_values = []
            dd_values_len = []
            for name in list(datalist.keys()):
                col = datalist[name]

                if col.is_virtual():  # col is VIRTUAL
                    self.__write_transform( block, col )

                else:                 # col is REGULAR
                    col_dd = self.__create_column_descriptor( block, col )
                    dd_list.append(col_dd)

                    if "uint8" in str(col.values.dtype) and col.is_bit_array() == True:
                        colvals = packbits(col.values, axis=-1)
                        dd_values.append(colvals)
                        dd_values_len.append(len(colvals))
                    else:
                        dd_values.append(col.values)
                        dd_values_len.append(len(col.values))


            nrows = max(dd_values_len)
            ncols = len(dd_list)
            
            # iterate by row
            for ii in range( 0, nrows ):
                #iterate by colname
                for jj in range(0, ncols): 
                    # make sure column has enough rows to write out
                    if dd_values_len[jj] > ii:
                        try:
                            dm.dmSetData( dd_list[jj], dd_values[jj][ii] )
                        except RuntimeError as ex:
                            pass
                dm.dmTableNextRow( block )


    def __create_column_descriptor(self, block, column):

        try:
            col_dd = dm.dmTableOpenColumn( block, column.name )

            if dm.dmDescriptorGetType( col_dd ) == dm.dmKEY:
                raise RuntimeError
            
            # update in place
            dm.dmSetName(col_dd, column.name)
            dm.dmSetUnit(col_dd, column.unit)
            dm.dmSetDesc(col_dd, column.desc)

        except RuntimeError as ex:
            try:
                
                coltype = column._values.dtype
                if column.is_varlen():
                    coltype = column._values[0].dtype

                colstrlen = 0
                if "str" in str(coltype.type) or "bytes" in str(coltype.type) or "unicode" in str(coltype.type):
                    colstrlen = column._values.astype('S').itemsize


                if column.is_vector():       # VECTOR 
                    if column.is_varlen():
                        tmp_dd = dm.dmColumnCreate(block, column.name, coltype.type, 
                                                   unit=column.unit, desc=column.desc, 
                                                   cptnames=column.get_cptslist(),
                                                   shape=array(column.get_dimarr()), varlen=True )
                    else:
                        tmp_dd = dm.dmColumnCreate(block, column.name, 
                                                   column._values.dtype.type, 
                                                   unit=column.unit, desc=column.desc,
                                                   cptnames=column.get_cptslist(),
                                                   shape=array(column.get_dimarr()) )

                elif (column.get_ndim() > 0):  # ARRAY
                    if "uint8" in str(coltype) and column.is_bit_array() == True:
                        collength = column._bit_array_itemsize
                        tmp_dd = dm.dmColumnCreate(block, column.name, coltype.type, 
                                                   unit=column.unit, desc=column.desc, 
                                                   itemsize=collength)
                    elif column.is_varlen():
                        tmp_dd = dm.dmColumnCreate(block, column.name, coltype.type, 
                                                   unit=column.unit, desc=column.desc, 
                                                   shape=array(column.get_dimarr()), varlen=True )

                    elif "str" in str(coltype.type) or "bytes" in str(coltype.type) or "unicode" in str(coltype.type):
                        tmp_dd = dm.dmColumnCreate(block, column.name, 
                                                   column._values.dtype.type, 
                                                   unit=column.unit, desc=column.desc, 
                                                   shape=array(column.get_dimarr()), 
                                                   itemsize=colstrlen )
                        
                    else: 
                        tmp_dd = dm.dmColumnCreate(block, column.name, 
                                                   column._values.dtype.type, 
                                                   unit=column.unit, desc=column.desc, 
                                                   shape=array(column.get_dimarr()) )

                else:                        # SCALAR
                    tmp_dd = dm.dmColumnCreate(block, column.name, 
                                               column._values.dtype.type, 
                                               unit=column.unit, desc=column.desc,
                                               itemsize=colstrlen)
 
            except AttributeError as ex:
                raise AttributeError("Unable to create column " + column.name + "; column attributes are not defined.")

            except ValueError as ex:
                err_str = self.__rm_func_name("dmColumnCreate()",str(ex))
                if err_str[err_str.__len__()-1] == ".":		# the string may contain a period
                    err_str = err_str.replace(".", "") 
                raise ValueError("Unable to create column " + column.name + ":" + err_str + ".")
 
            except:
                raise
            
            col_dd = tmp_dd[0]  #dmColumnCreate returns a list
            

            try:
                tlmin = column.get_tlmin()
                tlmax = column.get_tlmax()
                if tlmin is not None and tlmax is not None:
                    dm.dmDescriptorSetRange(col_dd, tlmin, tlmax)
            except TypeError as ex:
                pass
            
            try:
                if column.get_nullval() is not None:
                    dm.dmDescriptorSetNull(col_dd, column.get_nullval())
            except RuntimeError as ex:
                pass
            
            pass
        except:
            raise IOError("Unable to open or create column " + column.name + ".")
        
        return col_dd

	          
    def __write_image(self, block, crate, image):

        try:
            img_dd = dm.dmImageGetDataDescriptor( block )

        except:
            raise

        # set the Null value (i.e. the BLANK keyword value)
        nullval = image.get_nullval()
        if nullval is not None:
            dm.dmDescriptorSetNull(img_dd, nullval) 

        # set image descriptor units
        dm.dmSetUnit(img_dd, image.unit )

        axisnames = crate.get_axisnames()

        for name in axisnames:
            axis = crate.get_axis(name)

            ttype_name = axis.get_transform().get_className()

            if ttype_name == "LINEARTransform":
                try:
                    axis_dd = dm.dmArrayCreateAxisGroup( img_dd, name, image._values.dtype.type,
                                                         axis.unit, array("") )
                except:
                    raise
                    
                axis_trans = axis.get_transform()

                offset = axis_trans.get_parameter_value("OFFSET")
                cdelt =  axis_trans.get_parameter_value("SCALE")

                crpix = 0.5
                crval = offset + (crpix * cdelt)

                dm.dmCoordSetTransform( axis_dd, crpix, crval, cdelt)

            if ttype_name == "LINEAR2DTransform":
                try:
                    axis_dd = dm.dmArrayCreateAxisGroup( img_dd, name, image._values.dtype.type,
                                                         axis.unit, array(axis.get_cptslist()) )
                except:
                    raise
                    

                axis_trans = axis.get_transform()
                transform = tr.WCSTransform( axis_trans.get_name() )
                    
                matrix = axis_trans.get_transform_matrix()
                transform.set_transform_matrix( matrix )

                dm.dmCoordSetTransform( axis_dd, 
                                        transform.get_parameter_value("CRPIX"),
                                        transform.get_parameter_value("CRVAL"),
                                        transform.get_parameter_value("CDELT") )


            if ttype_name == "WCSTransform":
                self.__write_transform( block, axis )


        # if empty or NULL image, skip writing data
        if len(image.values) == 0: 
            return       

        try:
            dm.dmImageSetData( img_dd, image.values )
        except ValueError as ex:
            dm.dmImageSetData( img_dd, image.values.reshape( array(crate.get_shape())) )
            pass
        except:
            raise


    def __write_transform(self, block, item):
        try:
            tmp_dd = dm.dmTableOpenColumn( block, item.source.name )
        except RuntimeError as ex:
            # no source column
            return

        tmp_trans = item.get_transform()

        if isinstance(tmp_trans, tr.LINEARTransform):
            offset = tmp_trans.get_parameter_value("OFFSET")
            cdelt =  tmp_trans.get_parameter_value("SCALE")
            crpix = 0.5
            crval = offset + (crpix * cdelt)
            ctype="LINEAR"

        else:
            
            if isinstance(tmp_trans, tr.LINEAR2DTransform):
                transform = tr.WCSTransform( tmp_trans.get_name() )
                matrix = tmp_trans.get_transform_matrix()
                transform.set_transform_matrix( matrix )
                ctype="LINEAR"
            elif isinstance(tmp_trans, tr.WCSTransform):
                transform = tmp_trans
                ctype_param = tmp_trans.get_parameter_value("CTYPE")

                if ctype_param is None:  # default
                    ctype = "TAN"
                else:
                    if "LONG" in str(ctype_param[0]):
                        ctype = "TAN-P"
                    else:                
                        pos2 = len(ctype_param[0])
                        pos1 = pos2 - 3
                        ctype = (ctype_param[0])[pos1 : pos2]


            else:
                return
            crpix=transform.get_parameter_value("CRPIX")
            crval=transform.get_parameter_value("CRVAL")
            cdelt=transform.get_parameter_value("CDELT")


        try: 
            coord_dd = dm.dmDescriptorGetCoord(tmp_dd)
            dm.dmCoordSetTransform(coord_dd, crpix, crval, cdelt)

        except RuntimeError as ex:
            cpars = None

            if not isinstance(tmp_trans, tr.LINEARTransform):
                crota = transform.get_parameter_value("CROTA")
                equinox = transform.get_parameter_value("EQUINOX")

                cpars = []
                cpars.append(crota)
                cpars.append( 0 )
                cpars.append( 0 )
                cpars.append(equinox)

            cptnames = item.get_cptslist()

            if "bytes" in str(type(ctype)):
                ctype = ctype.decode("utf-8")

            if cptnames:
                if cpars is None:
                    dm.dmCoordCreate( tmp_dd, item.name, item.unit, 
                                      cptnames=cptnames, transform=str(ctype),
                                      crpix=crpix, crval=crval, cdelt=cdelt )
                else:
                    dm.dmCoordCreate( tmp_dd, item.name, item.unit, 
                                      cptnames=cptnames, transform=str(ctype),
                                      crpix=crpix, crval=crval, cdelt=cdelt,
                                      params=array(cpars))
            else:
                if cpars is None:
                    dm.dmCoordCreate( tmp_dd, item.name, item.unit, 
                                      transform=str(ctype),
                                      crpix=crpix, crval=crval, cdelt=cdelt)
                else:
                    dm.dmCoordCreate( tmp_dd, item.name, item.unit, 
                                      transform=str(ctype),
                                      crpix=crpix, crval=crval, cdelt=cdelt,
                                      params=array(cpars))
                
            pass
        except:
            raise


    def read_columns(self, crate):

        if isinstance(crate, cr.TABLECrate):
            blockno = crate._number
            block = dm.dmDatasetGetBlock(self.__fptr, blockno)

            ncols = dm.dmTableGetNoCols(block)
            cols = []
            if ncols != 0:
                cols = dm.dmTableOpenColumnList( block ) 

            datalist = OrderedDict()
            virtual_datalist = OrderedDict()

            nrows = crate.get_nrows()

            crate._set_data_flag(True)

            total_cols = ncols 
            for ii in range(len(cols)):
                cd = cr.CrateData()

                try:
                    self.__load_data(cd, cols[ii], nsets=nrows, block=block)    
                except TypeError as ex: 
                    warnings.warn("Unable to load column. " + str(ex), RuntimeWarning)
                    continue

                if cd is not None:
                    # add column as attribute
                    if hasattr(crate, cd.name):
                        warnings.warn("Unable to create named-column attribute for column '" + cd.name + "'.  Attribute already exists.")
                    else:
                        crate.__dict__[ cd.name ] = cd

                    # add column name to the datalist
                    datalist[ cd.name ] = cd

                    # if this column has a transform, read it in
                    ncoords = dm.dmDescriptorGetNoCoords(cols[ii])
                    
                    if ncoords > 0:

                        for jj in range(1, ncoords+1):
                            dd = dm.dmDescriptorGetCoord(cols[ii], jj)
                            (ttype, nparams) = dm.dmCoordGetTransformType(dd)

                            newcol = cr.CrateData()

                            try:
                                colname = self.__dm_get_name(dd)
                                if colname=="":                                # catch "" of SL-232 fixing 
                                    total_cols += 1                            # increment col# for new column
                                    colname = "COLUMN" + str( total_cols )     # assign string and the index#
                        
                                newcol.name = str.strip(colname)
                            except RuntimeError:
                                total_cols += 1                                # increment col# for new column
                                newcol.name = "COLUMN" + str( total_cols )  
            
                            newcol._set_eltype(cr.VIRTUAL)
                            newcol.source = cd

                            self.__load_data(newcol, dd, colname=newcol.name)

                            transform = None
                            if "LINEAR" in ttype:
                                transform = self.__create_linear_transform( dd, colname=newcol.name )
                            else:
                                cnames = newcol.get_cptslist()
                                transform = self.__create_wcs_transform( dd, ttype=ttype, 
                                                                         cptnames=cnames,
                                                                         colname=newcol.name )

                            newcol.set_transform( transform )
                            virtual_datalist[ newcol.name ] = newcol


            # if there are any virtual columns, append those to the 
            # end of the datalist

            if len(virtual_datalist) > 0:
                datalist.update(virtual_datalist)

            # set the datalist
            crate._set_datalist(datalist)

        else:
            raise TypeError("Input must be a TABLECrate object.")


    def read_subspace(self, crate):
        if not isinstance(crate, cr.TABLECrate ) and not isinstance(crate, cr.IMAGECrate ) :
            raise TypeError("Input must be a TABLECrate or an IMAGECrate object.")

        blockno = crate._number
        block = dm.dmDatasetGetBlock(self.__fptr, blockno)

        try:
            (ncpts, ncols, descs) = dm.dmBlockGetSubspace( block )
        except:
            return   # no subspace for this block

        subspace_list = []

        # iterate over subspace components
        for ii in range(ncpts):
            
            dm.dmBlockSetSubspaceCpt(block, ii+1)
            ss = cr.CrateSubspace()

            # iterate through column list
            for jj in range(ncols):
                col = descs[jj]

                ssdata = cr.CrateSubspaceData()
                ssdata.name = self.__dm_get_name(col)
                ssdata.unit = dm.dmGetUnit(col)
                ssdata.table_name = dm.dmSubspaceColGetTableName(col)
                ssdata.region = dm.dmSubspaceColGetRegion(col)

                extent = None
                if ssdata.region is not None:
                    extent = regExtent(ssdata.region)
                ss.add_subspace_data(ssdata)

                vdim = dm.dmGetElementDim(col)
                dtype = dm.dmGetDataType(col)

                # loop over vector column components
                if vdim > 1:
                    for kk in range(vdim):                    
                        colcpt = dm.dmGetCpt(col, kk+1)

                        cptname = self.__dm_get_name(colcpt)
                        ssdata.cpts.append( cptname )
                        ssdata_cpt = cr.CrateSubspaceData()
                        ssdata_cpt.name = cptname
                        ssdata_cpt.unit = dm.dmGetUnit(colcpt)

                        try:
                            if ssdata.region is not None:
                                ssdata_cpt.range_min = array([extent[ kk ]], dtype=double)
                                ssdata_cpt.range_max = array([extent[ kk+vdim ]], dtype=double)
                            else:
                                (sscol, ssdata_cpt.range_min, ssdata_cpt.range_max) = dm.dmSubspaceColRead( block, cptname )
                        except ValueError:
                            pass
 
                        ss.add_subspace_data(ssdata_cpt)

                else:
                    try:
                        (sscol, ssdata.range_min, ssdata.range_max) = dm.dmSubspaceColRead( block, ssdata.name )
                    except ValueError:
                        pass

                if not isinstance(ssdata.range_min, ndarray):
                    ssdata.range_min = array(ssdata.range_min, dtype=dtype)
                    ssdata.range_max = array(ssdata.range_max, dtype=dtype)

            subspace_list.append(ss)
        
        crate._set_subspace(subspace_list)


    def read_image(self, crate):
        if isinstance( crate, cr.IMAGECrate):
            blockno = crate._number
            block = dm.dmDatasetGetBlock(self.__fptr, blockno)

            imgdd = dm.dmImageGetDataDescriptor(block)

            cd = cr.CrateData()
            cd._image = True

            try: 
                self.__load_image_data(cd, imgdd)
            except TypeError as ex:
                warnings.warn("Unable to load image. " + str(ex), RuntimeWarning)
                return

            if cd is not None:
                # add image as attribute
                crate.__dict__[ "image" ] = cd

                # set-up image coordinate system information
                self.__read_axes(crate, cd, imgdd)
                
                # create datalist
                datalist = OrderedDict()
                datalist[ "image" ] = cd
                
                crate._set_datalist( datalist )

        else:
            raise TypeError("Input must be an IMAGECrate object.")



    def __read_axes(self, crate, cd, imgdd):
        if isinstance(crate, cr.IMAGECrate ):

            ngroups = dm.dmArrayGetNoAxisGroups(imgdd)

            if ngroups > 0:
                axes = OrderedDict()
            else:
                return
            
            #loop over PHYSICAL axes
            for ii in range(1, ngroups+1):

                phys_dd = dm.dmArrayGetAxisGroup(imgdd, ii)
                naxes = dm.dmGetElementDim( phys_dd )

   
                # LOGICAL axis - generate an index coordinate list 
                logical = self.__create_index_list(cd, naxes)

                # PHYSICAL axis - uses a LINEAR2DTranform on the logical values
                physical = cr.CrateData()
                physical._set_eltype(cr.VIRTUAL)
                physical.source = logical

                self.__load_data( physical, phys_dd )

                transform = self.__create_linear_transform( phys_dd )
                physical.set_transform( transform )

                axes[ physical.name ] = physical

                # WORLD axis - uses WCSTransform on physical values

                ncoords = dm.dmDescriptorGetNoCoords( phys_dd )
                
                for coordno in range(1, ncoords+1):
                    new_dd = dm.dmDescriptorGetCoord( phys_dd, coordno )
                    (ttype, nparams) = dm.dmCoordGetTransformType(new_dd)

                    world = cr.CrateData()
                    world._set_eltype(cr.VIRTUAL)
                    world.source = physical
                    
                    self.__load_data( world, new_dd )

                    transform = None

                    if "LINEAR" in ttype:
                        transform = self.__create_linear_transform( new_dd )
                    else:
                        cnames = world.get_cptslist()                            
                        transform = self.__create_wcs_transform( new_dd, ttype=ttype, 
                                                                 cptnames=cnames)

                    world.set_transform( transform )
                    axes[ world.name ] = world

            # Set IMAGECrate axes dictionary
            crate._set_axes(axes)            
        else:
            raise TypeError("Input must be an IMAGECrate object.")


    def __create_index_list(self, cd, naxes):

        logical = cr.CrateData()
        logical.name = "INDEX"
        logical.desc = "Logical coordinate value set"
        
        # assumes axis group number one - dmCoordGetAxisGroupNo( phys_dd )

        dimarr = cd.get_dimarr()

        arrsize=1
        for ii in range(0, len(dimarr)):
            arrsize = arrsize * dimarr[ii] 

        offset = 0.5
        darr = None

        if len(dimarr) == 2 and naxes == 2:
            darr = ndarray( shape=(arrsize, 2) )
            ndx = 0
            
            for ndx in range (dimarr[0]):
                start = ndx * dimarr[1]
                end   = start + dimarr[1]
                darr[start:end:, 0] = arange(offset, dimarr[1]+offset, 1)
                darr[start:end:, 1] = offset + ndx

            logical.vdim = 2
        else:
            darr = arange(offset, arrsize+offset, 1)

        logical.values = darr

        return logical



    def __create_linear_transform(self, phys_dd, colname=""):
        
        if colname:
            name = colname
        else:
            name = self.__dm_get_name(phys_dd)
 
        
        trans = None

        (crpix, crval, cdelt) = dm.dmCoordGetTransform( phys_dd )
        params =  dm.dmCoordGetParams( phys_dd )

        if len(crpix) == 2:
            trans = tr.LINEAR2DTransform( name )

            crota = params[0]
            
            tmp_param = trans.get_parameter("ROTATION")
            tmp_param.set_value( crota )
            
            # calculate offset
            rho = crota*(pi.real/180.0);
            offset = ndarray(shape=[2])
            
            if rho == 0:
                offset[0] = (-crpix[0] * cdelt[0]) + crval[0]
                offset[1] = (-crpix[1] * cdelt[1]) + crval[1]
            else:
                offset[0] = crval[0] - cdelt[0]*crpix[0]*cos(rho) - cdelt[0]*crpix[1]*sin(rho)
                offset[1] = crval[1] + cdelt[1]*crpix[0]*sin(rho) - cdelt[1]*crpix[1]*cos(rho)
        else:
            trans = tr.LINEARTransform( name )
            offset = crval - (crpix * cdelt)
            

        tmp_param = trans.get_parameter("OFFSET")
        tmp_param.set_value( offset )

        tmp_param = trans.get_parameter("SCALE")
        tmp_param.set_value( cdelt )

        return trans


    def __create_wcs_transform(self, world_dd, ttype="", cptnames="", colname=""):

        if colname:
            name = colname
        else:
            name = self.__dm_get_name(world_dd)

        trans = tr.WCSTransform( name )

        (crpix, crval, cdelt) = dm.dmCoordGetTransform( world_dd )
        params =  dm.dmCoordGetParams( world_dd )

        crota = params[0]
        epoch = 2000
        equinox = 0.0
        if params[3] != 0:
            equinox = params[3]
        else:
            equinox = 2000.0

        if ttype == "TAN":
            for ii in range(0, len(cptnames)):
                if cptnames[ii].upper().find("RA",0, 2) != -1:
                    cptnames[ii] = "RA"
                if cptnames[ii].upper().find("DEC",0, 3) != -1:
                    cptnames[ii] = "DEC"

        if ttype == "TAN-P":
            cptnames = ['LONG', 'NPOL']
            ttype = "TAN"

        ctype = []

        for ii in range(0, len(cptnames)):
            ctype_str = self.__get_ctype( cptnames[ii], ttype )
            ctype.append( ctype_str )

        tmp_param = trans.get_parameter("CRPIX")
        tmp_param.set_value( crpix )

        tmp_param = trans.get_parameter("CRVAL")
        tmp_param.set_value( crval )

        tmp_param = trans.get_parameter("CDELT")
        tmp_param.set_value( cdelt )

        tmp_param = trans.get_parameter("CROTA")
        tmp_param.set_value( crota )

        tmp_param = trans.get_parameter("EPOCH")
        tmp_param.set_value( epoch )

        tmp_param = trans.get_parameter("EQUINOX")
        tmp_param.set_value( equinox )

        tmp_param = trans.get_parameter("CTYPE")
        tmp_param.set_value( ctype )
        
        return trans


    def __get_ctype(self, cptname, ttype):

        ii = len(cptname)
        ctype = cptname
        while (ii < 5):
            ctype += '-'
            ii += 1
        ctype += ttype

        return ctype


    def __remove_trash(self, obj):

        if isinstance(obj, cr.CrateDataset):
            trash = obj._get_crate_trash()
            for item in trash:
                try:
                    block = dm.dmDatasetGetBlock(self.__fptr, item)

                    if block:
                        try:
                            dm.dmBlockDelete( block )
                        except IOError as ex:
                            pass
                except ValueError as ex:
                    pass

        if isinstance(obj, cr.TABLECrate) or isinstance(obj, cr.IMAGECrate):
            try:
                block = dm.dmDatasetGetBlock(self.__fptr, obj.name)
            except ValueError as ex:
                return
              
            ktrash = obj._get_key_trash()
            for item in ktrash:
                try:
                    key = dm.dmBlockMoveToKey(block, item)
                    if key:
                        dm.dmDescriptorDelete( key )
                except ValueError as ex:
                    pass


            dtrash = obj._get_data_trash()       	   
            for item in dtrash:
                try:
                    if isinstance(obj, cr.TABLECrate ):
                        data = dm.dmTableOpenColumn(block, item)
                    else:
                        data = dm.dmImageGetDataDescriptor(block)
                except RuntimeError as ex:
                    return

                try:
                    if data:
                        ncoords = dm.dmDescriptorGetNoCoords( data )
                        if ncoords > 0:
                            for jj in range(1, ncoords+1):
                                dd = dm.dmDescriptorGetCoord(data, jj)
                                dm.dmDescriptorDelete( dd )

                        dm.dmDescriptorDelete( data )


                except RuntimeError as ex:
                    warnings.warn("Unable to delete columns or images in update mode.")
                    pass


    def write_rmf(self, obj, outfile=None, history=True):

        if isinstance(obj, cr.RMFCrateDataset):

            close_flag = True   # lets the function know whether or not to 
                                # close the file after writing
            if outfile is None:
                outfile = obj.get_filename()

                if outfile is None:
                    raise IOError("Unable to write file without a specified destination.")

            if self.__fptr is None:
                try:
                    self.__fptr = dm.dmDatasetCreate( outfile )
                except IOError as ex:
                    try:
                        self.__fptr = dm.dmDatasetOpen( outfile )
                    except:
                        raise
                    pass
                except:
                    raise
            else:
                close_flag = False   # leave file open

            self.__remove_trash( obj )

            # write MATRIX extension
            try:
                crate = obj.get_crate( "MATRIX" )
            except IndexError as ex:
                try:
                    crate = obj.get_crate( "SPECRESP MATRIX" )
                except IndexError as ex:
                    try:
                        crate = obj.get_crate( "AXAF_RMF" )
                    except IndexError as ex:
                        raise IndexError(" MATRIX Crate not found")
                    pass
                pass
            pass

            if crate:
                self.__write_block( crate, close_flag, history )
 
            # write EBOUNDS extension
            crate = obj.get_crate( "EBOUNDS" )
            if crate:
                self.__write_block( crate, close_flag, history )
                
            # write all other extensions (if any)
            ncrates = obj.get_ncrates()
            for ii in range(1, ncrates+1):
                crate = obj.get_crate( ii )
                crname = crate.name.upper()
                if crname not in ["EBOUNDS", "MATRIX", "SPECRESP MATRIX", "AXAF_RMF"]:
                    self.__write_block( crate, close_flag, history )

            if close_flag:
                dm.dmDatasetClose( self.__fptr )

        else:
            raise TypeError("Input must be an RMFCrateDataset object.")
