/*
 * dmlib
 * -- Timeline file loading and timeline processing/execution
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2012-2013 Tecnic Software productions (TNSP)
 */
#include "dmengine.h"


static BOOL dmLoadFloatValue(DMResource *res, DMFloat *val)
{
    char tmp[DT_FLOAT_STORE_SIZE + 1];
    if (!dmf_read_str(res, &tmp, DT_FLOAT_STORE_SIZE))
        return FALSE;

    tmp[DT_FLOAT_STORE_SIZE] = 0;
    *val = atof(tmp);
    return TRUE;
}


static int dmLoadTimelinePoints(DMResource *res, DMTimelinePoints *points, int type)
{
    int point;
    Uint32 npoints;

    // Get number of points
    if (!dmf_read_le32(res, &npoints))
        return DMERR_FREAD;

    // Calculate and allocate space
    points->npoints = npoints;
    points->nallocated = ((npoints / 16) + 1) * 16;
    
    points->points = dmCalloc(points->nallocated, sizeof(DMTimelinePoint));
    if (points->points == NULL)
        return DMERR_MALLOC;
    
    // Read points
    for (point = 0; point < points->npoints; point++)
    {
        DMTimelinePoint *pt = &(points->points[point]);
        Uint32 ptype, ptime;
        if (!dmf_read_le32(res, &ptype) ||
            !dmf_read_le32(res, &ptime))
            return DMERR_FREAD;
        
        switch (type)
        {
            case EFPT_INT:
                {
                    Uint32 tmp;
                    if (!dmf_read_le32(res, &tmp))
                        return DMERR_FREAD;
                    pt->vint = tmp;
                }
                break;

            case EFPT_FLOAT:
                if (!dmLoadFloatValue(res, &pt->vfloat))
                    return DMERR_FREAD;
                break;
            
            case EFPT_VECTOR:
                if (!dmLoadFloatValue(res, &pt->vector.x) ||
                    !dmLoadFloatValue(res, &pt->vector.y) ||
                    !dmLoadFloatValue(res, &pt->vector.z) ||
                    !dmLoadFloatValue(res, &pt->vector.W))
                    return DMERR_FREAD;
                break;

            case EFPT_MATRIX:
                {
                    int x, y;
                    for (y = 0; y < DM_MATRIX_SIZE; y++)
                    for (x = 0; x < DM_MATRIX_SIZE; x++)
                    {
                        if (!dmLoadFloatValue(res, &(pt->matrix.m[y][x])))
                            return DMERR_FREAD;
                    }
                }
                break;
        }
        
        pt->type = ptype;
        pt->time = ptime;
    }
    return DMERR_OK;
}


static int dmLoadTimelineEventParam(DMResource *res, DMTimelineEventParam *param)
{
    int err;
    DMFTimelineEventParam hdr;
    Uint16 len;

    if (!dmf_read_str(res, &hdr.name, sizeof(hdr.name)) ||
        !dmf_read_le32(res, &hdr.type))
        return DMERR_FREAD;

    hdr.name[sizeof(hdr.name) - 1] = 0;

    param->name = dm_strdup(hdr.name);
    param->type = hdr.type;

    switch (param->type)
    {
        case EFPT_INT:
        case EFPT_FLOAT:
        case EFPT_VECTOR:
        case EFPT_MATRIX:
            if ((err = dmLoadTimelinePoints(res, &param->pts, param->type)) != DMERR_OK)
                return err;
            break;

        case EFPT_STRING:
            if (!dmf_read_le16(res, &len))
                return DMERR_FREAD;

            param->vstr = dmMalloc((unsigned int)len + 1);
            if (!dmf_read_str(res, param->vstr, len))
                return DMERR_FREAD;

            param->vstr[len] = 0;
            break;
    }

    return DMERR_OK;
}


