/*
 *  Copyright (C) 1995, 1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File: texture.c
 *
 *  Loads and manages textures.  Part of D3DApp.
 *
 *  D3DApp is a collection of helper functions for Direct3D applications.
 *  D3DApp consists of the following files:
 *	d3dapp.h    Main D3DApp header to be included by application
 *      d3dappi.h   Internal header
 *      d3dapp.c    D3DApp functions seen by application.
 *      ddcalls.c   All calls to DirectDraw objects except textures
 *      d3dcalls.c  All calls to Direct3D objects except textures
 *      texture.c   Texture loading and managing texture list
 *      misc.c	    Miscellaneous calls
 */

#include "d3dappi.h"

#define MAGICBYTES 2

/*
 * STATIC FUNCTION DECLARATIONS
 */
static void D3DAppIAddPathList(const char *path);
static void D3DAppIInitialisePathList();
static FILE * D3DAppIFindFile(const char *name, const char *mode);
static BOOL loadPPMHeader(FILE *fp, DWORD *width, DWORD *height, int *maxgrey);


/***************************************************************************/
/*                        Managing the texture list                        */
/***************************************************************************/
/*
 * D3DAppILoadTextureSurf
 * Creates a texture map surface and texture object from the numbered PPM
 * file.  This is done in a two step process.  A source texture surface and
 * object are created in system memory.  A second, initially empty, texture
 * surface is created (in video memory if hardware is present).  The source
 * texture is loaded into the destination texture surface and then discarded.
 * This process allows a device to compress or reformat a texture map as it
 * enters video memory during the Load call.
 */
BOOL
D3DAppILoadTextureSurf(int n, BOOL* bInVideo)
{
    DDSURFACEDESC ddsd;
    LPDIRECTDRAWSURFACE lpSrcTextureSurf = NULL;
    LPDIRECT3DTEXTURE2 lpSrcTexture = NULL;
    LPDIRECTDRAWPALETTE lpDstPalette = NULL;
    PALETTEENTRY ppe[256];
    DWORD pcaps;
    /*
     * Release the surface if it is hanging around
     */
    RELEASE(d3dappi.lpTextureSurf[n]);
    /*
     * Create a surface in system memory and load the PPM file into it.
     * Query for the texture interface.
     */
    lpSrcTextureSurf = D3DAppILoadSurface(d3dappi.lpDD, d3dappi.ImageFile[n],
					  &d3dappi.ThisTextureFormat.ddsd,
					  DDSCAPS_SYSTEMMEMORY);
    if (!lpSrcTextureSurf)
	goto exit_with_error;
    LastError = lpSrcTextureSurf->lpVtbl->QueryInterface(lpSrcTextureSurf,
					     &IID_IDirect3DTexture2,
					     (LPVOID*)&lpSrcTexture);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Failed to obtain D3D texture interface for a source texture.\n%s", D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    /*
     * Create an empty texture surface to load the source texture into.
     * The DDSCAPS_ALLOCONLOAD flag allows the DD driver to wait until the
     * load call to allocate the texture in memory because at this point,
     * we may not know how much memory the texture will take up (e.g. it
     * could be compressed to an unknown size in video memory).
     * Make sure SW renderers get textures in system memory
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, lpSrcTextureSurf);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Could not get the surface description of the source texture.\n%s",
			      D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_ALLOCONLOAD;
    if (!d3dappi.ThisDriver.bIsHardware)
	ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    LastError = D3DAppICreateSurface(&ddsd, &d3dappi.lpTextureSurf[n]);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Could not create the destination texture surface.\n%s",
			      D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) {
	pcaps = DDPCAPS_8BIT | DDPCAPS_ALLOW256;
    } else if (ddsd.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4) {
	pcaps = DDPCAPS_4BIT;
    } else {
	pcaps = 0;
    }
    if (pcaps) {
	memset(ppe, 0, sizeof(PALETTEENTRY) * 256);
	LastError = d3dappi.lpDD->lpVtbl->CreatePalette(d3dappi.lpDD, pcaps,
						 ppe, &lpDstPalette, NULL);
	if (LastError != DD_OK) {
	    D3DAppISetErrorString("Failed to create a palette for the destination texture.\n%s",
				  D3DAppErrorToString(LastError));
	    goto exit_with_error;
	}
	LastError = d3dappi.lpTextureSurf[n]->lpVtbl->SetPalette(d3dappi.lpTextureSurf[n],
				lpDstPalette);
	if (LastError != DD_OK) {
	    D3DAppISetErrorString("Failed to set the destination texture's palette.\n%s",
				  D3DAppErrorToString(LastError));
	    goto exit_with_error;
	}
//	lpDstPalette->lpVtbl->Release(lpDstPalette);
    }
    /*
     * Query our destination surface for a texture interface
     */
    LastError = d3dappi.lpTextureSurf[n]->lpVtbl->QueryInterface(d3dappi.lpTextureSurf[n],
					     &IID_IDirect3DTexture2,
					     (LPVOID*)&d3dappi.lpTexture[n]);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Failed to obtain D3D texture interface for a destination texture.\n%s",
			      D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    /*
     * Load the source texture into the destination.  During this call, a
     * driver could compress or reformat the texture surface and put it in
     * video memory.
     */
    LastError = d3dappi.lpTexture[n]->lpVtbl->Load(d3dappi.lpTexture[n], lpSrcTexture);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Could not load a source texture into a destination texture.\n%s",
			      D3DAppErrorToString(LastError));
	goto exit_with_error;
    }

    /* 
     * Now we are done with the source texture
     */
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);

    /*
     * Did the texture end up in video memory?
     */
    LastError = D3DAppIGetSurfDesc(&ddsd, d3dappi.lpTextureSurf[n]);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Could not get the surface description of the loaded texture surface.\n%s",
			      D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    if (ddsd.ddsCaps.dwCaps & DDSCAPS_VIDEOMEMORY)
	*bInVideo = TRUE;
    else
	*bInVideo = FALSE;
    
    
    return TRUE;

