# 
#  Copyright (C) 2010-2011,2015-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.
#

import cxcdm._cxcdm as _cxcdm
import numpy as np

# Define 'constants' for dmDescriptorType
dmBADDESC = _cxcdm.dmDescriptorType(0)
dmKEY = _cxcdm.dmDescriptorType(1)
dmSUBSPACE = _cxcdm.dmDescriptorType(2)
dmCOORD = _cxcdm.dmDescriptorType(3)
dmCOLUMN = _cxcdm.dmDescriptorType(6)

# Define 'constants' for dmElementType
dmVALUE = _cxcdm.dmElementType(0)
dmINTERVAL = _cxcdm.dmElementType(1)
dmRANGE = _cxcdm.dmElementType(2)


# ------------------------------------------------------------------------------
#                              dmColumnCreate                                   
# ------------------------------------------------------------------------------
def dmColumnCreate( block, name, dtype, **kwargs):
    """
    dmColumnCreate( block=<dmBlock>, name, dtype [, itemsize, unit, desc, cptnames, shape])

    Create n columns in a specified block. Scalar values create a single column. The 
    return value is a single column or an array of multiple columns.
       block - Required
          The block that the columns will be added to.

       name - Required
          The name(s) of the column(s).

       dtype - Required
          The data type(s) of the column(s).

       itemsize - Optional
	  The length(s) of string data column(s)

       unit - Optional
          The unit(s) of the column(s) data.

       desc - Optional
          A description(s) of the column(s).

       cptnames - Optional
          Names of the vector columns components

       shape - Optional
          The dimensions of the data

       varlen - Optional
          Flag indicating column(s) is a variable length array

    """

    # validate the keywords passed in
    valid_key_words = ['itemsize', 'unit', 'desc', 'cptnames', 'shape', 'varlen']
    for key in kwargs:
       if not (key in valid_key_words):
          raise TypeError ("dmColumnCreate() got an unexpected keyword argument '%s'"%key)
    

    raise_error = ''
    ncolumns = 0

    # check to see if there are multiple columns
    ncolumns = len(name)

    # if input name is scalar and of bytes datatype, convert to utf-8 str
    if "bytes" in str(type(name)):
        name=name.decode("utf-8")

    # make sure each arg is a list
    new_kwargs = {}

    # check for strings - try to features which are common to strings
    # this isn't the most secure way of doing this but it should weed out
    # the majority of false positives
    try:
        name + 'a'
        name.endswith
    except:
        pass
    else:
        ncolumns = 1
        name = [name]

    if ncolumns == 0:
        raise AttributeError ('Expected ncolumns to be greater than 0')

    # check each of the input values to make sure that they are of the same length
    try:
        nopts = len(dtype)
    except:
        nopts = 1
        dtype = [dtype]

    if ncolumns != nopts:
        raise AttributeError ('The number of data types, %d, does not match the number of column names, %d'%(nopts, ncolumns))

    # to reduce the possibility of type matching messing up assume for
    # the simple and most common case that a single column has none of
    # it's key words wrapped in a list and wrap each one of them as a list
    # of a single element
    if ncolumns == 1:
        for key in kwargs.keys ():
            new_kwargs[key] = [kwargs[key]]
    else:
        for key in kwargs.keys ():
            try:
                nopts = len(kwargs[key])

                # check for strings - try 2 features which are common to strings
                # this isn't the most secure way of doing this but it should 
                # weed out the majority of false positives
                try:
                    kwargs[key] + 'a'
                    kwargs[key].endswith
                except:
                    new_kwargs[key] = kwargs[key]
                else:
                    nopts = 1
                    new_kwargs[key] = [kwargs[key]]

                # check to see if the cptnames is a list of strings or a list
                # of lists
                if key == 'cptnames':
                    list_of_strings = True
                    tmp_cptnames = []

                    for ii in kwargs[key]:
                        try:
                            ii + 'a'
                            ii.endswith
                        except:
                            list_of_strings = False
                            tmp_cptnames.append(ii)
                        else:
                            tmp_cptnames.append([ii])

                    if list_of_strings:
                        nopts = 1
                        new_kwargs[key] = [kwargs[key]]
                    else:
                        nopts = len(tmp_cptnames)
                        new_kwargs[key] = tmp_cptnames
            except:
                nopts = 1
                new_kwargs[key] = ([kwargs[key]])

            if ncolumns < nopts:
                raise AttributeError ('The number of %s, %d, is greater than the number of column names, %d'%(key, nopts, ncolumns))


    descriptors = [None]*ncolumns
    for ii in range (ncolumns):
        tmp_kwargs = {}
        for jj in kwargs.keys ():
           if ii < len(new_kwargs[jj]):
              tmp_kwargs[jj] = new_kwargs[jj][ii]


        descriptors[ii] = _cxcdm.dmColumnCreate (block, name[ii], dtype[ii], **tmp_kwargs)

    return descriptors