static int dmLoadTimelineEvent(DMResource *res, DMTimelineEvent **pevent)
{
    int param;
    DMFTimelineEvent hdr;
    DMTimelineEvent *event;
    if ((*pevent = event = dmMalloc0(sizeof(DMTimelineEvent))) == NULL)
        return DMERR_MALLOC;

    // Get basic event data
    if (!dmf_read_le32(res, &hdr.start) ||
        !dmf_read_le32(res, &hdr.duration) ||
        !dmf_read_str(res, &hdr.effectName, sizeof(hdr.effectName)) ||
        !dmf_read_le32(res, &hdr.nparams))
        return DMERR_FREAD;

    hdr.effectName[sizeof(hdr.effectName) - 1] = 0;
    
    if (hdr.nparams > DT_MAX_EFFECT_PARAMS)
    {
        dmError("Invalid number of parameters, %d > %d ('%s' @ %d:%d)\n",
            hdr.nparams, DT_MAX_EFFECT_PARAMS, hdr.effectName, hdr.start, hdr.duration);
        return DMERR_INVALID_DATA;
    }

    event->start     = hdr.start;
    event->duration  = hdr.duration;
    event->nparams   = hdr.nparams;
    event->effect    = engineFindEffect(hdr.effectName, hdr.nparams);
    if (event->effect == NULL)
    {
        dmError("No matching registered effect found for '%s'.\n", hdr.effectName);
        return DMERR_INVALID_DATA;
    }

    event->params    = dmCalloc(event->nparams, sizeof(DMTimelineEventParam));
    if (event->params == NULL)
    {
        dmError("Could not allocate memory for timeline event parameters.\n");
        return DMERR_MALLOC;
    }

    for (param = 0; param < event->nparams; param++)
    {
        int err;
        if ((err = dmLoadTimelineEventParam(res, &(event->params[param]))) != DMERR_OK)
            return err;
    }

    return DMERR_OK;
}


static int dmLoadTimelineCurve(DMResource *res, DMTimelineCurve *curve)
{
    int err;

    curve->enabled = dmfgetc(res);
    if ((err = dmLoadTimelinePoints(res, &(curve->points), EFPT_FLOAT)) != DMERR_OK)
        return err;
    
    return DMERR_OK;
}


static int dmLoadTimelineTrack(DMResource *res, DMTimelineTrack **ptrack)
{
    int event, err;
    DMFTimelineTrack hdr;
    DMTimelineTrack *track;
    if ((*ptrack = track = dmMalloc0(sizeof(DMTimelineTrack))) == NULL)
        return DMERR_MALLOC;

    if (!dmf_read_le32(res, &hdr.index) ||
        !dmf_read_le32(res, &hdr.layer) ||
        !dmf_read_str(res, &hdr.name, sizeof(hdr.name)) ||
        !dmf_read_byte(res, &hdr.enabled) ||
        !dmf_read_le32(res, &hdr.nevents))
        return DMERR_FREAD;

    if (hdr.nevents >= 4096)
        return DMERR_INVALID_DATA;

    if ((track->events = dmCalloc(hdr.nevents, sizeof(DMTimelineEvent *))) == NULL)
        return DMERR_MALLOC;

    hdr.name[sizeof(hdr.name) - 1] = 0;
    track->name    = dm_strdup(hdr.name);
    track->enabled = hdr.enabled;
    track->nevents = hdr.nevents;

    for (event = 0; event < track->nevents; event++)
    {
        if ((err = dmLoadTimelineEvent(res, &(track->events[event]))) != DMERR_OK)
            return err;
    }

    if ((err = dmLoadTimelineCurve(res, &(track->composite))) != DMERR_OK)
        return err;

    return DMERR_OK;
}


int dmLoadTimeline(DMResource *res, DMTimeline **ptl)
{
    int track, err;
    DMFTimeline hdr;
    DMTimeline *tl;
    if ((*ptl = tl = dmMalloc0(sizeof(DMTimeline))) == NULL)
        return DMERR_MALLOC;

    // Read and check header
    if (!dmf_read_str(res, &hdr.magic, sizeof(hdr.magic)))
        return DMERR_FREAD;

    if (memcmp(hdr.magic, DT_MAGIC_ID, sizeof(hdr.magic)) != 0)
        return DMERR_INVALID_DATA;

    if (!dmf_read_str(res, &hdr.name, sizeof(hdr.name)) ||
        !dmf_read_le32(res, &hdr.ntracks) ||
        !dmf_read_le32(res, &hdr.duration))
        return DMERR_FREAD;

    if (hdr.ntracks >= 64)
        return DMERR_INVALID_DATA;

    // Allocate track pointers
    tl->tracks = (DMTimelineTrack **) dmCalloc(hdr.ntracks, sizeof(DMTimelineTrack *));
    if (tl->tracks == NULL)
        return DMERR_MALLOC;

    // Copy rest
    hdr.name[sizeof(hdr.name) - 1] = 0;
    tl->name     = dm_strdup(hdr.name);
    tl->duration = hdr.duration;
    tl->ntracks  = hdr.ntracks;

    // Read tracks
    for (track = 0; track < tl->ntracks; track++)
    {
        if ((err = dmLoadTimelineTrack(res, &(tl->tracks[track]))) != DMERR_OK)
            return err;
    }
    
    return DMERR_OK;
}


