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


static BOOL dmSaveFloatValue(DMResource *res, DMFloat val)
{
    char tmp[DT_FLOAT_STORE_SIZE];
    snprintf(tmp, DT_FLOAT_STORE_SIZE, "%f", val);
    return !dmf_write_str(res, tmp, DT_FLOAT_STORE_SIZE);
}


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

    // Save number of points
    npoints = points->npoints;
    if (!dmf_write_le32(res, npoints))
        return DMERR_FWRITE;
    
    // Save points
    for (point = 0; point < points->npoints; point++)
    {
        DMTimelinePoint *pt = &(points->points[point]);
        Uint32 ptype, ptime;
        ptype = pt->type;
        ptime = pt->time;
        if (!dmf_write_le32(res, ptype) ||
            !dmf_write_le32(res, ptime))
            return DMERR_FWRITE;

        switch (type)
        {
            case EFPT_INT:
                if (!dmf_write_le32(res, pt->vint))
                    return DMERR_FWRITE;
                break;

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

            case EFPT_MATRIX:
                {
                    int x, y;
                    for (y = 0; y < DM_MATRIX_SIZE; y++)
                    for (x = 0; x < DM_MATRIX_SIZE; x++)
                    {
                        if (!dmSaveFloatValue(res, pt->matrix.m[y][x]))
                            return DMERR_FWRITE;
                    }
                }
                break;
        }
    }
    return DMERR_OK;
}


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

    if (!dmf_write_byte(res, curve->enabled))
        return DMERR_FWRITE;

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


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

    strncpy(hdr.name, param->name, sizeof(hdr.name));
    hdr.type = param->type;

    if (!dmf_write_str(res, hdr.name, sizeof(hdr.name)) ||
        !dmf_write_le32(res, hdr.type))
        return DMERR_FWRITE;

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

        case EFPT_STRING:
            len = strlen(param->vstr);
            if (!dmf_write_le16(res, len))
                return DMERR_FWRITE;

            if (!dmf_write_str(res, param->vstr, len))
                return DMERR_FWRITE;
            break;
    }

    return DMERR_OK;
}


static int dmSaveTimelineEvent(DMResource *res, DMTimelineEvent *event)
{
    int param, err;
    DMFTimelineEvent hdr;

    strncpy(hdr.effectName, event->effect->name, sizeof(hdr.effectName));
    hdr.start    = event->start;
    hdr.duration = event->duration;
    hdr.nparams  = event->nparams;

    if (!dmf_write_le32(res, hdr.start) ||
        !dmf_write_le32(res, hdr.duration) ||
        !dmf_write_str(res, hdr.effectName, sizeof(hdr.effectName)) ||
        !dmf_write_le32(res, hdr.nparams))
        return DMERR_FWRITE;

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

    return DMERR_OK;
}


static int dmSaveTimelineTrack(DMResource *res, DMTimelineTrack *track)
{
    int event, err;
    DMFTimelineTrack hdr;
    
    strncpy(hdr.name, track->name, sizeof(hdr.name));

    if (!dmf_write_le32(res, track->index) ||
        !dmf_write_le32(res, track->layer) ||
        !dmf_write_str(res, hdr.name, sizeof(hdr.name)) ||
        !dmf_write_byte(res, track->enabled) ||
        !dmf_write_le32(res, track->nevents))
        return DMERR_FWRITE;

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

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

    return DMERR_OK;
}


int dmSaveTimeline(DMResource *res, DMTimeline *tl)
{
    int track, err;
    DMFTimeline hdr;

    memcpy(&hdr.magic, DT_MAGIC_ID, sizeof(hdr.magic));
    strncpy(hdr.name, tl->name, sizeof(hdr.name));
    hdr.ntracks = tl->ntracks;
    hdr.duration = tl->duration;
    
    if (!dmf_write_str(res, hdr.magic, sizeof(hdr.magic)) ||
        !dmf_write_str(res, hdr.name, sizeof(hdr.name)) ||
        !dmf_write_le32(res, hdr.ntracks) ||
        !dmf_write_le32(res, hdr.duration))
        return DMERR_FWRITE;

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

    return DMERR_OK;
}


int dmTimelineNew(DMTimeline **ptl, const char *name)
{
    DMTimeline *tl;
    if ((*ptl = tl = dmMalloc0(sizeof(DMTimeline))) == NULL)
        return DMERR_MALLOC;

    tl->name = dm_strdup(name);
    
    return DMERR_OK;
}


int dmTimelineAddTrack(DMTimeline *tl, DMTimelineTrack **ptrack, const char *name)
{
    DMTimelineTrack *track;
    
    if ((*ptrack = track = dmMalloc0(sizeof(DMTimelineTrack))) == NULL)
        return DMERR_MALLOC;
    
    track->name = dm_strdup(name);
    track->enabled = TRUE;

    if (tl->ntracks + 1 >= tl->nallocated)
    {
        tl->nallocated += 16;
        tl->tracks = dmRealloc(tl->tracks, sizeof(DMTimelineTrack *) * tl->nallocated);
        if (tl->tracks == NULL)
            return DMERR_MALLOC;
    }
    
    tl->tracks[tl->ntracks] = track;
    tl->ntracks++;
    
    return DMERR_OK;
}