exit_with_error:
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);
    RELEASE(lpDstPalette);
    RELEASE(d3dappi.lpTexture[n]);
    RELEASE(d3dappi.lpTextureSurf[n]);
    return FALSE;       
}

/*
 * D3DAppIReloadTextureSurf
 * Reloads a lost and restored texture surface
 */
BOOL
D3DAppIReloadTextureSurf(int n)
{
    LPDIRECTDRAWSURFACE lpSrcTextureSurf = NULL;
    LPDIRECT3DTEXTURE2 lpSrcTexture = NULL;

    /*
     * Create a surface in system memory and load the PPM file into it.
     * Query for the texture interface.
     */
    lpSrcTextureSurf = D3DAppILoadSurface(d3dappi.lpDD, d3dappi.ImageFile[n],
					  &d3dappi.ThisTextureFormat.ddsd,
					  DDSCAPS_SYSTEMMEMORY);
    if (!lpSrcTextureSurf)
	goto exit_with_error;
    LastError = lpSrcTextureSurf->lpVtbl->QueryInterface(lpSrcTextureSurf,
					     &IID_IDirect3DTexture2,
					     (LPVOID*)&lpSrcTexture);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Failed to obtain D3D texture interface for a source texture.\n%s", D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    /*
     * Load the source texture into the destination.  During this call, a
     * driver could compress or reformat the texture surface and put it in
     * video memory.
     */
    LastError = d3dappi.lpTexture[n]->lpVtbl->Load(d3dappi.lpTexture[n], lpSrcTexture);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Could not load a source texture into a destination texture.\n%s",
			      D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    /* 
     * Now we are done with the source texture
     */
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);

    return TRUE;

exit_with_error:
    RELEASE(lpSrcTexture);
    RELEASE(lpSrcTextureSurf);
    return FALSE;       
}


/*
 * D3DAppIGetTextureHandle
 * Get a texture handle from the current D3D device for this texture and save
 * it in the MasterTextureHandle list and public texture handle list.
 */
BOOL
D3DAppIGetTextureHandle(int n)
{
    LastError = d3dappi.lpTexture[n]->lpVtbl->GetHandle(d3dappi.lpTexture[n],
			       d3dappi.lpD3DDevice, &MasterTextureHandle[n]);
    if (LastError != DD_OK) {
	D3DAppISetErrorString("Could not get a handle to loaded texture %i.\n%s",
			      n, D3DAppErrorToString(LastError));
	goto exit_with_error;
    }
    /*
     * If textures are enabled, put the handle in the public texture list,
     * otherwise, keep it as zero.
     */
    if (!d3dappi.bTexturesDisabled) {
	d3dappi.TextureHandle[n] = MasterTextureHandle[n];
    } else {
	d3dappi.TextureHandle[n] = 0;
    }
    return TRUE;
exit_with_error:
    MasterTextureHandle[n] = 0;
    d3dappi.TextureHandle[n] = 0;
    return FALSE;       
}

