/* $Id$ 
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more 
 * contributor license agreements. See the NOTICE file distributed with  
 * this work for additional information regarding copyright ownership. 
 * The ASF licenses this file to you under the Apache License, Version  
 * 2.0 (the "License"); you may not use this file except in compliance  
 * with the License. You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 

/**
 * etch_natarray.c  
 * etch_nativearray implementation
 * does allocation and subscripting for n-dimensional arrays stored as a single
 * byte vector, to overcome the well-known C compiler issues with passing around
 * multi dimensioned arrays when the low order dimension is not known at compile
 * time. the mapping here from byte vector to array tests out as k&r compliant,
 * this is verified by constructing from a static array using new_nativearray_from
 * and testing that the get methods return the same result as statically indexed
 * array items. as it stands, this code handles a max of 3 dimensions. it can be 
 * generalized to n dimensions, however the caller would have to allocate, populate, 
 * and pass arrays of subscripts for this to happen.
 */

#include <stdio.h>
#include "etch_global.h"

etch_nativearray* init_nativearray 
    (unsigned short, const size_t, const int, const int, const int, const int);
int  etch_nativearray_put1 (etch_nativearray*, void*, int); 
int  etch_nativearray_put2 (etch_nativearray*, void*, int, int); 
int  etch_nativearray_put3 (etch_nativearray*, void*, int, int, int); 
int  etch_nativearray_get1 (etch_nativearray*, void*, int); 
int  etch_nativearray_get2 (etch_nativearray*, void*, int, int); 
int  etch_nativearray_get3 (etch_nativearray*, void*, int, int, int); 
int  etch_nativearray_set_content_type(etch_nativearray*);


/*
 * new_nativearray() 
 * etch_nativearray constructor
 * note that when creating arrays, the dimensions are passed low-order first,
 * so for x[2][3][4], dim0 is 4, dim1 is 3, dim2 is 2. for y[7][9], dim0 is 9,
 * dim1 is 7, and dim2 is zero of course.
 */
etch_nativearray* new_nativearray (unsigned short class_id, const size_t itemsize,  
    const int numdims, const int dim0, const int dim1, const int dim2)
{
    etch_nativearray* newarray = init_nativearray(class_id, itemsize, numdims, dim0, dim1, dim2);
    if (NULL == newarray) return NULL;

    newarray->values = etch_malloc(newarray->bytecount, ETCHTYPEB_BYTES);
    memset(newarray->values, 0, newarray->bytecount);
    newarray->is_content_owned = TRUE;
  
    etch_nativearray_set_content_type(newarray);
    return newarray;      
}


/*
 * new_nativearray_from() 
 * etch_nativearray constructor from existing byte vector
 * @param values source array, caller retains ownership.
 */
etch_nativearray* new_nativearray_from (void* values, unsigned short class_id, const size_t itemsize, 
    const int numdims, const int dim0, const int dim1, const int dim2)
{
    etch_nativearray* newarray = init_nativearray(class_id, itemsize, numdims, dim0, dim1, dim2);
    if (NULL == newarray) return NULL;

    newarray->values = values;
    newarray->is_content_owned = FALSE;

    etch_nativearray_set_content_type(newarray);
    return newarray;      
}


/*
 * new_nativearray_of() 
 * etch_nativearray constructor for array of objects of specified type and class
 */
etch_nativearray* new_nativearray_of 
   (unsigned short content_obj_type, unsigned short content_class_id,  
    const int numdims, const int dim0, const int dim1, const int dim2)
{
    etch_nativearray* newarray = init_nativearray
       (CLASSID_ARRAY_OBJECT, sizeof(void*), numdims, dim0, dim1, dim2);
    if (NULL == newarray) return NULL;

    newarray->content_obj_type = content_obj_type;
    newarray->content_class_id = content_class_id;
    return newarray;      
}



/*
 * init_nativearray() 
 * etch_nativearray private constructor
 * assumes max number of dimensions is 3
 */
