/*
 * This class contains everything we know about the format of information
 * on a Photo_CD.  Note that this knowledge is not complete, it's just
 * as much as has been deciphered by the hpcdtoppm program written by
 * Hadmut Danisch (danisch@ira.uka.de).  Some comments from that program:

 * hpcdtoppm (Hadmut's pcdtoppm) v0.3
 * Copyright (c) 1992 by Hadmut Danisch (danisch@ira.uka.de).
 * Permission to use and distribute this software and its
 * documentation for noncommercial use and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation. It is not allowed to sell this software in 
 * any way. This software is not public domain.

 * Some of that program is reimplemented in this class, and the rest is
 * still (currently) off in some C source and header files.
 * These comments by:
 *			Garance Alistair Drosehn;  Jan 14th, 1993
*/

#import "photoCD_Rdr.h"

#import "pcd.h"
#define PCD_READ_OK	0
#define PCD_READ_ERROR	1
#define PCD_READBUF { \
	if ( READBUF < 1 ) { \
	    NXRunAlertPanel(0, \
		" Unexpected eof while reading", \
		0, 0, 0); \
	    } \
	 }
#define PCD_SEEK(x) { \
	if (fseek(fin,((x) * SECSIZE),0)) { \
	    NXRunAlertPanel(0, \
		" Unexpected error from fseek to sector %d", \
		0, 0, 0, (x)); \
	    } \
	 }

extern void rotateBm( dim *w, dim *h, implane *r, implane *g, implane *b, enum TURNS t);

@implementation photoCD_Rdr

/* methods only called from within this class */
- (NXBitmapImageRep *) _allocBitmap
	     :(dim)width :(dim)height
	  red:(uBYTE *)redPlane 
	green:(uBYTE *)greenPlane
	 blue:(uBYTE *)bluePlane
{
    u_char	*rgb[5];
    NXBitmapImageRep	*bitmap;

    rgb[0] = redPlane;
    rgb[1] = greenPlane;
    rgb[2] = bluePlane;
    rgb[3] = NULL;
    rgb[4] = NULL;

    /* allocate the proper sized BitmapImage and use the planes
       allocated by that for our processing */
    bitmap = [[NXBitmapImageRep alloc] initDataPlanes:rgb 
			pixelsWide:width 
			pixelsHigh:height 
			bitsPerSample:8 
			samplesPerPixel:3 
			hasAlpha:NO 
			isPlanar:YES 
			colorSpace:NX_RGBColorSpace 
			bytesPerRow:0 
			bitsPerPixel:0];
    if ( ! bitmap ) {
	NXRunAlertPanel(0,
	    " Unable to allocate bitmap for a %d x %d image",
	    0, 0, 0, width, height);
	/* probably should so something less drastic here */
	exit(9);
	}
    
    return bitmap;
 }


- (short)_getExpectedThumbnailCount
{
    /* assume that the caller is reading an overview file */
    struct ph0		*d;

    PCD_SEEK(0);
    PCD_READBUF;
    d = (struct ph0 *) sbuffer;
    return d->num_thumbs;
}


- setDoSharp:(BOOL)wantsDoSharp
{
    mainSetSharpit( wantsDoSharp );
    return self;
}


- setNextGammaCorrection:(BOOL)wantsNextCorr
{
    if( wantsNextCorr) mainSetGammaCorr(C_NeXT);
    else mainSetGammaCorr(C_LINEAR);
    return self;
}


