#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>

// Raydium JPGS video generator (from a big bunch of JPEG files)
// See Raydium doc for examples.

// Compilation can be done with:
// - gcc mk_jpgs.c -lm -o mk_jpgs
// - ... or regular tools (odyncomp, Raydium SDK, ...)

char EXT[]=".jpg";
#define BUFFSIZE  4096

// Until our MinGW supports scandir and alpha sort, let's use this hack.
// (Badly) ported from Sparc64 release of MinGW ...
#ifdef WIN32
#include <sys/types.h>
#include <sys/stat.h>

/*
 * The DIRSIZ macro gives the minimum record length which will hold
 * the directory entry.  This requires the amount of space in struct dirent
 * without the d_name field, plus enough space for the name with a terminating
 * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary.
 */
#undef DIRSIZ
#define DIRSIZ(dp) \
    ((sizeof (struct dirent) - (_MAX_FNAME+1)) + (((dp)->d_namlen+1 + 3) &~ 3))

#ifndef __P
#define __P(args) ()
#endif

int
scandir(dirname, namelist, select, dcomp)
	const char *dirname;
	struct dirent ***namelist;
	int (*select) __P((struct dirent *));
	int (*dcomp) __P((const void *, const void *));
{
	register struct dirent *d, *p, **names;
	register size_t nitems;
	struct stat stb;
	long arraysz;
	DIR *dirp;
	int n;

	if ((dirp = opendir(dirname)) == NULL)
		return(-1);

	n=0;
	while ((d=readdir(dirp)) !=NULL) n++;
	rewinddir(dirp);
	arraysz = (n);
	names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *));
	if (names == NULL)
		return(-1);

	nitems = 0;
	while ((d = readdir(dirp)) != NULL) {
		if (select != NULL && !(*select)(d))
			continue;	/* just selected names */
		/*
		 * Make a minimum size copy of the data
		 */
		p = (struct dirent *)malloc(DIRSIZ(d));
		if (p == NULL)
			return(-1);
		p->d_ino = d->d_ino;
		p->d_reclen = d->d_reclen;
		p->d_namlen = d->d_namlen;
		strncpy(p->d_name, d->d_name,p->d_namlen + 1);
		/*
		 * Check to make sure the array has space left and
		 * realloc the maximum size.
		 */
		if (++nitems >= arraysz) {
			arraysz = stb.st_size / 12;
			names = (struct dirent **)realloc((char *)names,
				arraysz * sizeof(struct dirent *));
			if (names == NULL)
				return(-1);
		}
		names[nitems-1] = p;
	}
	closedir(dirp);
	if (nitems && dcomp != NULL)
		qsort(names, nitems, sizeof(struct dirent *), dcomp);
	*namelist = names;
	return(nitems);
}

int
alphasort(const void *d1, const void *d2)
{
	return (strcmp((*(const struct dirent *const *)d1)->d_name,
	    (*(const struct dirent *const *)d2)->d_name));
}
#endif

void copy(char *name,FILE *fpo)
{
FILE *fp;
char buf[BUFFSIZE];
size_t nmemb;

fp=fopen(name,"rb");
if(!fp)
    {
    printf("cannot open %s\n",name);
    exit(3);
    }


//while(fread(buf,BUFFSIZE,1,fp))
 while ((nmemb=fread(buf, 1, sizeof(buf), fp)) > 0)
    {
    fwrite(buf,1,nmemb,fpo);
    }

fclose(fp);
}

long filesize(char *name)
{
FILE *fp;
long ret;

fp=fopen(name,"rb");
if(!fp)
    {
    printf("cannot open %s\n",name);
    exit(3);
    }
fseek(fp,0,SEEK_END);
ret=ftell(fp);
fclose(fp);
return ret;
}

int is_jpg(const struct dirent *ent)
{
int l,m;
const char *name;

name=ent->d_name;
l=strlen(EXT);
m=strlen(name);

if(l<strlen(EXT))
    return 0;

if( name[m-l+0]==EXT[0] &&
    name[m-l+1]==EXT[1] &&
    name[m-l+2]==EXT[2] &&
    name[m-l+3]==EXT[3] )
        return 1;

return 0;
}