etch_nativearray* init_nativearray (unsigned short class_id, const size_t itemsize,  
    const int numdims, const int dim0, const int dim1, const int dim2)
{
    etch_nativearray* newarray = NULL;
    int i,j;
    if (numdims < 1 || itemsize < 1) return NULL;
    if (numdims > 1 && dim1 < 1)     return NULL;
    if (numdims > 2 && dim2 < 1)     return NULL;

    newarray = newarray = (etch_nativearray*) new_object
        (sizeof(etch_nativearray), ETCHTYPEB_NATIVEARRAY, class_id);

    newarray->numdims  = numdims;
    newarray->itemsize = itemsize;

    /* dimension array contains the array dimensions, 
     * e.g., for x[2][3][4], [0] is 4, [1] is 3, [2] is 2 
     */
    newarray->dimension[0] = dim0;
    newarray->dimension[1] = numdims > 1? dim1: 0;
    newarray->dimension[2] = numdims > 2? dim2: 0;

    /* dimsize array contains size in bytes of the next lower dimension, 
     * stored so that the values need not be calculated on each array access. 
     * [0] is item size, so for int x[5][7][9], [0] is sizeof(int), 
     * [1] is 9*sizeof(int), [2] is 7*9*sizeof(int)
     */
    newarray->dimsize[0] = itemsize;
    newarray->bytecount  = itemsize * dim0;

    for(i = 1, j = 0; i < numdims; i++, j++)
    {
        newarray->dimsize[i] = newarray->dimension[j] * newarray->dimsize[j];
        newarray->bytecount *= newarray->dimension[i];
    }

    newarray->clone   = clone_null;
    newarray->destroy = destroy_nativearray;  

    newarray->put1 = etch_nativearray_put1;
    newarray->put2 = etch_nativearray_put2;
    newarray->put3 = etch_nativearray_put3;
    newarray->get1 = etch_nativearray_get1;
    newarray->get2 = etch_nativearray_get2;
    newarray->get3 = etch_nativearray_get3;

    return newarray;      
}


/**
 * destroy_nativearray_content()
 * destroy nativearray's content vector.
 * return -1 if content not owned by nativearray and therefore not destroyed;
 * otherwise return zero, regardless of whether content was null.
 */
int destroy_nativearray_content(etch_nativearray* a)
{
    int  result = 0;
    if  (!is_etchobj_static_content(a) && a->is_content_owned)
         etch_free(a->values);  /* OK if values null */  
    else result = -1;
    return result;
}


/**
 * destroy_nativearray()
 * etch_nativearray destructor
 */
int destroy_nativearray(etch_nativearray* a) 
{
    if (a->refcount > 0 && --a->refcount > 0) return -1;  
    if (a->is_content_owned && !is_etchobj_static_content(a)) 
        destroy_nativearray_content(a);
    destroy_objectex((objmask*)a);
    return 0;
}


/*
 * new_subarray() 
 * construct and return subaarray[i] of this array.
 * subarray points into parent content and so does not own its content
 */
etch_nativearray* new_subarray(etch_nativearray* a, const int i) 
{
    etch_nativearray* subarray = NULL;
    const int sub_numdims = a->numdims - 1;

    if (a->numdims > 1 && i < (int) a->dimension[sub_numdims]) 
    {   
        const size_t sub_bytelen = a->dimsize[sub_numdims];
        const size_t vector_offset = i * sub_bytelen;
        byte* subvector = (byte*) a->values + vector_offset;

        subarray = new_nativearray_from(subvector, 
            a->class_id, (int) a->itemsize, sub_numdims,  
            (int) a->dimension[0], (int) a->dimension[1], 0);

        subarray->bytecount = sub_bytelen;
        subarray->content_obj_type = a->content_obj_type;
        subarray->content_class_id = a->content_class_id;
        subarray->is_content_owned = a->is_content_owned;

        subarray->counts[0] = a->counts[0]; /* user-maintained counts */  
        subarray->counts[1] = a->counts[1];  
        subarray->counts[2] = 0;
    }

    return subarray;
}


/*
 * etch_nativearray_assign_to() 
 * assign array b to array a, if legal to do so.
 */