int dmTimelineTrackAddEvent(DMTimelineTrack *track, int start, int duration, DMTimelineEvent **pev)
{
    DMTimelineEvent *ev;
    
    if ((*pev = ev = dmMalloc0(sizeof(DMTimelineEvent))) == NULL)
        return DMERR_MALLOC;
    
    ev->start    = start;
    ev->duration = duration;
    
    if (track->nevents + 1 >= track->nallocated)
    {
        track->nallocated += 16;
        track->events = dmRealloc(track->events, sizeof(DMTimelineEvent *) * track->nallocated);
        if (track->events == NULL)
            return DMERR_MALLOC;
    }
    
    track->events[track->nevents] = ev;
    track->nevents++;
    
    return DMERR_OK;
}


int dmTimelineEventSetEffect(DMTimelineEvent *event, DMEffect *ef)
{
    int param;
    if (ef == NULL)
        return DMERR_INVALID_DATA;

    event->effect = ef;
    event->nparams = ef->nparams;
    event->params = dmCalloc(ef->nparams, sizeof(DMTimelineEventParam));
    if (event->params == NULL)
        return DMERR_MALLOC;
    
    for (param = 0; param < ef->nparams; param++)
    {
        DMTimelineEventParam *psrc = &ef->params[param],
                             *pdst = &event->params[param];

        pdst->name   = dm_strdup(psrc->name);
        pdst->type   = psrc->type;
        pdst->vstr   = dm_strdup(psrc->vstr);

        if (psrc->pts.points != NULL && psrc->pts.npoints > 0)
        {
            pdst->pts.npoints    = psrc->pts.npoints;
            pdst->pts.nallocated = psrc->pts.nallocated;
            pdst->pts.points     = dmMalloc(psrc->pts.nallocated * sizeof(DMTimelinePoint));
            if (pdst->pts.points == NULL)
                return DMERR_MALLOC;

            memcpy(pdst->pts.points, psrc->pts.points, pdst->pts.npoints * sizeof(DMTimelinePoint));
        }
    }
    
    return DMERR_OK;
}


int dmTimelineEventSetEffectByIndex(DMTimelineEvent *event, const int index)
{
    if (index < 0 || index >= nengineEffects)
        return DMERR_INVALID_DATA;

    return dmTimelineEventSetEffect(event, &engineEffects[index]);
}


int dmTimelineEventSetEffectByName(DMTimelineEvent *event, const char *name)
{
    return dmTimelineEventSetEffect(event, engineFindEffectByName(name));
}


int dmCopyTimelinePoints(const DMTimelinePoints *src, DMTimelinePoints *dst)
{
    if (src->points == NULL || src->npoints <= 0 || src->nallocated <= 0)
        return DMERR_OK;

    dst->npoints    = src->npoints;
    dst->nallocated = src->nallocated;
    dst->points     = dmCalloc(src->nallocated, sizeof(DMTimelinePoint));
    if (dst->points == NULL)
        return DMERR_MALLOC;

    memcpy(dst->points, src->points, sizeof(DMTimelinePoint) * src->npoints);
    
    return DMERR_OK;
}


int dmCopyTimelineEventParam(const DMTimelineEventParam *src, DMTimelineEventParam *dst)
{
    dst->name = dm_strdup(src->name);
    dst->type = src->type;
    dst->vstr = dm_strdup(src->vstr);

    return dmCopyTimelinePoints(&src->pts, &dst->pts);
}


int dmCopyTimelineEvent(const DMTimelineEvent *src, DMTimelineEvent **pdst)
{
    int param;
    DMTimelineEvent *dst;
    if ((*pdst = dst = dmMalloc0(sizeof(DMTimelineEvent))) == NULL)
        return DMERR_MALLOC;
    
    dst->start     = src->start;
    dst->duration  = src->duration;
    dst->nparams   = src->nparams;
    dst->effect    = src->effect;
    dst->params    = dmCalloc(src->nparams, sizeof(DMTimelineEventParam));
    if (dst->params == NULL)
        return DMERR_MALLOC;

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

    return DMERR_OK;
}


int dmCopyTimelineCurve(const DMTimelineCurve *src, DMTimelineCurve *dst)
{
    dst->enabled = src->enabled;
    return dmCopyTimelinePoints(&(src->points), &(dst->points));
}


int dmCopyTimelineTrack(const DMTimelineTrack *src, DMTimelineTrack **pdst)
{
    int event, err;
    DMTimelineTrack *dst;
    if ((*pdst = dst = dmMalloc0(sizeof(DMTimelineTrack))) == NULL)
        return DMERR_MALLOC;

    if ((dst->events = dmCalloc(src->nevents, sizeof(DMTimelineEvent *))) == NULL)
        return DMERR_MALLOC;
    
    dst->name    = dm_strdup(src->name);
    dst->enabled = src->enabled;
    dst->nevents = src->nevents;

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

    if ((err = dmCopyTimelineCurve(&(src->composite), &(dst->composite))) != DMERR_OK)
        return err;

    return DMERR_OK;
}


int dmCopyTimeline(const DMTimeline *src, DMTimeline **pdst)
{
    int track, err;
    DMTimeline *dst;
    if ((*pdst = dst = dmMalloc0(sizeof(DMTimeline))) == NULL)
        return DMERR_MALLOC;
    
    dst->tracks = (DMTimelineTrack **) dmCalloc(src->ntracks, sizeof(DMTimelineTrack *));
    if (dst->tracks == NULL)
        return DMERR_MALLOC;

    dst->name     = dm_strdup(src->name);
    dst->duration = src->duration;
    dst->ntracks  = src->ntracks;

    for (track = 0; track < src->ntracks; track++)
    {
        if ((err = dmCopyTimelineTrack(src->tracks[track], &(dst->tracks[track]))) != DMERR_OK)
            return err;
    }
    
    return DMERR_OK;
}