int main(int argc, char **argv)
{
float fps;
int sx,sy,total;
char out[512];
char head[100];
FILE *fpo;
struct dirent **namelist;
int i;
long offset;

if(argc!=5)
    {
    printf("This program will create a movie file (JPGS) for Raydium 3D Game Engine\n");
    printf("using all jpg files in the current directory.\n");
    printf("usage: %s fps sizex sizey output.jpgs\n",argv[0]);
    exit(0);
    }

fps=atof(argv[1]);
sx=atoi(argv[2]);
sy=atoi(argv[3]);
strcpy(out,argv[4]);

fpo=fopen(out,"wb");
if(!fpo)
    {
    printf("cannot open %s for writing\n",out);
    exit(1);
    }

total=scandir(".", &namelist, is_jpg, alphasort);
if(total<0)
    {
    perror("scandir");
    exit(15);
    }

printf("%i files found, creating header ...\n",total);
offset=0;
sprintf(head,"%f %i %i %i|",fps,sx,sy,total);
fprintf(fpo,"%s",head);

// header, second part
offset=0;
for(i=0;i<total;i++)
    {
    fprintf(fpo,"%li|",offset);
    offset+=filesize(namelist[i]->d_name);
    }

// body
printf("creating file ...\n");
for(i=0;i<total;i++)
    {
    copy(namelist[i]->d_name,fpo);
    }

fclose(fpo);
printf("%s created\n",out);
return 0;
}
;
    attrib[i++] = AGL_NONE;

    if (multiSample)
    {
        i--;
        attrib[i++] = AGL_SAMPLE_BUFFERS_ARB;
        attrib[i++] = 1;
        attrib[i++] = AGL_SAMPLES_ARB;
        attrib[i++] = numSamples;
        attrib[i++] = AGL_NONE;

        if (fullScreen)
        {
            aglPixFmt = aglChoosePixelFormat(&screen, 1, attrib);
        }
        else
        {
            aglPixFmt = aglChoosePixelFormat(NULL, 0, attrib);
        }

        if (aglPixFmt == NULL)
        {
            multiSample = 0;
            raydium_log("(my)glut: multisample pixel format not found");
            i -= 5;
            attrib[i++] = AGL_NONE;
        }
    }

    if (!multiSample)
    {
        if (fullScreen)
        {
            aglPixFmt = aglChoosePixelFormat(&screen, 1, attrib);
        }
        else
        {
            aglPixFmt = aglChoosePixelFormat(NULL, 0, attrib);
        }
    }

    if (aglPixFmt == NULL)
    {
        raydium_log("(my)glut: could not find pixel format");
    }

    // Create an AGL context.
    currContext = aglCreateContext(aglPixFmt, NULL);
    aglDestroyPixelFormat(aglPixFmt);

    if (currContext == NULL)
    {
        raydium_log("(my)glut: could not create OpenGL context");
    }
}

void makeMenu()
{
    SInt32 response;
    MenuRef menu;
    CreateNewMenu(mApple, 0, &menu);
    SetMenuTitleWithCFString(menu, CFSTR("Raydium"));

    InsertMenu(menu, 0);
    InsertMenuItemTextWithCFString(menu, CFSTR("About Raydium..."), 0, 0, iAbout);

    // If we're not running on Mac OS X then we need to add a File:Quit command.
    Gestalt(gestaltMenuMgrAttr, &response);

    if ((response & gestaltMenuMgrAquaLayoutMask) == 0)
    {
        menu = NewMenu(mFile, (ConstStr255Param)"File");
        InsertMenu(menu, 0);
        AppendMenu(menu, (ConstStr255Param)"Quit");
    }

    DrawMenuBar();
}

static void exitFunc ()
{
    myglutCloseWindow();
}

static void aboutBox()
{
    // Show a dialog box with info on Raydium.
    SInt16 outItemHit;
    char version[32];
    Str255 Pversion;
    char* about = "Raydium 3D Game Engine <http://raydium.org>";
    Str255 Pabout;
    sprintf(version, "Raydium %s, CQFD Corp.", raydium_version());
    CtoPcpy(Pversion, version);
    CtoPcpy(Pabout, about);
    StandardAlert(kAlertPlainAlert, Pversion, Pabout, NULL, &outItemHit);
}

static void handleMenuEvent(long menuCmd)
{
    int menuNum = HiWord(menuCmd);
    int itemNum = LoWord(menuCmd);

    switch(menuNum)
    {
        case mApple:
        {
            switch (itemNum)
            {
                case iAbout: aboutBox(); break;
                default: break;
            }
            break;
        }
        case mFile:
        {
            switch (itemNum)
            {
                case iQuit: exitFunc(); break;
                default: break;
            }
        }
    }

    // Remove the highlight on the selected menu.
    HiliteMenu(0);
}