etch_nativearray* etch_nativearray_assign_to(etch_nativearray* a, etch_nativearray* b)
{ 
    if (!is_etch_nativearray(a) || !is_etch_nativearray(b)) return NULL;

    /* arrays must be either of same type, or the left side an array of 
     * object; and the dimensionalities of the two arrays must be the same.
     */
    if(((a->class_id != b->class_id) && (a->class_id != CLASSID_ARRAY_OBJECT))    
     || (a->numdims  != b->numdims))    
         return NULL;            
      
    destroy_nativearray_content(a);    /* destroy a content if owned */

    a->values    = b->values;          /* assign b content to a */
    a->itemsize  = b->itemsize;
    a->bytecount = b->bytecount;      
    memcpy(a->dimension,  b->dimension, sizeof(a->dimension));
    memcpy(a->dimsize,    b->dimsize,   sizeof(a->dimsize));
    memcpy(a->counts,     b->counts,    sizeof(a->counts)); 
    a->content_obj_type = b->content_obj_type;     
    a->content_class_id = b->content_class_id;  
    a->is_content_owned = FALSE;       /* b still owns array content */
    a->is_copy = TRUE; 
    return a;
}


/*
 * etch_nativearray_get_element() 
 * return element[i] of specified array, which, if array is multi-dimensioned,
 * will be another native array, otherwise an etch object. for example, if
 * the supplied array is byte[2][4], a native array of byte[4] is returned.
 * if the supplied array is byte[4], an etch_byte object is returned.
 * returned subarrays do not own content, their content references the content
 * of their parent.
 */
objmask* etch_nativearray_get_element(etch_nativearray* a, const int i) 
{
    int result = 0;
    objmask* wrapped_element = NULL;
    etch_nativearray* subarray = NULL;

    const int sub_numdims = a->numdims - 1;
    if (sub_numdims < 0) return NULL;

    if (sub_numdims > 0) 
        return (objmask*) new_subarray(a, i);

    result = etch_nativearray_get_wrapped_component(a, i, &wrapped_element);

    return wrapped_element;
}


/*
 * etch_nativearray_set_content_type() 
 * set content_obj_type and content_class_id based on array class_id.
 * returns zero if content type was set, otherwise -1
 */
int etch_nativearray_set_content_type(etch_nativearray* a)
{
    unsigned short obj_type = 0;
    int result = 0;

    switch(a->class_id) 
    {
        case CLASSID_ARRAY_BYTE:    obj_type = ETCHTYPEB_BYTE;   break;
        case CLASSID_ARRAY_BOOL:    obj_type = ETCHTYPEB_BOOL;   break;
        case CLASSID_ARRAY_INT8:    obj_type = ETCHTYPEB_INT8;   break;
        case CLASSID_ARRAY_INT16:   obj_type = ETCHTYPEB_INT16;  break;
        case CLASSID_ARRAY_INT32:   obj_type = ETCHTYPEB_INT32;  break;
        case CLASSID_ARRAY_INT64:   obj_type = ETCHTYPEB_INT64;  break;
        case CLASSID_ARRAY_FLOAT:   obj_type = ETCHTYPEB_IEEE32; break;
        case CLASSID_ARRAY_DOUBLE:  obj_type = ETCHTYPEB_IEEE64; break;
    }

    if (obj_type)
    {   a->content_obj_type = obj_type;
        a->content_class_id = CLASSID_UNWRAPPED;
    }
    else switch(a->class_id) 
    {
        case CLASSID_ARRAY_OBJECT: 
            a->content_obj_type = ETCHTYPEB_ETCHOBJECT; 
            a->content_class_id = CLASSID_OBJECT;
            break;

        case CLASSID_ARRAY_STRING:  
            a->content_obj_type = ETCHTYPEB_STRING; 
            a->content_class_id = CLASSID_STRING;
            break;

        default:
            result = -1; /* indicates content type not set */
    }
    
    return result;
}


/**
 * put1()
 * insert item to singly dimensioned array
 * array access can be generalized by passing an array of subscripts; however
 * since we are currently handling max 3 dimensions, the calling sequence is 
 * much simpler when we have separate accessors for each dimensionality.
 */
int etch_nativearray_put1(etch_nativearray* a, void* value, int i) 
{
    size_t offset = etch_nativearray_off1(a, i);
    if (value == NULL || offset < 0) return -1;
    memcpy((byte*)a->values + offset, value, a->itemsize); 
    return 0;
}