- (int)readOverviewFromFile:(char *)ovFil
	      curImageCount:(int)initialImageCount
	         ImageArray:(ovImageArray *)imageArrayPtr
		 MaxEntries:(int)overviewReadMax  /* note: settable */
{
    dim		w, h;
    implane	Luma, Chroma1, Chroma2;

    NXBitmapImageRep	*bm;
    int		pictNumber, imageCount, expectedCount, readStatus;
    char	statStr[45];

    fin=fopen(ovFil,"r");
    if ( !fin ) {
	NXRunAlertPanel(0,
	    " Unable to open overview file %s",
	    0, 0, 0, ovFil);
	return -1;
	}
	
    /*  free overview images from previous photo_cd (if any) */
    for( ; initialImageCount > 0; initialImageCount-- ) {
	bm = (*imageArrayPtr)[initialImageCount];
	if ( bm != nil ) {
	    (*imageArrayPtr)[initialImageCount] = nil;
	    [bm free];
	    }
    }
    initialImageCount = imageCount = 0;
    bm = nil;
    
    expectedCount = [self _getExpectedThumbnailCount];

    [readStatusField setStringValue:"Processing thumbnails"];
    [readStatusPanel makeKeyAndOrderFront:self];

    w=BaseW/4;
    h=BaseH/4;
    
    pictNumber = 1;
    while ( (!feof(fin)) && (pictNumber <= overviewReadMax) ) {

	/* Display a status message for each image, and call
	 * NXPing every 4 images to make sure the status messages
	 * are seen by the user.  Without the call to NXPing, the
	 * messages are buffered up, and aren't displayed until
	 * that buffer is full.
	 */
	sprintf(statStr, "Reading Thumbnail # %d", pictNumber);
	[readStatusField setStringValue:statStr];
	if ( (pictNumber % 4) == 0 ) NXPing();
	
	/* allocate the buffers */
	planealloc(&Luma,    w,h);
	planealloc(&Chroma1, w,h);
	planealloc(&Chroma2, w,h);

	PCD_SEEK(5 + SeBase16 * (pictNumber - 1));
	readStatus = readplain(w, h, &Luma, &Chroma1, &Chroma2, fin);
	if (readStatus != PCD_READ_OK) {
	    if (    (readStatus == PCD_READ_ERROR) 
	         && ( imageCount == expectedCount)  ) {
		/* not a problem, no error message to write */
		}
	    else {
		/*  more noticable error indication, as none of the
		 *  other errors should ever happen.
		 */
		NXRunAlertPanel(0,
		    " Unexpected error (%d) from readplain in"
		    " readOverview processing for picture #%d"
		    " in file %s",
		    0, 0, 0, readStatus, pictNumber, ovFil);
		}
	    break;
	    }
	interpolate(&Chroma1);
	interpolate(&Chroma2);
	colconvert(&w,&h,&Luma,&Chroma1,&Chroma2);
	/* at this point Luma is Red, Chroma1 is Green, Chroma2 is Blue */

	bm = [self _allocBitmap :w :h
		red:Luma.im green:Chroma1.im blue:Chroma2.im];

	imageCount = pictNumber;
	(*imageArrayPtr)[imageCount] = bm;
    
	pictNumber++;			/* try for next picture */
	}
    
    fclose(fin);
    fin = 0x0;

    if ( imageCount != expectedCount ) {
	/* just a simple error message to console */
	printf("Read in %d overview pictures, but %d were expected in %s\n",
	    imageCount, expectedCount, ovFil);
	}

    [readStatusPanel close];
    return imageCount;
}