void dmFreeTimelinePoints(DMTimelinePoints *points)
{
    dmFree(points->points);
    points->points = NULL;
    points->npoints = points->nallocated = 0;
}


void dmFreeTimelineEventParam(DMTimelineEventParam *param)
{
    dmFree(param->name);
    dmFree(param->vstr);
    dmFreeTimelinePoints(&(param->pts));
}


void dmFreeTimelineEvent(DMTimelineEvent *event)
{
    if (event != NULL)
    {
        int param;
        for (param = 0; param < event->nparams; param++)
            dmFreeTimelineEventParam(&(event->params[param]));
        dmFree(event->params);
    }
}


void dmFreeTimelineTrack(DMTimelineTrack *track)
{
    if (track != NULL)
    {
        int event;

        dmFree(track->name);

        for (event = 0; event < track->nevents; event++)
            dmFreeTimelineEvent(track->events[event]);
        dmFree(track->events);

        dmFreeTimelinePoints(&(track->composite.points));
    }
}


void dmFreeTimeline(DMTimeline *tl)
{
    if (tl != NULL)
    {
        int i;
        for (i = 0; i < tl->ntracks; i++)
            dmFreeTimelineTrack(tl->tracks[i]);

        dmFree(tl->tracks);
        dmFree(tl->name);
    }
}


static void dmFreePreparedEventGroup(DMPreparedEventGroup *group)
{
    if (group != NULL)
    {
        dmFree(group->events);
        dmFree(group);
    }
}


void dmFreePreparedTimelineData(DMPreparedTimeline *ptl)
{
    if (ptl != NULL)
    {
        int group;
        for (group = 0; group < ptl->ngroups; group++)
        {
            dmFreePreparedEventGroup(ptl->groups[group]);
            ptl->groups[group] = NULL;
        }

        dmFree(ptl->groups);
        ptl->groups = NULL;
    }
}


/* Prepare a loaded timeline for execution. Creates the "stacked" structure
 * of timeline data for efficient rendering.
 */
int dmAddSplitPreparedEventGroup(DMPreparedEventGroup **groups, DMTimelineTrack *track, DMTimelineEvent *event)
{
    DMPreparedEventGroup *node;
    
    for (node = *groups; node != NULL; node = node->next)
    {
    }
    
    return DMERR_OK;
}


int dmPrepareTimeline(DMTimeline *tl, DMPreparedTimeline *ptl)
{
    int group, ntrack, event, err;
    DMPreparedEventGroup *groups = NULL, *node;

    // Free previous data
    dmFreePreparedTimelineData(ptl);

    // Process tracks
    for (ntrack = 0; ntrack < tl->ntracks; ntrack++)
    {
        DMTimelineTrack *track = tl->tracks[ntrack];
        for (event = 0; event < track->nevents; event++)
        {
            if ((err = dmAddSplitPreparedEventGroup(&groups, track, track->events[event])) != DMERR_OK)
                return err;
        }
    }
    
    // Compute number of groups
    ptl->ngroups = 0;
    for (node = groups; node != NULL; node = node->next)
        ptl->ngroups++;

    // Allocate linear array for fast access
    ptl->groups = dmMalloc(sizeof(DMPreparedEventGroup) * ptl->ngroups);
    if (ptl->groups == NULL)
        return DMERR_MALLOC;

    // Store pointers in the array
    for (group = 0, node = groups; node != NULL; node = node->next)
        ptl->groups[group++] = node;
    
    return DMERR_OK;
}


/* Seeks to specified position in the timeline. The execution function
 * only handles monotonously increasing time, going backwards will not work
 * there correctly, thus to seek freely this function must be used.
 */
int dmSeekTimeline(DMPreparedTimeline *tl, int time)
{
    return DMERR_OK;
}


/* "Executes", or rather renders a frame on the specified timeline position.
 */
int dmExecuteTimeline(DMPreparedTimeline *tl, SDL_Surface *screen, int time)
{
    return DMERR_OK;
}
