# 
#  Copyright (C) 2010,2016  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 cxcdm import *
from cxcdm._cxcdm import _dmpBlockOpenMode
import numpy as np

# Define 'constants' for dmBlockType 
dmUNKBLOCK = dmBlockType(0)
dmTABLE    = dmBlockType(1)
dmIMAGE    = dmBlockType(2)


# ------------------------------------------------------------------------------
#                              dmBlockCreateCopy                                  
# ------------------------------------------------------------------------------
def dmBlockCreateCopy( ds, name, inblock, copydata=True ):
    """
    dmBlockCreateCopy( ds=<dmDataset>, name, inblock=<dmBlock>[, copydata=<True|False>)

    Copy the provided block to the dataset.
       ds - Required
          Dataset in which to add the block.

       name - Required
          Name of resulting block in dataset.

       inblock - Required
          Source block to copy into dataset.

       copydata - Optional
          Flag to control transfer of data with block definition
             True  = copy block definition and data
             False = copy block definition only

    """
    res = None
    histr = "";

    try:
        res = dmBlockCreateGenCopy( ds, name, inblock, histr, copydata, True )
    except:
        raise

    return res


# ------------------------------------------------------------------------------
#                                  dmBlockOpen                                  
# ------------------------------------------------------------------------------
def dmBlockOpen( name, ds=None, blktype=dmUNKBLOCK, update=False ):
    """
    dmBlock dmBlockOpen( name [, ds=<dmDataset>, blktype=<dmUNKBLOCK|dmTABLE|dmIMAGE>, update=<True|False>] )

    Open the specified file or block.
       ds - Optional
         If provided, the block specified by 'name' is opened.

       blktype - Optional
         Specifies the type of block to be opened

       update - Optional
         Update flag
            True  = open in Read/Write mode
            False = open in Read-only mode
         
    """
    res = None

    # translate update flag to read mode
    if update == True:
        mode = "RW"
    else:
        mode = "R"
    
    try:
        res = _dmpBlockOpenMode( name, ds, blktype, mode, "dmBlockOpen" )
    except:
        raise

    return res


# ------------------------------------------------------------------------------
#                                  dmTableCreate                                  
# ------------------------------------------------------------------------------
def dmTableCreate( name ):
    """
    dmBlock dmTableCreate( name )

    Create the specified table.
       name - Required
         File name to create.
         
    """
    res = None
    ds = None
    blktype = dmTABLE
    mode = "W"
    
    try:
        res = _dmpBlockOpenMode( name, ds, blktype, mode, "dmTableCreate" )
    except:
        raise

    return res

# ------------------------------------------------------------------------------
#                                dmTableAllocRows                                  
# ------------------------------------------------------------------------------
def dmTableAllocRow( obj, nrows=1 ):
    """
    recarr = dmTableAllocRow( <block|cols>, nrows=1 )

    Defines a creates a structured array based on block column list with the
    specified number of rows.

    Input argument may be a dmBlock object containing the column list
    or list of dmDescriptor objects defining the columns directly.
    
    """
    res = None

    if type(obj) is dmBlock:
        cols = dmTableOpenColumnList(obj)
    elif type(obj) is list and type(obj[0]) is dmDescriptor:
        cols = obj
    else:
        raise TypeError("dmTableAllocRow() argument 1 must be type dmBlock or list of type dmDescriptor.")

    # extract names and formats of columns in list.
    (names, formats) = cxc_get_row_struct( cols )

    # create structured array from definition... zeroed
    res = np.zeros(nrows, dtype={'names':names, 'formats':formats})

    return res

# ------------------------------------------------------------------------------
#                                dmTableGetRow                                  
# ------------------------------------------------------------------------------
def dmTableGetRow( block ):
    """
    rowdat = dmTableGetRow( block )

    Reads current row into a structured array.
    """
    res = dmTableGetRows( block, 1 )
    
    return res