/*
 * D3DAppIReleaseTexture
 * Release this texture surface and texture interface.  Remember, a texture
 * handle is NOT and object and does not need to be released or destroyed.
 * The handle is no longer valid after the device is destroyed, so set it to
 * zero here.
 */
void
D3DAppIReleaseTexture(int n)
{
    RELEASE(d3dappi.lpTexture[n]);
    RELEASE(d3dappi.lpTextureSurf[n]);
    MasterTextureHandle[n] = 0;
    d3dappi.TextureHandle[n] = 0;
}

/*
 * D3DAppIReleaseAllTextures
 * Release all texture surfaces and texture interfaces
 */
void
D3DAppIReleaseAllTextures(void)
{
    int i;
    for (i = 0; i < d3dappi.NumTextures; i++) {
	D3DAppIReleaseTexture(i);
    }
}

/*
 * D3DAppILoadAllTextures
 * Load all texture surfaces, qeury them for texture interfaces and get
 * handles for them from the current D3D driver.
 */
BOOL
D3DAppILoadAllTextures(void)
{
    int i;
    if (d3dappi.ThisDriver.bDoesTextures) {
	d3dappi.NumUsableTextures = 0;
	for (i = 0; i < d3dappi.NumTextures; i++) {
	    BOOL bInVideo;
	    ATTEMPT(D3DAppILoadTextureSurf(i, &bInVideo));
	    if (!bInVideo && d3dappi.ThisDriver.bIsHardware) {
		/*
		 * If a texture fails to load into video memory for a hardware
		 * device, stop the NumUsableTextures count here.
		 */
		D3DAppIReleaseTexture(i);
		break;
	    } else {
		++d3dappi.NumUsableTextures;
	    }
	}
	for (i = 0; i < d3dappi.NumUsableTextures; i++) {
	    ATTEMPT(D3DAppIGetTextureHandle(i));
	}
    } else {
	d3dappi.NumUsableTextures = 0;
    }
    return TRUE;

exit_with_error:
    for (i = 0; i < d3dappi.NumTextures; i++) {
	D3DAppIReleaseTexture(i);
    }
    return FALSE;
}

/***************************************************************************/
/*                    Loading a PPM file into a surface                    */
/***************************************************************************/
/*
 * LoadSurface
 * Loads a ppm file into a texture map DD surface of the given format.  The
 * memory flag specifies DDSCAPS_SYSTEMMEMORY or DDSCAPS_VIDEOMEMORY.
 */
