#include <../source/TAL/objbase.h>
#include <../source/geom/stroke.h>

/*
 * Last update: 09-aug-2005
 */

typedef struct mesh_glyph_s
{
    u32_t           num_strokes;
    mesh_stroke_p   *strokes;
    void_p          attrib;
}   mesh_glyph_t, *mesh_glyph_p;

/*
 *  Allocates array to store polyline with attribute
 */
V_LOCAL mesh_stroke_p
meshAllocStrokeFromStack(bstack_p coords,u32_t per_stroke_attrib_size)
{
    u32_t num_coords;
    vsize_t cksize;
    mesh_stroke_p stroke;
    num_coords = stackCount(coords);
    cksize = sizeof(mesh_stroke_t) + 
        num_coords * sizeof(vec3_t) + per_stroke_attrib_size;
    stroke = (mesh_stroke_p)memAlloc(cksize);
    if(!stroke)
        return NULL;
    stroke->num_coords = num_coords;
    if(num_coords)
    {
        stroke->coords = (vec3_t*)(stroke + 1);
        memCopy(stroke->coords, stackGetStorage(coords),
            num_coords * sizeof(vec3_t));
    }
    else
        stroke->coords = NULL;
    if(per_stroke_attrib_size)
    {
        stroke->attrib = (vec3_t*)(stroke + 1) + num_coords;
        memZero(stroke->attrib,per_stroke_attrib_size);
    }
    else
        stroke->attrib = NULL;
    return stroke;
}

/*
 *  Allocates glyph to store polylines
 */
V_LOCAL mesh_glyph_p
meshAllocGlyphFromStack(vp_stack_p strokes,u32_t per_glyph_attrib_size)
{
    u32_t num_strokes = vpsCount(strokes);
    vsize_t cksize = sizeof(mesh_glyph_t) + 
        num_strokes * sizeof(mesh_stroke_p) + per_glyph_attrib_size;
    mesh_glyph_p glyph = (mesh_glyph_p)memAlloc(cksize);
    glyph->num_strokes = num_strokes;
    if(num_strokes)
    {
        glyph->strokes = (mesh_stroke_p*)(glyph + 1);
        /* Copy pointers to strokes into glyph */
        memCopy(glyph->strokes, vpsGetStorage(strokes),
            num_strokes * sizeof(mesh_stroke_p));
    }
    else
        glyph->strokes = NULL;
    if(per_glyph_attrib_size)
    {
        glyph->attrib = (mesh_stroke_p*)(glyph + 1) + num_strokes;
        memZero(glyph->attrib, per_glyph_attrib_size);
    }
    else
        glyph->attrib = NULL;
    return glyph;
}

/*
 * Series of polylines
 */
typedef struct strokes_array_s
{
    libv_object_t base;
    /* Size of per-stroke extended information */
    u32_t       per_stroke_attrib_size;
    /* Size of per-glyph extended information */
    u32_t       per_glyph_attrib_size;
    /* Size of per-object extended information */
    u32_t       attrib_size;
    /* Complete glyphs */
    vp_stack_t  glyphs;
    /* Object attribute */
    void_p      attrib;
    /* Additional fields to use for glyph creation */
    bstack_t    coords;     /* Coordinates for current linestrip */
    vp_stack_t  strips;     /* Linestrips for current glyph */
}   strokes_array_t, *strokes_array_p;

#ifdef  CHECKED_BUILD
#define SARRAY_MAGIC    0x53415252
V_LOCAL char            *sarray_object="strokes array";
#define GET_SA(handle) \
    ((strokes_array_p)libvIsHandle(ms,caller_name,sarray_object,SARRAY_MAGIC))
#else   /* !CHECKED_BUILD */
#define GET_SA(handle)  ((strokes_array_p)handle)
#endif  /* !CHECKED_BUILD */

V_LOCAL u32_t V_CALL
saDestroy(vhandle_t handle)
{
    u32_t i;
    void *s;
    mesh_glyph_p g;
    strokes_array_t *sa = (strokes_array_t*)handle;
    UNREF_EXIT(sa);
    stackClear(&sa->coords);
    /* Delete linestrips of uncomplete glyph, if any */
    while(vpsPop(&sa->strips, &s))
        memFree(s);
    vpsClear(&sa->strips);
    /* Delete all glyphs */
    while(vpsPop(&sa->glyphs, (void_p*)&g))
    {
        for(i=0;i < g->num_strokes;i++)
            memFree(g->strokes[i]);
        memFree(g);
    }
    vpsClear(&sa->glyphs);
    memFreeObject(handle);
    return 0;
}