# ------------------------------------------------------------------------------
#                                dmTableGetRows                                 
# ------------------------------------------------------------------------------
def dmTableGetRows( block, nrows ):
    """
    rowdat = dmTableGetRows( block, nrows )

    Reads up to nrows of data into a structured array, starting at current row.
    """
    res = None

    if nrows < 1:
        raise ValueError("dmTableAllocRow() invalid argument value, must be > 0.")
        return res
    
    current_row = dmTableGetRowNo( block )
    if current_row < 0:
        return res

    cols = dmTableOpenColumnList(block)

    res = dmTableAllocRow( cols, nrows )

    for row in range(nrows):
        for ii in range(len(res[0])):
            #NOTE: we populate the row this way (rather than the DM function)
            # because there are some size conversions for certain datatypes..
            # we cannot just send in the data pointer.
            vals = dmGetData( cols[ii] )
            if vals.ndim == 0:
                res[row][ii] = vals
            else:
                res[row][ii].flat[:] = vals.flat[:]

        newrow = dmTableNextRow( block )
        if newrow is None:
            # Hit EOF.. reduce array to just those read.
            res = res[:row+1]
            break

    return res


# ------------------------------------------------------------------------------
#                                  dmTableOpen                                  
# ------------------------------------------------------------------------------
def dmTableOpen( name, update=False ):
    """
    dmBlock dmTableOpen( name [, update=<True|False>] )

    Open the specified table.
       name - Required
         File name to open.

       update - Optional
         Update flag
            True  = open in Read/Write mode
            False = open in Read-only mode
         
    """
    res = None
    ds = None
    blktype = dmTABLE

    # translate update flag to read mode
    if update == True:
        mode = "RW"
    else:
        mode = "R"
    
    try:
        res = _dmpBlockOpenMode( name, ds, blktype, mode, "dmTableOpen" )
    except:
        raise

    return res


# ------------------------------------------------------------------------------
#                                  dmImageCreate                                  
# ------------------------------------------------------------------------------
def dmImageCreate( name, dtype, axes ):
    """
    dmBlock dmImageCreate( name, dtype, axes )

    Create the specified table.
       name - Required
         File name to create.

       dtype - Required

       axes - Required
         Array providing the shape of the image.
    """

    ds = None
    
    try:
        res = dmDatasetCreateImage( ds, name, dtype, axes )
    except:
        raise

    return res

# ------------------------------------------------------------------------------
#                                  dmImageOpen                                  
# ------------------------------------------------------------------------------
def dmImageOpen( name, update=False ):
    """
    dmBlock dmImageOpen( name [, update=<True|False>] )

    Open the specified image.
       name - Required
         File name to open.

       update - Optional
         Update flag
            True  = open in Read/Write mode
            False = open in Read-only mode
         
    """
    res = None
    ds = None
    blktype = dmIMAGE

    # translate update flag to read mode
    if update == True:
        mode = "RW"
    else:
        mode = "R"
    
    try:
        res = _dmpBlockOpenMode( name, ds, blktype, mode, "dmImageOpen" )
    except:
        raise

    return res


# ------------------------------------------------------------------------------
#                              dmDatasetCreateTable                             
# ------------------------------------------------------------------------------
def dmDatasetCreateTable( ds, name ):
    """
    dmBlock dmDatasetCreateTable( ds=<dmDataset>, name )

    Create the specified table in existing dataset.
       ds   - Required
         dmDataset in which to create the named table.
         
       name - Required
         File name to create.
         
    """
    res = None
    blktype = dmTABLE
    mode = "W"

    #MCD NOTE:  We could (should?) check if the input ds is writeable.
    
    try:
        res = _dmpBlockOpenMode( name, ds, blktype, mode, "dmDatasetCreateTable" )
    except:
        raise

    return res



# ------------------------------------------------------------------------------
#                              cxc_get_row_struct                               
# ------------------------------------------------------------------------------
def cxc_get_row_struct( cols ):
    """
    """

    names   = []
    formats = []

    for dd in cols:
        name  = dmGetName(dd)
        
        dtp   = np.dtype(dmGetDataType(dd))
        kind  = dtp.kind
        size  = dtp.itemsize

        if kind == 'S':
            kind = 'a'
            size = dmDescriptorGetLength(dd)

        vdim  = dmGetElementDim(dd)
        dims  = dmGetArrayDimensions(dd)

        tarr  = []
        if vdim > 1:
            tarr.append(str(vdim))
        
        if len(dims) > 0:
            for n in range(0,len(dims)):
                tarr.append(str(dims[n]))

        if len(tarr) == 0:
            fmt = "%c%d" % (kind,size)
        elif len(tarr) == 1:
            fmt = "%s%c%d" % (tarr[0],kind,size)
        else:
            fmt = "(%s)%c%d" % ( ",".join(tarr[:]),kind,size)

        names.append(name.lower())
        formats.append(fmt)

    return (names, formats)