/**
 * put2()
 * insert item to 2-dimensional array
 */
int etch_nativearray_put2(etch_nativearray* a, void* value, int i, int j) 
{
    size_t offset = etch_nativearray_off2(a, i, j);
    if (value == NULL || offset < 0) return -1;
    memcpy((byte*)a->values + offset, value, a->itemsize);  
    return 0;
}


/**
 * put3()
 * insert item to 3-dimensional array
 */
int etch_nativearray_put3(etch_nativearray* a, void* value, int i, int j, int k) 
{
    size_t offset = etch_nativearray_off3(a, i, j, k);
    if (value == NULL || offset < 0) return -1;
    memcpy((byte*)a->values + offset, value, a->itemsize); 
    return 0;
}


/**
 * get1()
 * access and return item from singly dimensioned array
 */
int etch_nativearray_get1(etch_nativearray* a, void* value, int i) 
{
    size_t offset = etch_nativearray_off1(a, i);
    if (value == NULL || offset < 0) return -1;
    memcpy(value, (byte*)a->values + offset, a->itemsize); 
    return 0;
}


/**
 * get2()
 * access and return item from 2-dimensional array
 */
int etch_nativearray_get2(etch_nativearray* a, void* value, int i, int j) 
{
    size_t offset = etch_nativearray_off2(a, i, j);
    if (value == NULL || offset < 0) return -1;
    memcpy(value, (byte*)a->values + offset, a->itemsize); 
    return 0;
}


/**
 * get3()
 * access and return item from 3-dimensional array
 */
int etch_nativearray_get3(etch_nativearray* a, void* value, int i, int j, int k) 
{
    size_t offset = etch_nativearray_off3(a, i, j, k);
    if (value == NULL || offset < 0) return -1;
    memcpy(value, (byte*)a->values + offset, a->itemsize); 
    return 0;
}


/*
 * etch_nativearray_off1() 
 * calculate byte offset into byte vector for a 1-dimension array
 */
size_t etch_nativearray_off1(etch_nativearray* a, int i)
{
    size_t offset = 0;    
    if (i >= (int) a->dimension[0] || i < 0) return -1; 

    offset = i * a->dimsize[0];
    return offset;
}


/*
 * etch_nativearray_off2() 
 * calculate byte offset into byte vector for a 2-dimension array
 */
size_t etch_nativearray_off2(etch_nativearray* a, int i, int j)
{
    size_t offset = 0;    
    if ((i >= (int) a->dimension[1] || i < 0)  
     || (j >= (int) a->dimension[0] || j < 0)) return -1; 

    offset = (i * a->dimsize[1]) + (j * a->dimsize[0]);
    return offset;
}


/*
 * etch_nativearray_off3() 
 * calculate byte offset into byte vector for a 3-dimension array
 */
size_t etch_nativearray_off3(etch_nativearray* a, int i, int j, int k)
{
    size_t offset = 0;    
    if ((i >= (int) a->dimension[2] || i < 0)  
     || (j >= (int) a->dimension[1] || j < 0)   
     || (k >= (int) a->dimension[0] || k < 0)) return -1; 

    offset = (i * a->dimsize[2]) + (j * a->dimsize[1]) + (k * a->dimsize[0]);
    return offset;
}


/**
 * etch_nativearray_get_component_type()
 * java.lang.class.getcomponenttype returns the class of an array's members. 
 * we instead identify the etch object type and class id of the array's members, 
 * returning results in the passed parameter struct reference. 
 */
