#include "dmengine.h"
#include "dmtext.h"
#include "dmfft.h"
#include "dmvecmat.h"
#include <math.h>

static int demoInit(DMEngineData *engine);
static void demoShutdown(DMEngineData *engine);
static int demoRender(DMEngineData *engine);


int demoPreInit(DMEngineData *engine)
{
    dmInitProg("hitler",
        "Hitler on kiva #01",
        "0", "ISO",
        NULL);

    engine->optPackFilename  = "hitler.dat";
    engine->optDataPath      = NULL;
    engine->optResFlags      = DRF_USE_PACK | DRF_PRELOAD_RES; // | DRF_USE_STDIO;

    engine->optAudioSetup    = DM_ASETUP_JSS;

    engine->optVidSetup      = DM_VSETUP_ASPECT;
    engine->optVidWidth      = 640;
    engine->optVidHeight     = 480;
    engine->optVidDepth      = 32;
    engine->optVFlags        = SDL_SWSURFACE;

    engine->demoInit = demoInit;
    engine->demoRender = demoRender;
    engine->demoShutdown = demoShutdown;

    return DMERR_OK;
}


#define FFT_SIZE 128
DMFFTContext fft;


static int demoInit(DMEngineData *engine)
{
    int i;
    JSSModule *mod = NULL;

    dmInitializeFFT(&fft, FFT_SIZE);

    engineGetResModule(engine, mod, "ehka.jss");

    if ((i = jssConvertModuleForPlaying(mod)) != DMERR_OK)
    {
        dmError("Could not convert module for playing, %d: %s\n",
            i, dmErrorStr(i));
        return DMERR_INIT_FAIL;
    }
    
    jvmSetCallback(engine->jssDev, jmpExec, engine->jssPlr);
    jmpSetModule(engine->jssPlr, mod);
    jmpPlayOrder(engine->jssPlr, 0);
    jvmSetGlobalVol(engine->jssDev, 55);

    // Jne
    srand(15);

    return DMERR_OK;
}


static void demoShutdown(DMEngineData *engine)
{
    (void) engine;
    dmEndFFT(&fft);
}


static inline float dmCX(DMEngineData *engine, const float x)
{
    return (x * engine->screen->w);
}


static inline float dmCY(DMEngineData *engine, const float y)
{
    return (y * engine->screen->h);
}


static inline float dmQX(DMEngineData *engine, SDL_Surface *img, const float x)
{
    return engine->optVidNative ? (img->w * x) : (img->w * engine->screen->w * x) / 640.0f;
}


static inline float dmQY(DMEngineData *engine, SDL_Surface *img, const float y)
{
    return engine->optVidNative ? (img->h * y) : (img->h * engine->screen->h * y) / 480.0f;
}


typedef struct
{
    char *filename;
    float xc, yc;
    SDL_Surface *img;
} DMTextItem;


static DMTextItem textItems[] =
{
    { "text01.png", 0.05, 0.10, NULL },
    { "text02.png", 0.10, 0.30, NULL },
    { "text03.png", 0.60, 0.30, NULL },
    { "text04.png", 0.20, 0.60, NULL },
    { "text05.png", 0.30, 0.70, NULL },
};

static const int ntextItems = sizeof(textItems) / sizeof(textItems[0]);


static DMTextItem textItems2[] =
{
    { "text11.png", 0.05, 0.10, NULL },
    { "text02.png", 0.30, 0.25, NULL },
    { "text12.png", 0.10, 0.40, NULL },
    { "text13.png", 0.20, 0.60, NULL },
    { "text14.png", 0.30, 0.70, NULL },
};

static const int ntextItems2 = sizeof(textItems2) / sizeof(textItems2[0]);


static DMTextItem textItems3[] =
{
    { "teki.png"   , 0.25, 0.20, NULL },
    { "mitvit.png" , 0.05, 0.40, NULL },
    { "ja.png"     , 0.05, 0.65, NULL },
    { "kemisti.png", 0.15, 0.65, NULL },
};

