#include "dmlib.h"
#include "dmq3d.h"
#include "dmimage.h"


#define DM_DRAWLINE_NAME dmDrawLineSpec
#define DM_DRAWLINE_DST_BYTES 4
#define DM_DRAWLINE_DST_TYPE DMRGBA32
#define DM_DRAWLINE_ARGS , SDL_Surface *bmp
#define DM_DRAWLINE_INIT const int px = bmp->w / 2, py = bmp->h / 2;
#define DM_DRAWLINE_INNER \
     dmUnscaledBlitSurface32to32Transparent(bmp, x0-px, y0-py, screen);
#define DM_DRAWLINE_SPEC
#include "dmdrawline.h"


static int dmFind3DBitmap(int nitems, DM3DBitmap *items, DM3DBitmap *item)
{
    int i;
    for (i = 0; i < nitems; i++)
    if (strcmp(item->name, items[i].name) == 0)
        return i;
    return -1;
}

#define DM_FIND_ITEM_FUNC(NAME, TYPE) \
static int dmFind3D ## NAME (int nitems, TYPE *items, TYPE *item) \
{ \
    int i; \
    for (i = 0; i < nitems; i++) \
    if (memcmp(item, items+i, sizeof(TYPE)) == 0) \
        return i; \
    return -1; \
}