int etch_nativearray_get_component_type
   (etch_object* classobj, etch_component_type_params* p)
{
    int result = 0;
    if (!p) return -1;
    memset(p, 0, sizeof(etch_component_type_params));
    if (!is_etch_nativearray(classobj)) return -1;  

    p->origl_array    = (etch_nativearray*) classobj;
    p->origl_obj_type = p->origl_array->content_obj_type;
    p->origl_class_id = p->origl_array->content_class_id;
    p->dimensions     = p->origl_array->numdims;

    p->final_array    = p->origl_array;
    p->final_obj_type = p->origl_obj_type;
    p->final_class_id = p->origl_class_id;

    /* we could have here an array of (pointers to) array. recurse the array 
     * until array content is no longer array.
     */
    while(is_etch_nativearray(p->final_obj_type))
    {   
        /* the content of the current array is array,         
         * so peek at item[0] in this array of arrays */
        etch_nativearray* content_array = p->final_array->bytecount >= sizeof(void*)?
           (etch_nativearray*) ((etch_nativearray**) p->final_array->values)[0]: NULL;

        if (!is_etch_nativearray(content_array))
        {   result = -1;/* sanity check, [0] not array, however content */
            break;      /* type indicated array, logic error, exception */
        }

        p->final_array    = content_array; 
        p->final_obj_type = content_array->content_obj_type;
        p->final_class_id = content_array->content_class_id;
        if (is_etch_nativearray(p->final_obj_type))
            p->dimensions += p->final_array->numdims; 
    } 

    return result;
}


/** 
 * etch_nativearray_get_wrapped_component()
 * get wrapped component[i] from the single-dimensioned native array.
 * caller owns returned object.
 * todo: test that if array content was wrapped, the is_value_owned
 * setting is sufficient for tranparent memory management of returned object.
 */
int etch_nativearray_get_wrapped_component(etch_nativearray* a, const int i, objmask** out)
{
    const int numentries = (int) a->dimension[0];
    const int is_wrapped_content = a->content_class_id != 0;
    int result = 0;
    union
    { char  vbyte; short  vint16; int   vint32; int64 vint64; 
      float vfloat;double vdouble;void* vaddr;  int64 all;
    } v;

    if (a->numdims != 1 || i >= numentries) return -1;
    v.all = 0;

    if (is_wrapped_content)
    {   /* content of the native array is wrapped, e.g, etch_object*,   
         * so we return the item as is */   
         result = a->get1(a, &v.vaddr, i);
         *out = v.vaddr;        
    }
    else switch(a->content_obj_type)
    {   /* content of the native array is unwrapped, e.g, int[], so we wrap   
         * the array item with an etch object prior to returning it */                  
        case ETCHTYPEB_BYTE: 
             result = a->get1(a, &v.vbyte, i);
             *out = (objmask*) new_byte(v.vbyte);
             break;
  
        case ETCHTYPEB_BOOL: 
             result = a->get1(a, &v.vbyte, i);
             *out = (objmask*) new_boolean(v.vbyte);
             break;   

        case ETCHTYPEB_INT8: 
             result = a->get1(a, &v.vbyte, i);
             *out = (objmask*) new_int8(v.vbyte);
             break; 
     
        case ETCHTYPEB_INT16:  
             result = a->get1(a, &v.vint16, i);
             *out = (objmask*) new_int16(v.vint16);
             break; 

        case ETCHTYPEB_INT32: 
             result = a->get1(a, &v.vint32, i);
             *out = (objmask*) new_int32(v.vint32);
             break; 

        case ETCHTYPEB_INT64: 
             result = a->get1(a, &v.vint64, i);
             *out = (objmask*) new_int64(v.vint64);
             break;  

        case ETCHTYPEB_IEEE32:  
             result = a->get1(a, &v.vfloat, i);
             *out = (objmask*) new_float(v.vfloat);
             break;      

        case ETCHTYPEB_IEEE64:
             result = a->get1(a, &v.vdouble, i);
             *out = (objmask*) new_double(v.vdouble);
             break; 

        case ETCHTYPEB_STRING: 
             result = a->get1(a, &v.vaddr, i);
             *out = (objmask*) new_string(v.vaddr, ETCH_ENCODING_UTF16);
             break; 

        default: /* item is an anonymous pointer to a value owned by caller */
        {    etch_object* wrapper = NULL;               
             result  = a->get1(a, &v.vaddr, i);  
             wrapper = new_etch_object(a->content_class_id, v.vaddr); 
             /* we clear is_value_owned so value will not be freed by dtor */
             wrapper->is_value_object = wrapper->is_value_owned = FALSE;
             *out = (objmask*) wrapper;
        } 

    }  /* switch(a->content_obj_type) */ 

    return result;
}