- readBitmapFromFile:(char *)imFile atSize:(enum is_Tags)imageSize
{
    dim		w, h, rp_w, rp_h;
    int		seekPos = -1, cd_offset = -1;
    implane	Luma, Chroma1, Chroma2;
    NXBitmapImageRep  *bm;
    int		readStatus;
    enum TURNS	turn=T_NONE;

    fin=fopen(imFile,"r");
    if ( !fin ) {
	NXRunAlertPanel(0, " Unable to open image file %s",
	    0, 0, 0, imFile);
	return nil;
	}

    switch ( imageSize ) {
	case is_Base16_TAG:
	    w = rp_w = BaseW / 4;
	    h = rp_h = BaseH / 4;
	    seekPos = L_Head + 1;
	    break;
	case is_Base4_TAG:  
	    w = rp_w = BaseW / 2;
	    h = rp_h = BaseH / 2;
	    seekPos = L_Head + L_Base16 + 1;
	    break;
	case is_4Base_TAG:
	    w = 2 * BaseW;
	    rp_w = BaseW;
	    h = 2 * BaseH;
	    rp_h = BaseH;
	    seekPos = L_Head + L_Base16 + L_Base4 + 1;	// sic
	    break;
	case is_16Base_TAG:
	    //	this option does seem to work, but it's currently
	    //	disabled in the preferences panel because it uses
	    //	*huge* amounts of VM...
	    w = 4 * BaseW;
	    rp_w = BaseW;
	    h = 4 * BaseH;
	    rp_h = BaseH;
	    seekPos = L_Head + L_Base16 + L_Base4 + 1;	// sic
	    break;
	default:
	    // 	the correct values for case is_Base_TAG.
	    w = rp_w = BaseW;
	    h = rp_h = BaseH;
	    seekPos = L_Head + L_Base16 + L_Base4 + 1;
	    break;
	}

    /* allocate the buffers */
    planealloc(&Luma,    w,h);
    planealloc(&Chroma1, w,h);
    planealloc(&Chroma2, w,h);

    PCD_SEEK(1); PCD_READBUF;
    switch(sbuffer[0xe02 & 0x7ff]&0x03) {
	case 0x00: turn=T_NONE;  break;
	case 0x01: turn=T_LEFT;  break;
	case 0x03: turn=T_RIGHT; break;
	default:
	    NXRunAlertPanel(0,
		    " Unexpected ªturnº in pickImage processing",
		    0, 0, 0);
	    fclose(fin);		// perform some clean-up
	    fin = 0x0;
	    return nil;		// can't process the picture
    }

    PCD_SEEK( seekPos );
    readStatus = readplain(rp_w, rp_h, &Luma, &Chroma1, &Chroma2, fin);
    if ( readStatus != PCD_READ_OK ) {
	NXRunAlertPanel(0,
	    " Unexpected error (%d) from readplain in pickImage"
	    " processing for image at w=%d, h=%d",
	    0, 0, 0, readStatus, rp_w, rp_h);
	fclose(fin);		// perform some clean-up
	fin = 0x0;
	return nil;		// can't process the picture
	}
    interpolate(&Chroma1);
    interpolate(&Chroma2);
    if ( w != rp_w ) {		// ie, is_4Base_TAG or is_16Base_TAG
	//   these image sizes need more interpolate-tion calls
	interpolate(&Luma);
	interpolate(&Chroma1);
	interpolate(&Chroma2);

	//   They also need this readhqt/decode processing.
#define nullplane ((implane *) 0)
	switch ( imageSize ) {
	    case is_4Base_TAG:
		cd_offset = L_Head + L_Base16 + L_Base4 + L_Base;
		PCD_SEEK(cd_offset + 4);
		readhqt(w, h, 1, fin);
		PCD_SEEK(cd_offset + 5);
		decode(w, h, &Luma, nullplane, nullplane, 0, fin);
		break;
	    case is_16Base_TAG:
		cd_offset = L_Head + L_Base16 + L_Base4 + L_Base;
		PCD_SEEK(cd_offset + 4);
		readhqt(w / 2, h / 2, 1, fin);
		PCD_SEEK(cd_offset + 5);
		decode(w / 2, h / 2, &Luma, nullplane, nullplane, 0, fin);
		interpolate(&Luma);
    
		cd_offset = ftell(fin);
		if (cd_offset % SECSIZE) {
		    NXRunAlertPanel(0,
			" Base16 Position error in pickImage",
			0, 0, 0);
		    fclose(fin);		// perform some clean-up
		    fin = 0x0;
		    return nil;		// can't process the picture
		    }
		cd_offset /= SECSIZE;
    
		PCD_SEEK(cd_offset + 12);
		readhqt(w, h, 3, fin);
		PCD_SEEK(cd_offset + 14);
		decode(w, h, &Luma, &Chroma1, &Chroma2, 0, fin);
    
		interpolate(&Chroma1);
		interpolate(&Chroma2);
		break;
	    default:
		NXRunAlertPanel(0, " Internal error in pickImage",
		    0, 0, 0);
		fclose(fin);		// perform some clean-up
		fin = 0x0;
		return nil;		// can't process the picture
	    }
#undef nullplane

	}

    colconvert(&w,&h,&Luma,&Chroma1,&Chroma2);
    /* at this point Luma is Red, Chroma1 is Green, Chroma2 is Blue */

    if( turn != T_NONE ) {
	rotateBm(&w, &h, &Luma, &Chroma1, &Chroma2, turn);
	}
	
    bm = [self _allocBitmap :w :h
		red:Luma.im green:Chroma1.im blue:Chroma2.im];

    fclose(fin);
    fin = 0x0;

    /* note that anyone freeing this bitmap will also have to know
       to free the RGB buffers that it's using */
    return bm;
}


/* note that nobody calls this yet, instead there's code that's
   mighty similar to this over in PhotoWinProcs.  Oops.
*/
- freeDataPlanes:(NXBitmapImageRep *)imgBitmap
{
    u_char	*rgb[5];

    if ( [imgBitmap isPlanar] ) {
	/* a rather sleezy way to distinguish between images that
	   were read in and images which were cut/pasted in...  */
	[imgBitmap getDataPlanes:rgb];
	if ( rgb[0] ) free( rgb[0] );
	if ( rgb[1] ) free( rgb[1] );
	if ( rgb[2] ) free( rgb[2] );
	}

    return self;
}


@end