vhandle_t   V_GLOBAL
meshCreateStrokesEx(u32_t attrib_size,u32_t per_glyph_attrib_size,
    u32_t per_stroke_attrib_size)
{
    strokes_array_t *sa;
    ALLOC_OBJECT(sa,strokes_array_t,attrib_size,SARRAY_MAGIC);
    sa->base.destroy = saDestroy;
    sa->per_stroke_attrib_size = per_stroke_attrib_size;
    sa->per_glyph_attrib_size = per_glyph_attrib_size;
    sa->attrib_size = attrib_size;
    if(attrib_size)
    {
        sa->attrib = sa + 1;
        memZero(sa->attrib, attrib_size);
    }
    else
        sa->attrib = NULL;
    stackInit(&sa->coords, sizeof(vec3_t));
    vpsInit(&sa->strips);
    vpsInit(&sa->glyphs);
    return (vhandle_t)sa;
}

vhandle_t   V_GLOBAL
meshCreateStrokes(void)
{
    return meshCreateStrokesEx(0,0,0);
}

void    V_GLOBAL
meshDeleteStrokes(vhandle_t ms)
{
    libvCloseHandle(ms,"meshDeleteStrokes",sarray_object,SARRAY_MAGIC);
}

void    V_GLOBAL
meshStrokeCoord(vhandle_t ms,vec_t x,vec_t y,vec_t z)
{
    vec3_t v;
    vec3Set(v,x,y,z);
    meshStrokeCoordv(ms,v);
}

void    V_GLOBAL
meshStrokeCoordv(vhandle_t ms,vec3_t xyz)
{
    CALLER_NAME(meshStrokeCoordv)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return;
    stackPush(&sa->coords, xyz);
}

void    V_GLOBAL
meshCancelStroke(vhandle_t ms)
{
    CALLER_NAME(meshCancelStroke)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return;
    /* Clear coords */
    stackResize(&sa->coords, 0);
}

void    V_GLOBAL
meshCancelGlyph(vhandle_t ms)
{
    CALLER_NAME(meshCancelGlyph)
    void *s;
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return;
    /* Delete all strokes for new glyph */
    while(vpsPop(&sa->strips, &s))
        memFree(s);
    /* Cancel last stroke */
    stackResize(&sa->coords, 0);
}

/*
 * Stores recorded stroke in 'obj->strips'
 */
s32_t V_GLOBAL
meshEndStrokeEx(vhandle_t ms, void_p attrib_src)
{
    CALLER_NAME(meshEndStrokeEx)
    mesh_stroke_t *stroke;
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return -1;
	if(stackCount(&sa->coords) < 2)
        stackResize(&sa->coords, 0);
    stroke = meshAllocStrokeFromStack(&sa->coords,sa->per_stroke_attrib_size);
    if(!stroke)
    {
        ERROR_GENERIC(error_out_of_memory);
        stackResize(&sa->coords, 0);
        return -1;
    }
    vpsPush(&sa->strips, stroke);
    stackResize(&sa->coords, 0);
    if(attrib_src && sa->per_stroke_attrib_size)
        memCopy(stroke->attrib, attrib_src, sa->per_stroke_attrib_size);
    return ((s32_t)vpsCount(&sa->strips)) - 1;
}

s32_t V_GLOBAL
meshEndStroke(vhandle_t ms)
{
    return meshEndStrokeEx(ms, NULL);
}

/*
 * Stores recorded array of strokes in array of glyphs
 */
s32_t   V_GLOBAL
meshEndGlyphEx(vhandle_t ms,void_p attrib_src)
{
    CALLER_NAME(meshEndGlyph)
    mesh_glyph_t *glyph;
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return -1;
    /* empty glyphs _allowed_ */
    glyph = meshAllocGlyphFromStack(&sa->strips,sa->per_glyph_attrib_size);
    if(!glyph)
    {
        ERROR_GENERIC(error_out_of_memory);
        return -1;
    }
    vpsPush(&sa->glyphs, glyph);
    stackResize(&sa->coords, 0);
    vpsResize(&sa->strips, 0);
    if(attrib_src && sa->per_glyph_attrib_size)
        memCopy(glyph->attrib, attrib_src, sa->per_glyph_attrib_size);
    return ((s32_t)vpsCount(&sa->glyphs)) - 1;
}