static const int ntextItems3 = sizeof(textItems3) / sizeof(textItems3[0]);




#define DM_RADIAL_BLUR(YC, XC) \
        DMVector p1 = { xc, yc, 0, 0 }, p2 = { cx, cy, 0, 0 }, v; \
        dm_vector_sub_r(&v, &p2, &p1); \
        dm_vector_scale(&v, scale); \
        dm_vector_add(&v, &p1); \
        if (v.y YC || v.x XC) continue; \
        DMRGBA32 *dp = pix + xc, \
                 *q = ((DMRGBA32 *)img->pixels) + ((int)(v.y) * pitch) + (int)v.x; \
        dp->r = (q->r + dp->r) / 2; \
        dp->g = (q->g + dp->g) / 2; \
        dp->b = (q->b + dp->b) / 2;



void dmRadialBlur(SDL_Surface *img, const int cx, const int cy, const DMFloat scale)
{
    const int pitch = img->pitch / sizeof(DMRGBA32);
    int xc, yc;

#pragma omp parallel private(yc, xc) shared(img)
    {
#pragma omp sections nowait
        {
#pragma omp section
            for (yc = cy; yc >= 0; yc--)
            {
                DMRGBA32 *pix = ((DMRGBA32 *)img->pixels) + yc * pitch;
                for (xc = cx + 1; xc < img->w; xc++)
                {
                    DM_RADIAL_BLUR(< 0, >= img->w)
                }
            }

#pragma omp section
            for (yc = cy; yc >= 0; yc--)
            {
                DMRGBA32 *pix = ((DMRGBA32 *)img->pixels) + yc * pitch;
                for (xc = cx; xc > 0; xc--)
                {
                    DM_RADIAL_BLUR(< 0, < 0)
                }    
            }

#pragma omp section
            for (yc = cy + 1; yc < img->h; yc++)
            {
                DMRGBA32 *pix = ((DMRGBA32 *)img->pixels) + yc * pitch;
                for (xc = cx; xc > 0; xc--)
                {
                    DM_RADIAL_BLUR(>= img->h, < 0)
                }    
            }

#pragma omp section
            for (yc = cy + 1; yc < img->h; yc++)
            {
                DMRGBA32 *pix = ((DMRGBA32 *)img->pixels) + yc * pitch;
                for (xc = cx + 1; xc < img->w; xc++)
                {
                    DM_RADIAL_BLUR(>= img->h, >= img->w)
                }    
            }
        }
    }
}


static int hitlerText(DMEngineData *engine, const int dt, float fftPow, DMTextItem *items, const int nitems, BOOL *nollattu)
{
    int i, vis;
    if (!(*nollattu))
    {
        for (i = 0; i < nitems; i++)
            engineGetResImage(engine, items[i].img, items[i].filename);

        *nollattu = TRUE;
    }
    
    float q = fftPow * 0.05f;
    vis = dt / 1000;
    for (i = 0; i < nitems; i++)
    {
        DMTextItem *item = &items[i];
        if (i < vis)
        {
            dmScaledBlitSurface32to32TransparentGA(
                item->img,
                dmCX(engine, item->xc - q),
                dmCY(engine, item->yc - q),
                
                dmQX(engine, item->img, 1.0f + q * 2.0f),
                dmQY(engine, item->img, 1.0f + q * 2.0f),
                
                engine->screen,
                
                100 + 80 * fftPow);
        }
    }
    
    return DMERR_OK;
}