#define DM_ADD_ITEM_FUNC(NAME, TYPE) \
static int dmAdd3D ## NAME (int *nitems, int *nalloc, TYPE **items, TYPE *item, int *res) \
{ \
    int tmp; \
    if ((tmp = dmFind3D ## NAME (*nitems, *items, item)) >= 0) \
    { \
        if (res != NULL) *res = tmp; \
        return DMERR_OK; \
    } \
    if ((*nitems) + 1 >= *nalloc) \
    { \
        (*nalloc) += 16; \
        if ((*items = dmRealloc(*items, *nalloc * sizeof(TYPE))) == NULL) \
        { \
            dmError("Error allocating memory for " # TYPE ".\n"); \
            return DMERR_MALLOC; \
        } \
    } \
    memcpy((*items) + (*nitems), item, sizeof(TYPE)); \
    if (res != NULL) *res = *nitems; \
    (*nitems)++; \
    return DMERR_OK; \
}

DM_FIND_ITEM_FUNC(Vertex, DMVector)
DM_FIND_ITEM_FUNC(Vector, DM3DVector)
DM_FIND_ITEM_FUNC(Sprite, DM3DSprite)

DM_ADD_ITEM_FUNC(Vertex, DMVector)
DM_ADD_ITEM_FUNC(Vector, DM3DVector)
DM_ADD_ITEM_FUNC(Sprite, DM3DSprite)
DM_ADD_ITEM_FUNC(Bitmap, DM3DBitmap)


int dmAdd3DVectorSpriteModelVertex(DM3DVectorSpriteModel *model, DMVector *v, int *index)
{
    return dmAdd3DVertex(&model->nvertices, &model->nvertexalloc,
         &model->vertices, v, index);
}

int dmAdd3DVectorSpriteModelVector(DM3DVectorSpriteModel *model, DM3DVector *v, int *index)
{
    return dmAdd3DVector(&model->nlines, &model->nlinesalloc,
         &model->lines, v, index);
}

int dmAdd3DVectorSpriteModelSprite(DM3DVectorSpriteModel *model, DM3DSprite *v, int *index)
{
    return dmAdd3DSprite(&model->nsprites, &model->nspritesalloc,
         &model->sprites, v, index);
}

int dmAdd3DVectorSpriteModelBitmap(DM3DVectorSpriteModel *model, DM3DBitmap *v, int *index)
{
    return dmAdd3DBitmap(&model->nbitmaps, &model->nbitmapsalloc,
         &model->bitmaps, v, index);
}


static inline DMFloat dmPX(int cx, DMVector p)
{
    return cx + (p.x * 250.0f) / p.z;
}


static inline DMFloat dmPY(int cy, DMVector p)
{
    return cy + (p.y * 250.0f) / p.z;
}


void dmDraw3DVectorSpriteModel(SDL_Surface *screen, const DM3DVectorSpriteModel *model, const DMVector *pos, const DMMatrix *mat, SDL_Surface *fbmap, const Uint32 lcol)
{
    int i;
    int cx = screen->w / 2, cy = screen->h / 2;
    for (i = 0; i < model->nlines; i++)
    {
        DM3DVector *line = &model->lines[i];
        DMVector pv[2];
        dm_vector_copy(&pv[0], &model->vertices[line->v1]);
        dm_vector_copy(&pv[1], &model->vertices[line->v2]);
        dm_vector_mul_by_mat_n(pv, 2, mat);
        dm_vector_add(&pv[0], pos);
        dm_vector_add(&pv[1], pos);
        
        if (pv[0].z <= 0 && pv[1].z <= 0)
            continue;

        if (line->type > 1)
            dmDrawLineSpec(screen, dmPX(cx, pv[0]), dmPY(cy, pv[0]), dmPX(cx, pv[1]), dmPY(cy, pv[1]), lcol, fbmap);
        else
            dmDrawLine32(screen, dmPX(cx, pv[0]), dmPY(cy, pv[0]), dmPX(cx, pv[1]), dmPY(cy, pv[1]), lcol);
    }

    for (i = 0; i < model->nsprites; i++)
    {
        DM3DSprite *sprite = &model->sprites[i];
        DM3DBitmap *bmp = &model->bitmaps[sprite->bitmap];
        DMVector pv;
        dm_vector_mul_by_mat(&pv, &model->vertices[sprite->v], mat);
        dm_vector_add(&pv, pos);
        if (pv.z <= 0)
            continue;
        dmUnscaledBlitSurface32to32Transparent(bmp->img, dmPX(cx, pv), dmPY(cy, pv), screen);
    }
}


static char *dmSkipWhitespace(char *line, BOOL invert)
{
    if (invert)
        for (; *line && !isspace(*line); line++);
    else
        for (; *line && isspace(*line); line++);
    return line;
}


static char *dmSkipUntil(char *line, char ch)
{
    for (; *line && *line != ch; line++);
    return line;
}


static BOOL dmReadCoordinate(const char *orig, char **line, float *value, BOOL next)
{
    *line = dmSkipWhitespace(*line, FALSE);
    if (sscanf(*line, "%f", value) != 1)
    {
        dmError("Expected floating point value @ %d:\n%s\n", (*line - orig), orig);
        return FALSE;
    }
    if (next)
    {
        *line = dmSkipUntil(*line, ',');
        if (**line != ',')
        {
            dmError("Expected comma @ %d:\n%s\n", (*line - orig), orig);
            return FALSE;
        }
        *(*line)++;
    }
    else
        *line = dmSkipWhitespace(*line, TRUE);

    return TRUE;
}


static BOOL dmReadVectorSegments(char *line, DM3DVectorSpriteModel *model, BOOL relative, const DMVector *pt)
{
    DMVector v, p, *t;
    int nvertices, vertex, type;
    int *indices = NULL;
    char *ptr = line;
    
    if (sscanf(ptr+1, "%d", &nvertices) != 1)
    {
        dmError("No # of segments @ '%s'\n", ptr);
        goto error;
    }
    
    if ((indices = dmMalloc(sizeof(int) * (nvertices+1))) == NULL)
        goto error;
    
    ptr = dmSkipWhitespace(ptr, TRUE);
    dm_vector_copy(&v, pt);
    for (vertex = 0; vertex <= nvertices; vertex++)
    {
        if (*ptr == 'Z')
        {
            indices[vertex] = indices[0];
            ptr++;
        }
        else
        {
            if (!dmReadCoordinate(line, &ptr, &p.x, TRUE)) return FALSE;
            if (!dmReadCoordinate(line, &ptr, &p.y, TRUE)) return FALSE;
            if (!dmReadCoordinate(line, &ptr, &p.z, FALSE)) return FALSE;
            if (relative)
            {
                dm_vector_add(&v, &p);
                t = &v;
            }
            else
            {
                dm_vector_add_r(&v, &p, pt);
                t = &v;
            }

            if (dmAdd3DVectorSpriteModelVertex(model, t, &indices[vertex]) != DMERR_OK)
                goto error;
        }

        ptr = dmSkipWhitespace(ptr, FALSE);
    }
    
    if (sscanf(ptr, "%d", &type) != 1)
    {
        dmError("No line type @ '%s'\n", ptr);
        goto error;
    }
    
    for (vertex = 1; vertex <= nvertices; vertex++)
    {
        DM3DVector vec;
        vec.v1 = indices[vertex - 1];
        vec.v2 = indices[vertex];
        vec.type = type;
        if (dmAdd3DVectorSpriteModelVector(model, &vec, NULL) != DMERR_OK)
            goto error;
    }

    return TRUE;

error:
    dmFree(indices);
    return FALSE;
}


static BOOL dmReadSprite(char *line, DM3DVectorSpriteModel *model, DMVector *pos)
{
    DMVector pt;
    DM3DSprite spr;
    char *ptr = line;

    ptr++;
    if (!dmReadCoordinate(line, &ptr, &pt.x, TRUE)) return FALSE;
    if (!dmReadCoordinate(line, &ptr, &pt.y, TRUE)) return FALSE;
    if (!dmReadCoordinate(line, &ptr, &pt.z, FALSE)) return FALSE;
    ptr = dmSkipWhitespace(ptr, FALSE);
    if (*ptr != 'B')
    {
        dmError("No bitmap definition found for sprite.\n");
        return FALSE;
    }

    spr.bitmap = atoi(ptr + 1);

    dm_vector_add(&pt, pos);
    if (dmAdd3DVectorSpriteModelVertex(model, &pt, &spr.v) != DMERR_OK)
        return FALSE;

    if (dmAdd3DVectorSpriteModelSprite(model, &spr, NULL) != DMERR_OK)
        return FALSE;

    return TRUE;
}

static BOOL dmReadBitmap(char *line, DM3DVectorSpriteModel *model, DMResourceLib *lib)
{
    DM3DBitmap bmp, *rbmp;
    int index;
    char *ptr = line;

    strncpy(bmp.name, ptr + 1, sizeof(bmp.name));
    bmp.img = NULL;

    if (dmAdd3DVectorSpriteModelBitmap(model, &bmp, &index) != DMERR_OK)
        return FALSE;

    rbmp = &(model->bitmaps[index]);
    if (rbmp->img == NULL)
    {
        DMResource *fh;
        int res;
        if ((res = dmf_open(lib, rbmp->name, &fh)) != DMERR_OK)
        {
            dmError("Could not open resource file '%s', #%d: %s.\n",
                rbmp->name, res, dmErrorStr(res));
            return FALSE;
        }
        rbmp->img = dmLoadImage(fh);
        dmf_close(fh);
        if (rbmp->img == NULL)
        {
            dmError("Could not load image file '%s'.\n", rbmp->name);
            return FALSE;
        }
    }

    return TRUE;
}


static int dmDoRead3DVectorSpriteModel(DMResource *f, DM3DVectorSpriteModel *model, DMVector *pos)
{
    char line[8192];

    while (dmfgets(line, sizeof(line), f) != NULL)
    {
        char *start = dmSkipWhitespace(line, FALSE);
        switch (*start)
        {
            case 0:
            case '#':
                break;
            
            case 'G':
                {
                int res;
                DMVector pt;
                start++;
                if (!dmReadCoordinate(line, &start, &pt.x, TRUE)) return DMERR_INVALID_DATA;
                if (!dmReadCoordinate(line, &start, &pt.y, TRUE)) return DMERR_INVALID_DATA;
                if (!dmReadCoordinate(line, &start, &pt.z, FALSE)) return DMERR_INVALID_DATA;
                dm_vector_add_r(&pt, pos, &pt);
                if ((res = dmDoRead3DVectorSpriteModel(f, model, &pt)) != DMERR_OK)
                    return res;
                }
                break;
            
            case 'E':
                return DMERR_OK;

            case 'B':
                if (!dmReadBitmap(start, model, f->lib))
                    return DMERR_INVALID_DATA;
                break;

            case 'L':
                if (!dmReadVectorSegments(start, model, FALSE, pos))
                    return DMERR_INVALID_DATA;
                break;

            case 'R':
                if (!dmReadVectorSegments(start, model, TRUE, pos))
                    return DMERR_INVALID_DATA;
                break;
            
            case 'S':
                if (!dmReadSprite(start, model, pos))
                    return DMERR_INVALID_DATA;
                break;
            
            default:
                break;
        }
    }
    return DMERR_OK;
}


int dmRead3DVectorSpriteModel(DMResource *f, DM3DVectorSpriteModel **model)
{
    DMVector pos;

    if ((*model = dmMalloc0(sizeof(DM3DVectorSpriteModel))) == NULL)
        return DMERR_MALLOC;

    memset(&pos, 0, sizeof(pos));
    
    return dmDoRead3DVectorSpriteModel(f, *model, &pos);
}


void dmFree3DVectorSpriteModel(DM3DVectorSpriteModel *model)
{
    int i;
    for (i = 0; i < model->nbitmaps; i++)
    {
        if (model->bitmaps[i].img != NULL)
            SDL_FreeSurface(model->bitmaps[i].img);
    }

    dmFree(model->bitmaps);
    dmFree(model->vertices);
    dmFree(model->lines);
    dmFree(model->sprites);
    dmFree(model);
}