static int translateKey(int ch, int key)
{
    special = 1;

    switch (ch)
    {
        case kLeftArrowCharCode:
            ch = GLUT_KEY_LEFT;
            break;
        case kUpArrowCharCode:
            ch = GLUT_KEY_UP;
            break;
        case kRightArrowCharCode:
            ch = GLUT_KEY_RIGHT;
            break;
        case kDownArrowCharCode:
            ch = GLUT_KEY_DOWN;
            break;
        case kPageUpCharCode:
            ch = GLUT_KEY_PAGE_UP;
            break;
        case kPageDownCharCode:
            ch = GLUT_KEY_PAGE_DOWN;
            break;
        case kHomeCharCode:
            ch = GLUT_KEY_HOME;
            break;
        case kEndCharCode:
            ch = GLUT_KEY_END;
            break;
        case kHelpCharCode:
            ch = GLUT_KEY_INSERT;
            break;
        case kFunctionKeyCharCode:
            switch(key)        
            {
                case 0x7A:
                    ch = GLUT_KEY_F1;
                    break;
                case 0x78:
                    ch = GLUT_KEY_F2;
                    break;
                case 0x63:
                    ch = GLUT_KEY_F3;
                    break;
                case 0x76:
                    ch = GLUT_KEY_F4;
                    break;
                case 0x60:
                    ch = GLUT_KEY_F5;
                    break;
                case 0x61:
                    ch = GLUT_KEY_F6;
                    break;
                case 0x62:
                    ch = GLUT_KEY_F7;
                    break;
                case 0x64:
                    ch = GLUT_KEY_F8;
                    break;
                case 0x65:
                    ch = GLUT_KEY_F9;
                    break;
                case 0x6D:
                    ch = GLUT_KEY_F10;
                    break;
                case 0x67:
                    ch = GLUT_KEY_F11;
                    break;
                case 0x6F:
                    ch = GLUT_KEY_F12;
                    break;
                default:
                    // Above F12.
                    raydium_log("(my)glut: unsupported function key");
                    break;
            }
            break;
        default:
            special = 0;
            break;
    }
    return ch;
}

static void handleKeyEvent(EventRecord* eventPtr, int updown)
{
    GrafPtr origPort;

    if (!glutKeyboardFuncCB) return;

    // Handle key events.
    int ch = eventPtr->message & charCodeMask;
    int key = (eventPtr->message & keyCodeMask) >> 8;
    ch = translateKey(ch, key);
    GetPort(&origPort);
    SETPORT(window);
    GlobalToLocal(&eventPtr->where);

    // Special down.
    if (special && updown == GLUT_DOWN && glutSpecialFuncCB && !raydium_key[ch])
        glutSpecialFuncCB(ch, eventPtr->where.h, eventPtr->where.v);

    // Special up.
    if( special && updown == GLUT_UP && glutSpecialUpFuncCB && raydium_key[ch])
        glutSpecialUpFuncCB(ch, eventPtr->where.h, eventPtr->where.v);

    // Normal.
    if(!special && updown == GLUT_DOWN && glutKeyboardFuncCB)
        glutKeyboardFuncCB(ch, eventPtr->where.h, eventPtr->where.v);

    SetPort(origPort);

    return;
}

static void handleDrag(WindowPtr wd, Point mouseloc)
{
    Point loc;
    GrafPtr origPort;
    Rect dragBounds, windowBounds;

    GetRegionBounds(GetGrayRgn(), &dragBounds);
    DragWindow(wd, mouseloc, &dragBounds);

    GetPort(&origPort);
    SETPORT(window);

    GetWindowPortBounds(wd, &windowBounds);
    loc.h = windowBounds.left;
    loc.v = windowBounds.top;

    LocalToGlobal(&loc);
    origin[0] = loc.h;
    origin[1] = loc.v;

    MoveWindow(GRAFPTR window, loc.h, loc.v, false);
    aglUpdateContext(currContext);
    SetPort(origPort);
}

static void handleResize(WindowPtr wd)
{
    GrafPtr origPort;
    Rect rectPort;

    GetPort(&origPort);
    SETPORT(window);

    aglSetCurrentContext(currContext);
    aglUpdateContext(currContext);

    GetWindowPortBounds(wd, &rectPort);

    size[0] = rectPort.right - rectPort.left;
    size[1] = rectPort.bottom - rectPort.top;

    _glutWindowSize[0] = size[0];
    _glutWindowSize[1] = size[1];

    SetPort(origPort);
}

static void handleGoAwayBox(WindowPtr wd, Point mouseloc)
{
    GrafPtr origPort;

    GetPort(&origPort);
    SETPORT(wd);

    if (TrackGoAway(wd, mouseloc))
    { 
        exitFunc();
    }

    SetPort(origPort);
}

static int last_m_h = 0;
static int last_m_v = 0;