# ------------------------------------------------------------------------------
#                              dmCoordCalc                                   
# ------------------------------------------------------------------------------
def dmCoordCalc( dd , data ):
    """
    dmCoordCalc( dd=<dmDescriptor>, data )

    Apply coordinate transform contained within the dmDescriptor to the
    supplied data.

    Return value is a float64 array containing the results of the transform.

    NOTE: The input array can contain multiple sets of coordinates to transform.
          ie: if descriptor accepts (x,y) pairs, the input may be an array
              of (x,y) pairs.
              
       dd - Required
          dmDescriptor containing the transform to apply.

       data - Required
          Array of coordinates on which to apply the transform.
          
    """

    res = None

    datarr   = np.array(data)
    dat_dims = list(datarr.shape)

    # Get data expectation of descriptor
    try:
        vdim = _cxcdm.dmGetElementDim(dd)
        dims = _cxcdm.dmGetArrayDimensions(dd)
    except TypeError:
        raise TypeError("dmCoordCalc() argument 1 must be dmDescriptor" )
    except:
        raise

    dd_dims = []
    if ( vdim > 1 ):
        dd_dims.append(int(vdim))

    for d in dims:
        dd_dims.append(d)

    # Run input coordinate 'sets' through base function one at a time.
    if ( dat_dims == dd_dims ):
        # Input data same shape as descriptor.. 1 set
        res = _cxcdm._dmCoordCalc( dd, datarr )
        
    elif (len(dat_dims) == len(dd_dims)+1) and ( dat_dims[1:] == dd_dims ):
        # Input data same shape as descriptor.. n sets 

        # Create output array.. same shape as input.
        res = np.zeros(shape=dat_dims, dtype=np.float64)
        
        for ii in range( dat_dims[0] ):
            res[ii] = _cxcdm._dmCoordCalc( dd, datarr[ii] )

    else:
        raise ValueError("dmCoordCalc() input data not same shape as dmDescriptor.")

    return res


# ------------------------------------------------------------------------------
#                              dmCoordInvert                                    
# ------------------------------------------------------------------------------
def dmCoordInvert( dd , data ):
    """
    dmCoordInvert( dd=<dmDescriptor>, data )

    Apply inverse coordinate transform contained within the dmDescriptor to the
    supplied data.

    Return value is a float64 array containing the results of the transform.

    NOTE: The input array can contain multiple sets of coordinates to transform.
          ie: if descriptor accepts (x,y) pairs, the input may be an array
              of (x,y) pairs.
              
       dd - Required
          dmDescriptor containing the transform to apply.

       data - Required
          Array of coordinates on which to apply the transform.
          
    """

    res = None

    dat_dims = list(np.array(data).shape)

    # Get data expectation of descriptor
    try:
        vdim = _cxcdm.dmGetElementDim(dd)
        dims = _cxcdm.dmGetArrayDimensions(dd)
    except TypeError:
        raise TypeError("dmCoordCalc() argument 1 must be dmDescriptor" )
    except:
        raise

    dd_dims = []
    if ( vdim > 1 ):
        dd_dims.append(int(vdim))

    for d in dims:
        dd_dims.append(d)


    # Create output array.. same shape as input.
    res = np.zeros(shape=dat_dims, dtype=np.float64)

    # Run input coordinate 'sets' through base function one at a time.
    if ( dat_dims == dd_dims ):
        # Input data same shape as descriptor.. 1 set
        res = _cxcdm._dmCoordInvert( dd, data )
        
    elif (len(dat_dims) == len(dd_dims)+1) and ( dat_dims[1:] == dd_dims ):
        # Input data same shape as descriptor.. n sets 
        for ii in range( dat_dims[0] ):
            res[ii] = _cxcdm._dmCoordInvert( dd, data[ii] )
    else:
        raise ValueError("dmCoordCalc() input data not same shape as dmDescriptor.")

    return res

def dmArrayCreateAxisGroup( desc, name, dtype, unit, cptnames):
    """
    dmColumnCreate( desc, name, dtype, unit, cptnames)                                                   
    Create an axis group on the next available set of axes. 
       desc - Required
          An image descriptor.
       name - Required
          The name of axis group.
       dtype - Required
          The data type of the parameters.
       unit - Optional
          The unit of the data.
       cptnames - Required
          The associated component names.
    """

    # check if cptnames is a string if so make it
    # a single element list

    # check for strings - try to features which are common to strings
    # this isn't the most secure way of doing this but it should weed out
    # the majority of false positives
    try:
        cptnames + 'a'
        cptnames.endswith
    except:
        pass
    else:
        cptnames = [cptnames]

    return _cxcdm.dmArrayCreateAxisGroup( desc, name, dtype, unit, cptnames)