LPDIRECTDRAWSURFACE
D3DAppILoadSurface(LPDIRECTDRAW lpDD, LPCSTR lpName,
		   LPDDSURFACEDESC lpFormat, DWORD memoryflag)
{
    LPDIRECTDRAWSURFACE lpDDS;
    DDSURFACEDESC ddsd, format;
    D3DCOLOR colors[256];
    D3DCOLOR c;
    DWORD dwWidth, dwHeight;
    int i, j;
    FILE *fp;
    char *lpC;
    LPDIRECTDRAWPALETTE lpDDPal;
    PALETTEENTRY ppe[256];
    int psize;
    DWORD pcaps;
    int color_count;
    BOOL bQuant = FALSE;
    HRESULT ddrval;

    /*
     * Find the image file and open it
     */
    fp = D3DAppIFindFile(lpName, "rb");
    if (fp == NULL) {
        D3DAppISetErrorString("Cannot find %s.\n", lpName);
	return NULL;
    }
    /*
     * Parse the PPM header
     */
    if (!loadPPMHeader(fp, &dwWidth, &dwHeight, &i)) {
	fclose(fp);
	D3DAppISetErrorString("Could not load or parse PPM header in %s.\n", lpName);
	return NULL;
    }
    /*
     * Create a surface of the given format using the dimensions of the PPM
     * file.
     */
    memcpy(&format, lpFormat, sizeof(DDSURFACEDESC));
    if (format.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) {
        bQuant = TRUE;
	psize = 256;
	pcaps = DDPCAPS_8BIT | DDPCAPS_ALLOW256;
    } else if (format.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED4) {
        bQuant = TRUE;
	psize = 16;
	pcaps = DDPCAPS_4BIT;
    }
    memcpy(&ddsd, &format, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | memoryflag;
    ddsd.dwHeight = dwHeight;
    ddsd.dwWidth = dwWidth;

    ddrval = lpDD->lpVtbl->CreateSurface(lpDD, &ddsd, &lpDDS, NULL);
    if (ddrval != DD_OK) {
        D3DAppISetErrorString("CreateSurface for texture failed (loadtex).\n%s",
			      D3DAppErrorToString(ddrval));
	return NULL;
    }
    /*
     * Lock the surface so it can be filled with the PPM file
     */
    memset(&ddsd, 0, sizeof(DDSURFACEDESC));
    ddsd.dwSize = sizeof(DDSURFACEDESC);
    ddrval = lpDDS->lpVtbl->Lock(lpDDS, NULL, &ddsd, 0, NULL);
    if (ddrval != DD_OK) {
	lpDDS->lpVtbl->Release(lpDDS);
        D3DAppISetErrorString("Lock failed while loading surface (loadtex).\n%s",
			      D3DAppErrorToString(ddrval));
	return NULL;
    }
    /*
     * The method of loading depends on the pixel format of the dest surface
     */
    if (!bQuant) {
	/*
	 * The texture surface is not palettized
	 */
        unsigned long* lpLP;
	unsigned short* lpSP;
	unsigned char* lpCP;
        unsigned long m;
        int s;
        int red_shift, red_scale;
        int green_shift, green_scale;
        int blue_shift, blue_scale;
	/*
	 * Determine the red, green and blue masks' shift and scale.
	 */
        for (s = 0, m = format.ddpfPixelFormat.dwRBitMask; !(m & 1);
							       s++, m >>= 1);
        red_shift = s;
        red_scale = 255 / (format.ddpfPixelFormat.dwRBitMask >> s);
        for (s = 0, m = format.ddpfPixelFormat.dwGBitMask; !(m & 1);
							       s++, m >>= 1);
        green_shift = s;
        green_scale = 255 / (format.ddpfPixelFormat.dwGBitMask >> s);
        for (s = 0, m = format.ddpfPixelFormat.dwBBitMask; !(m & 1);
							       s++, m >>= 1);
        blue_shift = s;
        blue_scale = 255 / (format.ddpfPixelFormat.dwBBitMask >> s);
	/*
	 * Each RGB bit count requires different pointers
	 */
	switch (format.ddpfPixelFormat.dwRGBBitCount) {
	    case 32 :
		for (j = 0; j < (int)dwHeight; j++) {
		    /*
		     * Point to next row in texture surface
		     */
		    lpLP = (unsigned long*)(((char*)ddsd.lpSurface) +
							    ddsd.lPitch * j);
		    for (i = 0; i < (int)dwWidth; i++) {
			int r, g, b;
			/*
			 * Read each value, scale it and shift it into position
			 */
			r = getc(fp) / red_scale;
			g = getc(fp) / green_scale;
			b = getc(fp) / blue_scale;
			*lpLP = (r << red_shift) | (g << green_shift) |
				(b << blue_shift);
			lpLP++;
		    }
		}
		break;
	    case 16 :
		for (j = 0; j < (int)dwHeight; j++) {
		    lpSP = (unsigned short*)(((char*)ddsd.lpSurface) +
							    ddsd.lPitch * j);
		    for (i = 0; i < (int)dwWidth; i++) {
			int r, g, b;
			r = getc(fp) / red_scale;
			g = getc(fp) / green_scale;
			b = getc(fp) / blue_scale;
			*lpSP = (r << red_shift) | (g << green_shift) |
				(b << blue_shift);
			lpSP++;
		    }
		}
		break;
	    case 8:
		for (j = 0; j < (int)dwHeight; j++) {
		    lpCP = (unsigned char*)(((char*)ddsd.lpSurface) +
							    ddsd.lPitch * j);
		    for (i = 0; i < (int)dwWidth; i++) {
			int r, g, b;
			r = getc(fp) / red_scale;
			g = getc(fp) / green_scale;
			b = getc(fp) / blue_scale;
			*lpCP = (r << red_shift) | (g << green_shift) | 
				(b << blue_shift);
			lpCP++;
		    }
		}
		break;
	    default:
		/*
		 * This wasn't a format I recognize
		 */
	    	lpDDS->lpVtbl->Unlock(lpDDS, NULL);
		fclose(fp);
		lpDDS->lpVtbl->Release(lpDDS);
                D3DAppISetErrorString("Unknown pixel format (loadtex).");
		return NULL;
	}
	/*
	 * Unlock the texture and return the surface pointer
	 */
	lpDDS->lpVtbl->Unlock(lpDDS, NULL);
        fclose(fp);
        return (lpDDS);
    }

    /*
     * We assume the 8-bit palettized case
     */
    color_count = 0;	/* number of colors in the texture */
    for (j = 0; j < (int)dwHeight; j++) {
	/*
	 * Point to next row in surface
	 */
	lpC = ((char*)ddsd.lpSurface) + ddsd.lPitch * j;
	for (i = 0; i < (int)dwWidth; i++) {
	    int r, g, b, k;
	    /*
	     * Get the next red, green and blue values and turn them into a
	     * D3DCOLOR
	     */
	    r = getc(fp);
	    g = getc(fp);
	    b = getc(fp);
            c = RGB_MAKE(r, g, b);
	    /*
	     * Search for this color in a table of colors in this texture
	     */
            for (k = 0; k < color_count; k++)
                if (c == colors[k]) break;
            if (k == color_count) {
		/*
		 * This is a new color, so add it to the list
		 */
                color_count++;
		/*
		 * More than 256 and we fail (8-bit) 
		 */
                if (color_count > psize) {
		    color_count--;
		    k = color_count - 1;
                    //goto burst_colors;
		}
                colors[k] = c;
            }
	    /*
	     * Set the "pixel" value on the surface to be the index into the
	     * color table
	     */
	    if (psize == 16) {
		if ((i & 1) == 0)
		    *lpC = k & 0xf;
		else {
		    *lpC |= (k & 0xf) << 4;
		    lpC++;
		}
	    } else {
		*lpC = (char)k;
		lpC++;
	    }
        }
    }
    /*
     * Close the file and unlock the surface
     */
    fclose(fp);
    lpDDS->lpVtbl->Unlock(lpDDS, NULL);

//burst_colors:
    if (color_count > psize) {
	/*
	 * If there are more than 256 colors, we overran our palette
	 */
        lpDDS->lpVtbl->Unlock(lpDDS, NULL);
	lpDDS->lpVtbl->Release(lpDDS);
        D3DAppISetErrorString("Palette burst. (loadtex).\n");
	return (NULL);
    }

    /*
     * Create a palette with the colors in our color table
     */
    memset(ppe, 0, sizeof(PALETTEENTRY) * 256);
    for (i = 0; i < color_count; i++) {
	ppe[i].peRed = (unsigned char)RGB_GETRED(colors[i]);
        ppe[i].peGreen = (unsigned char)RGB_GETGREEN(colors[i]);
	ppe[i].peBlue = (unsigned char)RGB_GETBLUE(colors[i]);
    }
    /*
     * Set all remaining entry flags to D3DPAL_RESERVED, which are ignored by
     * the renderer.
     */
    for (; i < 256; i++)
	ppe[i].peFlags = D3DPAL_RESERVED;
    /*
     * Create the palette with the DDPCAPS_ALLOW256 flag because we want to
     * have access to all entries.
     */
    ddrval = lpDD->lpVtbl->CreatePalette(lpDD,
					 DDPCAPS_INITIALIZE | pcaps,
					 ppe, &lpDDPal, NULL);
    if (ddrval != DD_OK) {
        lpDDS->lpVtbl->Release(lpDDS);
        D3DAppISetErrorString("Create palette failed while loading surface (loadtex).\n%s",
			      D3DAppErrorToString(ddrval));
	return (NULL);
    }
    /*
     * Finally, bind the palette to the surface
     */
    ddrval = lpDDS->lpVtbl->SetPalette(lpDDS, lpDDPal);
    if (ddrval != DD_OK) {
	lpDDS->lpVtbl->Release(lpDDS);
	lpDDPal->lpVtbl->Release(lpDDPal);
        D3DAppISetErrorString("SetPalette failed while loading surface (loadtex).\n%s",
			      D3DAppErrorToString(ddrval));
	return (NULL);
    }

    lpDDPal->lpVtbl->Release(lpDDPal);

    return lpDDS;
}

static BOOL
ppm_getbyte(FILE *fp, char *newByte)
{
    char cchar;
    int cc;

    /* Get a byte, and dump comments */
    cchar = cc = getc(fp);
    if (cc == EOF) {
      return FALSE;
    }
  
    if (cchar == '#') {
	/* Read until next end of line */
	do {
	    cchar = cc = getc(fp);
	    if (cc == EOF)
		return FALSE;
	} while (cchar != '\n' && cchar != '\r');
    }

  *newByte = cchar;

  return TRUE;
}

static BOOL
ppm_getint(FILE *fp, int *newInt)
{
  int cint;
  char cchar;

  do {
    if (!ppm_getbyte(fp, &cchar)) return FALSE;
  } while (isspace(cchar));
  
  if (!isdigit(cchar)) {
    return FALSE;
  }
  
  cint = 0;
  
  do {
    cint = (cint * 10) + (cchar - '0');
    if (!ppm_getbyte(fp, &cchar)) return FALSE;
  } while(isdigit(cchar));

  *newInt = cint;

  return TRUE;
}
    
static BOOL
loadPPMHeader(FILE *fp, DWORD *width, DWORD *height, int *maxgrey)
{
    char magic[MAGICBYTES], cchar;

    /* Slurp up ppm header until we get width, height and maxgrey values */

    /* Read and check the magic bytes */
    if (fread(magic, MAGICBYTES, 1, fp) != 1)
	return FALSE;
    if (magic[0] != 'P' || magic[1] != '6')
	return FALSE;

    /* Now we can actually read some numbers */
    if (!ppm_getint(fp, width))
	return FALSE;
    if (!ppm_getint(fp, height))
	return FALSE;
    if (!ppm_getint(fp, maxgrey))
	return FALSE;

    /* Slurp up rest of white space so we get to actual data */
    do {
	if (!ppm_getbyte(fp, &cchar))
	    return FALSE;
    } while (cchar == ' ' || cchar == '\t' || cchar == '\n' || cchar == '\r');

    fseek(fp, -1, SEEK_CUR);

    return TRUE;
}


/***************************************************************************/
/*                         Finding Textures                                */
/***************************************************************************/

#define MAXPATH    256
#define PATHSEP    ';'
#define FILESEP    '\\'
#define MAXCONTENTS 25
#define RESPATH     "Software\\Microsoft\\Direct3D"

static int PathListInitialised = FALSE;

/*
 * PathList structure
 * A list of directories in which to search for the texture.
 */
static struct {
    int count;
    char *contents[MAXCONTENTS];
} PathList;

/*
 * D3DAppIAddPathList
 * Add this string to the search path list
 */
static void 
D3DAppIAddPathList(const char *path)
{
    char *p;
    char *elt;
    int len;

    while (path) {
	p = LSTRCHR(path, PATHSEP);
	if (p)
	    len = p - path;
	else
	    len = lstrlen(path);
	elt = (char *) malloc(len + 1);
	if (elt == NULL)
	    return;
	lstrcpyn(elt, path, len + 1);
	elt[len] = '\0';
	PathList.contents[PathList.count] = elt;
	PathList.count++;
	if (p)
	    path = p + 1;
	else
	    path = NULL;
	if (PathList.count == MAXCONTENTS)
	    return;
    }
    return;
}

/*
 * D3DAppIInitialisePathList
 * Create a search path with the D3DPATH env. var and D3D Path registry entry
 */
static void 
D3DAppIInitialisePathList()
{
    long result;
    HKEY key;
    DWORD type, size;
    static char buf[512];
    char* path;

    if (PathListInitialised)
	return;
    PathListInitialised = TRUE;

    PathList.count = 0;
    path = getenv("D3DPATH");
    D3DAppIAddPathList(".");
    if (path != NULL) {
        D3DAppIAddPathList(path);
        return;
    }
    result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, RESPATH, 0, KEY_READ, &key);
    if (result == ERROR_SUCCESS) {
        size = sizeof(buf);
	result = RegQueryValueEx(key, "D3D Path", NULL, &type, (LPBYTE) buf,
	                         &size);
	RegCloseKey(key);
	if (result == ERROR_SUCCESS && type == REG_SZ)
	    D3DAppIAddPathList(buf);
    }
}


/*
 * D3DAppIFindFile
 * Find and open a file using the current search path.
 */
static FILE*
D3DAppIFindFile(const char *name, const char *mode)
{
    FILE *fp;
    char buf[MAXPATH];
    static char filesep[] = {FILESEP, 0};
    int i;

    D3DAppIInitialisePathList();

    fp = fopen(name, mode);
    if (fp != NULL)
	return fp;

    for (i = 0; i < PathList.count; i++) {
	lstrcpy(buf, PathList.contents[i]);
	lstrcat(buf, filesep);
	lstrcat(buf, name);
	fp = fopen(buf, mode);
	if (fp)
	    return fp;
    }
    return NULL;
}

/*
 * D3DAppIReleasePathList
 * Release the path list for program termination
 */
void
D3DAppIReleasePathList(void)
{
    int i;
    for (i = 0; i < PathList.count; i++) {
        free(PathList.contents[i]);
        PathList.contents[i] = NULL;
    }
    PathList.count = 0;
    PathListInitialised = FALSE;
}