static void handleMouseMoveEvt(EventRecord* eventPtr)
{
    GrafPtr origPort;

    if (!glutPassiveMotionFuncCB) return;

    int m_h = eventPtr->where.h;
    int m_v = eventPtr->where.v;

    if ((m_v != last_m_v) || (m_h != last_m_h))
    {
        last_m_v = m_v;
        last_m_h = m_h ;
        GetPort(&origPort);
        SETPORT(window);
        GlobalToLocal(&eventPtr->where);

        if (glutPassiveMotionFuncCB)
        {
            glutPassiveMotionFuncCB(eventPtr->where.h, eventPtr->where.v);
        }

        SetPort(origPort);
    }
    return;
}

static void handleEvents()
{
    EventRecord event;
    GrafPtr origPort;

    if (quitFlag) exitFunc();

    WaitNextEvent(everyEvent, &event, sleepTime, nil);

    button = GLUT_LEFT_BUTTON;
    state = GLUT_UP;

    modifiers = 0;

    if (event.modifiers & (shiftKey | rightShiftKey))
        modifiers |= APPLE_MOD_SHIFT;
    if (event.modifiers & (optionKey | rightOptionKey))
        modifiers |= APPLE_MOD_ALT;
    if (event.modifiers & (controlKey | rightControlKey))
        modifiers |= APPLE_MOD_CTRL;

    switch (event.what)
    {
        case keyDown:
        {            
            state = GLUT_DOWN;

            // Look for a "Command + Key" for the menu.
            if (event.modifiers & cmdKey)
            {
                char achar = (char)(event.message & charCodeMask);
                handleMenuEvent(MenuKey(achar));
                return;
            }

            handleKeyEvent(&event, state);

            return;
        }
        case autoKey:
        {
            if (!autoRepeatKey)
            {
                // Report a mouse move event.
                handleMouseMoveEvt(&event);
                return;
            }

            // Report a key down event.
            state = GLUT_DOWN;
        }
        case keyUp:
        {
            state = GLUT_UP;
            handleKeyEvent(&event, state);
            return;
        }
        case mouseDown:
        {
            short part;
            WindowPtr wd;

            state = GLUT_DOWN;
            part = FindWindow(event.where, &wd);

            switch(part)
            {
                case inMenuBar:
                {
                    handleMenuEvent(MenuSelect(event.where));
                    return;
                }
                case inDrag:
                {
                    handleDrag(wd, event.where);
                    return;
                }
                case inGoAway:
                {
                    handleGoAwayBox(wd, event.where);
                    return;
                }
                case inZoomOut:
                {
                    ZoomWindow(wd, inZoomOut, true);
                    handleResize(wd);
                    return;
                }
                case inZoomIn:
                {
                    ZoomWindow(wd, inZoomIn, false);
                    handleResize(wd);
                    return;
                }
                case inContent:
                {                    
                    if (!wd) return;

                    if (GRAFPTR window != FrontWindow())
                    {
                        SelectWindow(GRAFPTR window);
                        return;
                    }
                }
            }

            clickModifiers = modifiers;

            if (glutMouseFuncCB)
            {
                glutMouseFuncCB(button, state, event.where.h, event.where.v);
            }

            SetPort(origPort);

            return;
        }
        case mouseUp:
        {
            state = GLUT_UP;

            switch(clickModifiers)
            {
                case APPLE_MOD_ALT:
                    button = GLUT_RIGHT_BUTTON;
                    break;
                case APPLE_MOD_CTRL:
                    button = GLUT_MIDDLE_BUTTON;
                    break;
                default:
                    button = GLUT_LEFT_BUTTON;
                    break;
            }

            GetPort(&origPort);
            SETPORT(window);
            GlobalToLocal(&event.where);
            modifiers = 0;

            if (glutMouseFuncCB)
            {
                glutMouseFuncCB(button, state, event.where.h, event.where.v);
            }

            SetPort(origPort);

            return;
        }
        case osEvt:
        {
            unsigned char subcode = event.message >> 24;

            if (subcode == suspendResumeMessage)
                if (resumeFlag & event.message)
                {
                    sleepTime = ACTIVE_SLEEPTIME;
                    return;
                }
                else
                    sleepTime = INACTIVE_SLEEPTIME;
            break;
        }
        case activateEvt:
        {
            if (event.modifiers & activeFlag)
            {
                    sleepTime = ACTIVE_SLEEPTIME;
                    return;
            }
            else
                sleepTime = INACTIVE_SLEEPTIME;
            break;
        }
        case updateEvt:
        {            
            if (glutReshapeFuncCB)
            {
                glutReshapeFuncCB(size[0], size[1]);
            }
        }
        case nullEvent:
        {
            // Likely an idle event.
            // Report mouse location, because there are no mouse move events.
            handleMouseMoveEvt(&event);
            return;
        }
        default:
        {
            raydium_log("(my)glut: unhandled event: %i", event.what);
            break;
        }
    }
}

