#include "dmlib.h"
#include "dmargs.h"
#include "dmvecmat.h"
#include "dmtext.h"
#include <math.h>

#define DM_COLORS (256)

char *optFontFile = "font.ttf";
BOOL optBenchmark = FALSE;
int optVFlags = SDL_SWSURFACE | SDL_HWPALETTE;
int optScrWidth = 640, optScrHeight = 480, optFontSize = 20, optScrDepth = 32;
int optBenchmarkLen = 20;

DMOptArg optList[] = {
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 3, 'f', "full",       "Fullscreen", OPT_NONE },
    { 4, 'h', "hw",         "Use SDL hardware surface", OPT_NONE },
    { 5, 's', "size",       "Initial window size/resolution -s 640x480", OPT_ARGREQ },
    { 6, 'd', "depth",      "Color depth of mode/window in bits (8/32)", OPT_ARGREQ },
    { 7, 'b', "bench",      "Run in benchmark mode", OPT_NONE },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    dmArgsPrintHelp(stdout, optList, optListN);
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN) {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 2:
        dmVerbosity++;
        break;
    
    case 3:
        optVFlags |= SDL_FULLSCREEN;
        break;

    case 6:
        if (optArg)
            optScrDepth = atoi(optArg);
        break;

    case 5:
        {
            int w, h;
            if (sscanf(optArg, "%dx%d", &w, &h) == 2)
            {
                if (w < 320 || h < 200 || w > 3200 || h > 3200)
                {
                    dmError("Invalid width or height: %d x %d\n", w, h);
                    return FALSE;
                }
                optScrWidth = w;
                optScrHeight = h;
            }
            else 
            {
                dmError("Invalid size argument '%s'.\n", optArg);
                return FALSE;
            }
        }
        break;

    case 7:
        optBenchmark = TRUE;
        break;

    default:
        dmError("Unknown option '%s'.\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


void DM_MakePalette(SDL_Surface *scr)
{
    SDL_Color pal[DM_COLORS];
    int n;

    for (n = 0; n < 256; n++)
    {
        pal[n].r = n;
        pal[n].g = n;
        pal[n].b = n;
    }

    SDL_SetColors(scr, pal, 0, DM_COLORS);
}


void DM_PrintRect(FILE *f, SDL_Rect *r)
{
    fprintf(f, "SDL_Rect <%d, %d : %d, %d>\n",
        r->x, r->y, r->w, r->h);
}

BOOL DM_InitializeVideo(SDL_Surface **screen)
{
    *screen = SDL_SetVideoMode(optScrWidth, optScrHeight, optScrDepth, optVFlags | SDL_RESIZABLE);
    if (*screen == NULL)
    {
        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
        return FALSE;
    }

#if 0
    SDL_Rect r;
    r.x = -50;
    r.y = 50;
    r.w = 700;
    r.h = 300;
    DM_PrintRect(stderr, &r);
    SDL_SetClipRect(*screen, &r);
    DM_PrintRect(stderr, &r);
    DM_PrintRect(stderr, &((*screen)->clip_rect));
#endif

    return TRUE;
}

void DM_Random(SDL_Surface *screen, int q)
{
    Uint8 *pix = screen->pixels;
    int xc, yc;
    
    for (yc = 0; yc < screen->h; yc++)
    {
        Uint8 *dp = pix;

        for (xc = 0; xc < screen->w; xc++)
            *dp++ = yc + (xc ^ q) + (yc & q);

        pix += screen->pitch;
    }
}

#define QWIDTH	256
#define QHEIGHT	160

typedef Uint8 DMBlockMap[QHEIGHT][QWIDTH];


static DMFloat dmClip(DMFloat a)
{
    return (a < 0.0f ? 0.0f : (a > 1.0f ? 1.0f : a));
}


void dmMakeBumpMap(DMBlockMap map, DMFloat q, DMFloat m)
{
    int x, y;
    for (y = 0; y < QHEIGHT; y++)
        for (x = 0; x < QWIDTH; x++)
        {
            DMFloat f = 0.40f + dmPerlinNoise2D(x, y, 1.1f, q, 2);
            map[y][x] = (int) (dmClip(f) * m);
        }
}


void dmShadowTraceHeightMap(DMBlockMap lightMap, DMBlockMap pheightMap, DMVector *light)
{
    int i, j;

    for (j = 0; j < QHEIGHT; j++)
        for (i = 0; i < QWIDTH; i++)
        {
            DMVector vr, vl, va;
            DMFloat vrayLen, vfactor;
            int vlen;
            BOOL wasHit;

            // Perform shadow occlusion via simplistic raytracing
            vr.x = i;
            vr.y = j;
            vr.z = light->z; // - 10.0;
//            vr.z = pheightMap[j][i];
            
            // Calculate light vector vector
            dm_vector_sub_r(&vl, &vr, light);
            vrayLen = dm_vector_length(&vl);
            
#if 1
            dm_vector_copy(&va, &vl);
            dm_vector_normalize(&va);
            dm_vector_scale(&va, 0.6f);
            dm_vector_copy(&vr, light);

            vlen = 0;
            wasHit = FALSE;
            do
            {
                float h;
                
                // If ray is inside the heightmap, get value
                if (vr.x >= 0 && vr.y >= 0 && vr.x < QWIDTH && vr.y < QHEIGHT)
                    h = pheightMap[(int) vr.y][(int) vr.x];
                else
                    break;
                
                // Check for hits
                if (h > vr.z)
                    wasHit = TRUE;
                else
                {
                    // Move forwards
                    dm_vector_add(&vr, &va);
                    vlen++;
                }
            }
            while (!wasHit && vlen <= vrayLen);

            // Check if the ray hit something, e.g. is this point occluded?
            if (wasHit && vlen < vrayLen)
            {
                vfactor = vlen * 0.01;
            }
            else
                vfactor = vlen * 0.02;
#endif

#if 1
            {
                /* Calculate light's intensity based on the angle it "hits"
                 *
                 * 1) Calculate the vectors that form the imaginary "plane"
                 * 2) Cross-product -> normal vector of the plane
                 * 2) Normalize the normal vector
                 * 3) Calculate light vector's hit angle by dot product
                 */
                DMVector v1, v2;
                DMFloat c;

                v1.x = 2.0f;
                v1.y = 0.0f;
                v1.z = (DMFloat) (pheightMap[j][i] - pheightMap[j][i + 1]);

                v2.x = 0.0f;
                v2.y = 2.0f;
                v2.z = (DMFloat) (pheightMap[j][i] - pheightMap[j + 1][i]);

                dm_vector_cross(&vr, &v1, &v2);
                dm_vector_normalize(&vr);
                dm_vector_normalize(&vl);
                c = dm_vector_dot(&vl, &vr);

		vrayLen = 255 - (vrayLen * 0.1) * vrayLen + (c * 128.0f) + (vfactor * vfactor * 1255);
            }
#else
            vrayLen = 255 - vrayLen * vrayLen * (vfactor * vfactor);
            if (vrayLen < 0) vrayLen = 0;
            vrayLen += pheightMap[j][i];
#endif

            // Clip result
            if (vrayLen < 0)
                vrayLen = 0;
            else if (vrayLen > 255.0f)
                vrayLen = 255.0f;

            lightMap[j][i] = vrayLen;
        }
}


int main(int argc, char *argv[])
{
    SDL_Surface *screen = NULL, *bmap = NULL;
    TTF_Font *font = NULL;
    SDL_Color fontcol={255,155,155,0};
    SDL_Event event;
    int mouseX, mouseY;
    BOOL initSDL = FALSE, initTTF = FALSE, exitFlag, showMap = FALSE;

    dmInitProg("blittest", "dmlib blittest", "0.2", NULL, NULL);
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, NULL, FALSE))
        exit(1);

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
    {
        dmError("Could not initialize SDL: %s\n", SDL_GetError());
        goto error_exit;
    }
    initSDL = TRUE;


    if (TTF_Init() < 0)
    {
        dmError("Could not initialize FreeType/TTF: %s\n", SDL_GetError());
        goto error_exit;
    }
    initTTF = TRUE;

    font = TTF_OpenFont(optFontFile, optFontSize);
    if (font == NULL)
    {
        dmError("Could not load TTF font '%s' (%d): %s\n",
            optFontFile, optFontSize, SDL_GetError());
        goto error_exit;
    }
    TTF_SetFontStyle(font, TTF_STYLE_NORMAL);

    if (optBenchmark)
    {
        screen = SDL_CreateRGBSurface(SDL_SWSURFACE, optScrWidth, optScrHeight, optScrDepth, 0, 0, 0, 0);
        if (screen == NULL)
        {
            dmError("Could not create screen surface.\n");
            goto error_exit;
        }
        
        dmMsg(0, "Benchmark mode, not opening window.\n");
    }
    else
    {
        if (!DM_InitializeVideo(&screen))
            goto error_exit;

        SDL_WM_SetCaption("Halleluja", "DMT");
    }

    dmPerlinInit();

    bmap = SDL_CreateRGBSurface(SDL_SWSURFACE, QWIDTH, QHEIGHT, 8, 0, 0, 0, 0);
    DM_MakePalette(bmap);
    DM_Random(bmap, 15);
    
    DMVector light;
    DMBlockMap heightMap;
    light.x = light.y = 128;
    light.z = 128;
    dmMakeBumpMap(heightMap, 0.06, 254);



    int numFrames = 0, startTime = SDL_GetTicks(), endTime = 0;
    exitFlag = FALSE;

    if (optBenchmark)
        dmMsg(0, "Starting benchmark, running for %d seconds.\n", optBenchmarkLen);

    while (!exitFlag)
    {
        dmMakeBumpMap(heightMap, 0.05 + sin(SDL_GetTicks() * 0.001) * 0.002 , 254);
        if (!optBenchmark)
        {
            while (SDL_PollEvent(&event))
            switch (event.type)
            {
                case SDL_KEYDOWN:
                    switch (event.key.keysym.sym)
                    {
                        case SDLK_ESCAPE: exitFlag = TRUE; break;
                        
                        case SDLK_F5:
                            showMap = !showMap;
                            break;
                            
                        default:
                            break;
                    }

                    break;
                
                case SDL_VIDEORESIZE:
                    optScrWidth = event.resize.w;
                    optScrHeight = event.resize.h;

                    if (!DM_InitializeVideo(&screen))
                        goto error_exit;

                    break;
                
                case SDL_VIDEOEXPOSE:
                    break;

                case SDL_QUIT:
                    exit(0);
            }

            SDL_GetMouseState(&mouseX, &mouseY);
            light.x = ((DMFloat) mouseX * QWIDTH) / (DMFloat) optScrWidth;
            light.y = ((DMFloat) mouseY * QHEIGHT) / (DMFloat) optScrHeight;
        }

        if (!optBenchmark && SDL_MUSTLOCK(screen) != 0 && SDL_LockSurface(screen) != 0)
        {
            dmError("Can't lock surface.\n");
            goto error_exit;
        }


        if (showMap)
            memcpy(bmap->pixels, heightMap, QWIDTH * QHEIGHT);
        else
            dmShadowTraceHeightMap(bmap->pixels, heightMap, &light);
        
        dmScaledBlitSurfaceAny(bmap, 0, 0, screen->w, screen->h, screen, DMD_NONE);

        if (!optBenchmark)
        {
            dmDrawTTFText(screen, font, fontcol, 0, 0, "%3.1f FPS", 
                (float) (numFrames * 1000.0f) / (float) (endTime - startTime));

            if (SDL_MUSTLOCK(screen) != 0)
                SDL_UnlockSurface(screen);

            SDL_Flip(screen);
            SDL_Delay(10);
        }

        endTime = SDL_GetTicks();
        numFrames++;

        if (optBenchmark)
        {
            if (endTime - startTime > optBenchmarkLen * 1000)
                exitFlag = TRUE;
        }
    }

    // Print benchmark results
    dmMsg(0, "%d frames in %d ms, fps = %1.3f\n",
        numFrames, endTime - startTime,
        (float) (numFrames * 1000.0f) / (float) (endTime - startTime));


error_exit:
    dmMsg(0, "Shutting down dmlib.\n");
    if (screen)
        SDL_FreeSurface(screen);

    if (font)
        TTF_CloseFont(font);

    if (initSDL)
        SDL_Quit();

    if (initTTF)
        TTF_Quit();

    return 0;
}