static int demoRender(DMEngineData *engine)
{
    float t = engineGetTimeDT(engine);
    static DMScaledBlitFunc cblit = NULL;

    // Do FFT
    DMFFTType fftPow = 0;
    BOOL fftOK = FALSE;
    static DMFFTType fftAmp[FFT_SIZE / 2];
    static DMFFTType fftData[FFT_SIZE];

    dmMutexLock(engine->audioStreamMutex);
    if (engine->audioStreamBuf != 0 && engine->audioStreamLen > FFT_SIZE)
    {
        int i;
        Sint16 *buf = (Sint16 *) engine->audioStreamBuf;
        for (i = 0; i < FFT_SIZE; i++)
        {
            fftData[i] = *buf;
            buf += 2;
        }
        fftOK = TRUE;
    }
    dmMutexUnlock(engine->audioStreamMutex);

    if (fftOK)
    {
        dmRealFFT(&fft, fftData);
        dmConvertFFTtoPowerAndSum(&fft, fftData, fftAmp, 1.0, &fftPow, 0.00004f);
    }


#if 1
    // Demokoodi
    if (t < 5)
    {
        // Alkufeidi sisään
        int dt = engineGetTime(engine, 0);
        static DMLerpContext fadeLerp;
        static SDL_Surface *tausta = NULL;
        static BOOL nollattu = FALSE;
        if (!nollattu)
        {
            engineGetResImage(engine, tausta, "tausta.jpg");
            cblit = dmGetScaledBlitFunc(tausta->format, engine->screen->format, DMD_NONE);

            dmLerpInit(&fadeLerp, 0, 255, 5000);
            nollattu = TRUE;
        }

        dmClearSurface(engine->screen, dmMapRGB(engine->screen, 0,0,0));

        dmScaledBlitSurface32to32TransparentGA(tausta,
            0,
            0,

            dmQX(engine, tausta, 1.0f),
            dmQX(engine, tausta, 1.0f),

            engine->screen,
            dmLerpSCurveClamp(&fadeLerp, dt + 150500 * fftPow));
    }
    else
    if (t < 25)
    {
        static BOOL nollattu = FALSE;
        static SDL_Surface *tausta = NULL;
        if (!nollattu)
        {
            engineGetResImage(engine, tausta, "tausta.jpg");
        }

        cblit(tausta,
            0,
            0,

            dmQX(engine, tausta, 1.0f),
            dmQX(engine, tausta, 1.0f),

            engine->screen);
    }
    
    if (t <= 7)
    {
        int dt = engineGetTime(engine, 2);
        static DMLerpContext fadeLerp;
        static SDL_Surface *logo1 = NULL, *logo2 = NULL;
        static BOOL nollattu = FALSE;
        if (!nollattu)
        {
            engineGetResImage(engine, logo1, "iso.png");
            engineGetResImage(engine, logo2, "forevisar.png");

            dmLerpInit(&fadeLerp, 0, 255, 2500);
            nollattu = TRUE;
        }
    
        int fade;
        if (dt < 2500)
        {
            fade = dmLerpSCurveClamp(&fadeLerp, dt + fftPow * 1000);
        }
        else
        if (dt > 4500)
        {
            fade = 255 - dmLerpSCurveClamp(&fadeLerp, dt - 4500 + fftPow * 1000);
        }
        else
        {
            fade = 255;
        }
        
        float d = fftPow * 0.1f;

        dmScaledBlitSurface32to32TransparentGA(logo1,
            dmCX(engine, 0.1f),
            dmCY(engine, 0.22f),

            dmQX(engine, logo1, 1.0f),
            dmQY(engine, logo1, 1.0f),

            engine->screen,
            fade);

        dmScaledBlitSurface32to32TransparentGA(logo2,
            dmCX(engine, 0.2f - d),
            dmCY(engine, 0.72f - d),

            dmQX(engine, logo2, 1.0f + d * 2.0f),
            dmQY(engine, logo2, 1.0f + d * 2.0f),

            engine->screen,
            fade);
    }
    else
    if (t <= 15)
    {
        int dt = engineGetTime(engine, 7);
        static DMLerpContext fadeLerp;
        static SDL_Surface *logo1 = NULL, *logo2 = NULL;
        static BOOL nollattu = FALSE;
        if (!nollattu)
        {
            engineGetResImage(engine, logo1, "hitler_on_kiva.png");
            engineGetResImage(engine, logo2, "hitler.png");

            dmLerpInit(&fadeLerp, 0, 255, 2500);
            nollattu = TRUE;
        }
    
        int fade;
        if (dt < 2500)
        {
            fade = dmLerpSCurveClamp(&fadeLerp, dt + fftPow * 1000);
        }
        else
        if (dt > 4500)
        {
            fade = 255 - dmLerpSCurveClamp(&fadeLerp, dt - 4500 + fftPow * 1000);
        }
        else
        {
            fade = 255;
        }
        
        dmScaledBlitSurface32to32TransparentGA(logo1,
            dmCX(engine, 0.1f),
            dmCY(engine, 0.22f),

            dmQX(engine, logo1, 1.0f),
            dmQY(engine, logo1, 1.0f),

            engine->screen,
            fade);

        float q = fftPow * 0.2;
        dmScaledBlitSurface32to32TransparentGA(logo2,
            dmCX(engine, 0.15f),
            dmCY(engine, 0.47f - q),

            dmQX(engine, logo2, 1.0f + q),
            dmQY(engine, logo2, 1.0f + q),

            engine->screen,
            fade);
    }
    
    if (t <= 15)
    {
        int dt = engineGetTime(engine, 0);
        static DMLerpContext posLerp, posLerp2;
        static SDL_Surface *mainos = NULL;
        static BOOL nollattu = FALSE;
        if (!nollattu)
        {
            engineGetResImage(engine, mainos, "radio.png");

            dmLerpInit(&posLerp, 1, -1, 15000);
            dmLerpInit(&posLerp2, 0.3, 0.7, 15000);
            nollattu = TRUE;
        }

        dmScaledBlitSurface32to32TransparentGA(mainos,
            dmCX(engine, dmLerpSCurveClamp(&posLerp2, dt)),
            dmCY(engine, dmLerpSCurveClamp(&posLerp, dt)),

            dmQX(engine, mainos, 1.0f),
            dmQY(engine, mainos, 1.0f),

            engine->screen,
            50);
    }
    else
    if (t <= 25)
    {
        int dt = engineGetTime(engine, 15);
        static DMLerpContext posLerp, posLerp2;
        static DMScaledBlitFunc qblit = NULL;
        static SDL_Surface *mainos = NULL, *mainos2 = NULL, *hitler = NULL, *sydan = NULL;
        static BOOL nollattu = FALSE;
        if (!nollattu)
        {
            engineGetResImage(engine, mainos, "lasi.jpg");
            engineGetResImage(engine, mainos2, "nigrolin.jpg");
            engineGetResImage(engine, hitler, "hitler2.png");
            engineGetResImage(engine, sydan, "sydan.png");

            qblit = dmGetScaledBlitFunc(hitler->format, engine->screen->format, DMD_TRANSPARENT);

            dmLerpInit(&posLerp, 1, -1, 20000);
            dmLerpInit(&posLerp2, -0.5, -0.2, 20000);

            nollattu = TRUE;
        }

        dmScaledBlitSurface32to32TransparentGA(mainos,
            dmCX(engine, dmLerpSCurveClamp(&posLerp2, dt)),
            dmCY(engine, dmLerpSCurveClamp(&posLerp, dt + fftPow * 250)),

            dmQX(engine, mainos, 1.0f),
            dmQY(engine, mainos, 1.0f),

            engine->screen,
            50);

        dmScaledBlitSurface32to32TransparentGA(mainos2,
            dmCX(engine, dmLerpSCurveClamp(&posLerp, dt)),
            dmCY(engine, dmLerpSCurveClamp(&posLerp2, dt + fftPow * 250)),

            dmQX(engine, mainos2, 1.0f),
            dmQY(engine, mainos2, 1.0f),

            engine->screen,
            50);

        float d = fftPow * 0.1f;

        qblit(hitler,
            dmCX(engine, 0.4 - d),
            dmCY(engine, 0.3 - d),

            dmQX(engine, hitler, 1.0f + d * 2.0f),
            dmQY(engine, hitler, 1.0f + d * 2.0f),

            engine->screen);



        if (t > 17)
        {
            static BOOL xnollattu = FALSE;

            if (t > 19)
            {
            float z = sin(t * 2.0f + fftPow) * 0.1f;
            qblit(sydan,
                dmCX(engine, 0.7) + dmQX(engine, sydan, 0.5f - z),
                dmCY(engine, 0.55) + dmQX(engine, sydan, 0.5f - z),

                dmQX(engine, sydan, 1.0f + z * 2.0f),
                dmQY(engine, sydan, 1.0f + z * 2.0f),

                engine->screen);
            }

            hitlerText(engine, engineGetTime(engine, 17), fftPow, textItems, ntextItems, &xnollattu);
        }
    }
    else
#endif
    if (t <= 60)
    {
        int dt = engineGetTime(engine, 25);
        static SDL_Surface *tekstur, *poni1, *poni2;
        static BOOL nollattu = FALSE;
        static DMScaledBlitFunc qblit = NULL;
        if (!nollattu)
        {
            engineGetResImage(engine, tekstur, "disco.jpg");
            engineGetResImage(engine, poni1, "poni1.png");
            engineGetResImage(engine, poni2, "poni2.png");

            qblit = dmGetScaledBlitFunc(poni1->format, engine->screen->format, DMD_TRANSPARENT);

            nollattu = TRUE;
        }

        float m = t*2.0f;
        float q = sin(m) * 0.3f,
              q2 = sin(m + 0.2) * 0.4f,
              z = sin(m) * 0.3f + 2.0f,
              z2 = sin(m + 0.1f) * 0.3f + 2.0f;
        
        dmScaledBlitSurface32to32TransparentGA(tekstur,
            dmCX(engine, sin(q) * 0.1f - 0.2f),
            dmCY(engine, cos(q) * 0.1f - 0.2f),

            dmQX(engine, tekstur, z),
            dmQY(engine, tekstur, z),

            engine->screen,
            130);

        dmScaledBlitSurface32to32TransparentGA(tekstur,
            dmCX(engine, sin(q2) * 0.1f - 0.2f),
            dmCY(engine, cos(q2) * 0.1f - 0.2f),

            dmQX(engine, tekstur, z2),
            dmQY(engine, tekstur, z2),

            engine->screen,
            130);


        qblit(poni1,
            dmCX(engine, 0.2f),
            dmCY(engine, 0.07f),

            dmQX(engine, poni1, 1.0f),
            dmQY(engine, poni1, 1.0f),

            engine->screen);


        dmScaledBlitSurface32to32TransparentGA(poni2,
            dmCX(engine, 0.2f),
            dmCY(engine, 0.07f),

            dmQX(engine, poni2, 1.0f),
            dmQY(engine, poni2, 1.0f),

            engine->screen,
            50 + fftPow * 400);
          
        dmRadialBlur(engine->screen,
            dmCX(engine, 0.5f + sin(m) * 0.1f),
            dmCY(engine, 0.5f + cos(m) * 0.1f),
            0.035f);

        if (t >= 30 && t <= 37)
        {
            static BOOL xnollattu = FALSE;
            hitlerText(engine, engineGetTime(engine, 30), fftPow + 0.2, textItems2, ntextItems2, &xnollattu);
        }
        else
        if (t >= 38 && t <= 47)
        {
            static BOOL xnollattu = FALSE;
            hitlerText(engine, engineGetTime(engine, 38), fftPow + 0.2, textItems3, ntextItems3, &xnollattu);
        }
        else
        if (t > 50)
            return 1;
    } 

    return DMERR_OK;
}