int currScreen;
int currConnect;

void glutInit(int *argc, char **argv)
{
    currScreen  = 0;
    currConnect = 0;

    glutReshapeFuncCB=NULL;
    glutKeyboardFuncCB=NULL;
    glutSpecialUpFuncCB=NULL;
    glutSpecialFuncCB=NULL;
    glutMotionFuncCB=NULL;
    glutPassiveMotionFuncCB=NULL;
    glutMouseFuncCB=NULL;
    glutDisplayFuncCB=NULL;
    glutIdleFuncCB=NULL;

    _glutMouseVisible=1;
}

void pwInit(int x, int y, int w, int h, int multisample, char *title, int border, int num_samples, int window_style)
{
    if (initialized)
    {
        raydium_log("(my)glut: pwInit already called");
        return;
    }

    fullScreen = ((w < 0) || (h < 0)) ? true : false;
    bool resizable = (window_style == RAYDIUM_RENDERING_WINDOW);
    initialize();
    initialized = true;
    window = NULL;

    if (!makeWindow(x, y, w, h, title, resizable))
    {
        exitFunc();
        exit(4);
    }

    // Initialize OpenGL.
    createContext(multisample, num_samples, fullScreen);

    if (fullScreen)
    {
        sleepTime = 0;

        raydium_log("Entering %ix%i:%i fullscreen mode", horScrSze, verScrSze, 32);

        aglSetCurrentContext(currContext);
        aglSetFullScreen(currContext, 0, 0, 0, 0);

        // Initialise origin and size of the screen.
        origin[0] = 0;
        origin[1] = 0;
        size[0] = horScrSze;
        size[1] = verScrSze;
    }
    else
    {
        // Attach the context to the window.
        aglSetDrawable(currContext, GetWindowPort(GRAFPTR window));
        aglSetCurrentContext(currContext);

        // Create a default menu bar with the quit command.
        makeMenu();

        // Initialise origin and size of the window.
        origin[0] = (horScrSze/2)-(w/2);
        origin[1] = (verScrSze/2)-(h/2);
        size[0] = w;
        size[1] = h;

        MoveWindow(GRAFPTR window, origin[0], origin[1], false);
    }

    aglUpdateContext(currContext);

    _glutWindowSize[0] = size[0];
    _glutWindowSize[1] = size[1];
    _glutWindowInverseRatio = 0;

    glClear(GL_COLOR_BUFFER_BIT);
    glutSwapBuffers();
    glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
}

void glutSetCursor(int c)
{
    int csr = 0xFFFF;
    Cursor* csrPtr;

    switch (c)
    {
        case GLUT_CURSOR_NONE:
            HideCursor();
            _glutMouseVisible = 0;
            return;
        case GLUT_CURSOR_LEFT_ARROW:
            csr = kThemeArrowCursor;
            break;
        default:
            csr = kThemeArrowCursor;
            raydium_log("(my)glut: unavailable cursor type");
    }

    if (csr==0xFFFF)
        SetCursor(csrPtr);
    else
        SetThemeCursor(csr);

    ShowCursor();
    _glutMouseVisible = 1;
}

void glutWarpPointer(int x, int y)
{
    GrafPtr origPort;
    CGPoint pos;

    if (fullScreen)
    {
        x = x - 3;
        y = y - 3;
    }

    pos.x = origin[0] + x;
    pos.y = origin[1] + y;

    GetPort(&origPort);
    SETPORT(window);

    CGSetLocalEventsSuppressionInterval(0);
    CGWarpMouseCursorPosition(pos);

    SetPort(origPort);
}

void glutSwapBuffers(void)
{
    if (!initialized)
    {
        raydium_log("(my)glut: call to glutSwapBuffers without call to pwInit");
        return;
    }

    aglSwapBuffers(currContext);
}

// glutMainLoop is generic (myglut.c)

void myglutGetEvents(void)
{
    handleEvents();
}

void myglutCloseWindow(void)
{
    if (!initialized)
    {
        raydium_log("(my)glut: call to myglutCloseWindow without call to pwInit");
        return;
    }

    if (currContext != NULL)
    {
        aglSetDrawable(currContext, NULL);
        aglSetCurrentContext(NULL);
        aglDestroyContext(currContext);
    }

    if (window != NULL) DisposeWindow(GRAFPTR window);

    initialized = false;

    exit(0);
}