s32_t   V_GLOBAL
meshEndGlyph(vhandle_t ms)
{
    return meshEndGlyphEx(ms, NULL);
}

/*
 * Frees unused memory and stores the attrib
 */
void    V_GLOBAL
meshFinishStrokesEx(vhandle_t ms,void_p attrib_src)
{
    CALLER_NAME(meshFinishStrokes)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return;
    stackClear(&sa->coords);
    vpsClear(&sa->strips);
    vpsTrim(&sa->glyphs);
    if(attrib_src && sa->attrib_size)
        memCopy(sa->attrib, attrib_src, sa->attrib_size);
    return;
}

void    V_GLOBAL
meshFinishStrokes(vhandle_t ms)
{
    meshFinishStrokesEx(ms, NULL);
}

/*
 * Returns number of complete glyphs
 */
u32_t   V_GLOBAL
meshGlyphCount(vhandle_t ms)
{
    CALLER_NAME(meshGlyphCount)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return 0;
    return vpsCount(&sa->glyphs);
}

/*
 * Returns number of strokes in given glyph
 */
u32_t   V_GLOBAL
meshStrokesCount(vhandle_t ms,u32_t glyph_num)
{
    CALLER_NAME(meshGlyphCount)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return 0;
    if(glyph_num >= vpsCount(&sa->glyphs))
    {
        ERROR_GENERIC("no such glyph");
        return 0;
    }
    return ((mesh_glyph_p)vpsGet(&sa->glyphs,glyph_num))->num_strokes;
}

/*
 * Returns specified stroke of glyph
 */
mesh_stroke_p   V_GLOBAL
meshGlyphStroke(vhandle_t ms,u32_t glyph_num,u32_t stroke_num)
{
    CALLER_NAME(meshGlyphStroke)
    strokes_array_t *sa = GET_SA(ms);
    mesh_glyph_p glyph;
    if(sa == LIBV_INVALID_HANDLE)
        return NULL;
    if(glyph_num >= vpsCount(&sa->glyphs))
    {
        ERROR_GENERIC("no such glyph");
        return NULL;
    }
    glyph = (mesh_glyph_p)vpsGet(&sa->glyphs,glyph_num);
    if(stroke_num >= glyph->num_strokes)
    {
        ERROR_GENERIC("no such stroke");
        return NULL;
    }
    return glyph->strokes[stroke_num];
}

/*
 * Returns attribute sizes
 */
vboolean V_GLOBAL
meshStrokeAttribSizes(vhandle_t ms,
    u32_t *per_stroke_attrib_size_ret,u32_t *per_glyph_attrib_size_ret,
    u32_t *attrib_size_ret)
{
    CALLER_NAME(meshStrokeAttribSizes)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return false;
    if(per_stroke_attrib_size_ret)
        *per_stroke_attrib_size_ret = sa->per_stroke_attrib_size;
    if(per_glyph_attrib_size_ret)
        *per_glyph_attrib_size_ret = sa->per_glyph_attrib_size;
    if(attrib_size_ret)
        *attrib_size_ret = sa->attrib_size;
    return true;
}

/*
 * Returns pointer to attribute buffer for glyph # 'glyph_num'
 */
void_p  V_GLOBAL
meshGlyphAttrib(vhandle_t ms,u32_t glyph_num)
{
    CALLER_NAME(meshGlyphAttrib)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return NULL;
    if(glyph_num >= vpsCount(&sa->glyphs))
    {
        ERROR_GENERIC("no such glyph");
        return NULL;
    }
    return ((mesh_glyph_p)vpsGet(&sa->glyphs,glyph_num))->attrib;
}

/*
 * Returns pointer to attribute buffer of object
 */
void_p  V_GLOBAL
meshStrokesAttrib(vhandle_t ms)
{
    CALLER_NAME(meshStrokesAttrib)
    strokes_array_t *sa = GET_SA(ms);
    if(sa == LIBV_INVALID_HANDLE)
        return NULL;
    return sa->attrib;
}
