// pgmfile.cpp: implementation of the Cpgmfile class.
//
//////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include "pgmfile.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

Cpgmfile::Cpgmfile()
{

	m_iWidth = 0;
	m_iHeight = 0;
	m_chMaxGrayVal = 255;
	m_pData = NULL;
}//constructor

Cpgmfile::~Cpgmfile()
{
	if(m_pData != NULL)
		free(m_pData);

}//destructor

int Cpgmfile::getWidth()
{
	return m_iWidth;
}

int Cpgmfile::getHeight()
{
	return m_iHeight;
}

unsigned char Cpgmfile::getMaxGrayVal(){
	return m_chMaxGrayVal;
}

int Cpgmfile::getDataSize()
{
	return (m_iHeight * m_iWidth);
}

//This method will read in the file 
int Cpgmfile::readFile(char *pFilename){
	
	if(pFilename == NULL)
		return -1;

	char magic[3];  
	memset(magic, 0, 3);
	FILE *in = fopen(pFilename, "r");
	if(in == NULL)
		return -1;

	fread(magic, sizeof(char), 2, in);
	fclose(in);

	if(strncmp(magic, PGM_MAGIC_ID_ASCII, 2)==0){
		return readASCIIFile(pFilename);
	}//if
	else if(strncmp(magic, PGM_MAGIC_ID_BIN, 2)==0){
		return readBinaryFile(pFilename);
	}//else if(binary pgm file)
	else{			//This isn't a PGM file I can handle...
		return -1;
	}//else

}//readFile method

int Cpgmfile::readBinaryFile(char *pFilename){

	char seps[] = " \t\r\n";
	char buffer[1000];

	if(pFilename == NULL)
		return -1;

	FILE *in = fopen(pFilename, "rb");
	if(in == NULL)
		return -1;

	//Note: I have made the [stupid] assumption that the header is contained within
	//the first 1000 bytes of the file.  If this is not the case, we will probably have 
	//some additional errors.  I believe that the header can be any size, depending on 
	//comments...so this code won't work for all cases.  Call me lazy...

	memset(buffer, 0, 1000);
	fread(buffer, sizeof(char), 999, in);
	fclose(in);

	removeComments(buffer);

	char *pToken = strtok(buffer, seps);			//Read past P5 magic ID
	if(strncmp(pToken, PGM_MAGIC_ID_BIN, 2) != 0)
		return -1;
	
	pToken = strtok(NULL, seps);
	m_iWidth = atoi(pToken);
	if(m_iWidth < 0)
		m_iWidth = 0;								//Sanity check
	
	pToken = strtok(NULL, seps);
	m_iHeight = atoi(pToken);
	if(m_iHeight < 0)
		m_iHeight = 0;								//Sanity check
		
	pToken = strtok(NULL, seps);
	m_chMaxGrayVal = atoi(pToken);
	if(m_chMaxGrayVal < 0)
		m_chMaxGrayVal = 255;						//Sanity check

	char *pOldTok = pToken+strlen(pToken);
	int iOffset;
	pToken = strtok(NULL, seps);	
	if(pToken == NULL)
		iOffset = pOldTok+1-buffer;
	else
		iOffset = pToken - buffer;			//Find where the data starts

	in = fopen(pFilename, "rb");
	if(in == NULL)
		return -1;

	if(m_pData != NULL)
		free(m_pData);								//Free up mem if necessary

	//Note: There is some error here as well.  I have made a big assumption that
	//the max gray value will always fit within a byte and that each pixel can
	//be contained with a single byte.  If the max gray val is > 255, this code 
	//will not work. 

	m_pData = (unsigned char*)malloc( m_iHeight * m_iWidth);

	fseek(in, iOffset, SEEK_SET);					//Seek to where data starts
	fread(m_pData, sizeof(char), m_iHeight * m_iWidth, in);
	fclose(in);

	return 0;

}//readBinaryFile method

int Cpgmfile::readASCIIFile(char *pFilename){

	char seps[] = " \t\r\n";
	FILE *in;

	if(pFilename == NULL)
		return -1;
	
	struct _finddata_t fileinfo;
	long hFile = _findfirst(pFilename, &fileinfo);
	if(hFile == -1){
		return -1;			//Could not get file info
	}
	_findclose(hFile);
	
	char *pBuff = (char*)malloc( fileinfo.size + 1);
	
	in = fopen(pFilename, "rb");											//Open file
	if(in == NULL)
		return -1;

	size_t rsize = fread(pBuff, sizeof(char), fileinfo.size, in);		//Read it into memory
	if(rsize != fileinfo.size){											//We couldn't read the whole thing....
		free(pBuff);
		fclose(in);
		return -1;
	}

	fclose(in);
	
	removeComments(pBuff);
	char *pToken = NULL;

	pToken = strtok(pBuff, seps);
	if(strcmp(pToken, PGM_MAGIC_ID_ASCII) != 0){			//Not a PGM file
		free(pBuff);
		return -1;
	}//if

	pToken = strtok(NULL, seps);
	m_iWidth = atoi(pToken);
	if(m_iWidth < 0)
		m_iWidth = 0;								//Sanity check
	
	pToken = strtok(NULL, seps);
	m_iHeight = atoi(pToken);
	if(m_iHeight < 0)
		m_iHeight = 0;								//Sanity check
	
	pToken = strtok(NULL, seps);
	m_chMaxGrayVal = atoi(pToken);
	if(m_chMaxGrayVal < 0)
		m_chMaxGrayVal = 255;						//Sanity check

	if(m_pData != NULL)
		free(m_pData);								//Free up mem if necessary

	m_pData = (unsigned char*)malloc( m_iHeight * m_iWidth);
	
	for(int i=0;(i<m_iHeight*m_iWidth) && (pToken!=NULL);i++){
		pToken = strtok(NULL, seps);
		m_pData[i] = atoi(pToken);
	}//for

	free(pBuff);
	
	return 0;
}//readASCIIFile method

//Helper function used to remove comments
void Cpgmfile::removeComments(char *pBuff){
	if(pBuff == NULL)
		return;

	char *pTok;

	pTok = strchr(pBuff, '#');				//find the pound char
	while(pTok != NULL){					//Loop for all comments in file

		while((pTok[0] != '\r') && (pTok[0] != '\n')){
			pTok[0] = ' ';
			pTok++;
		}//while
		pTok = strchr(pTok, '#');
	}//while

}//removeComments method

void Cpgmfile::setData(unsigned char *pData, int iWidth, int iHeight){
	if((pData == NULL) || (iWidth < 0) || (iHeight < 0))
		return;

	if(m_pData != NULL)
		free(m_pData);

	m_pData = (unsigned char*)malloc(iWidth * iHeight);
	memcpy(m_pData, pData, iWidth*iHeight);

	m_iHeight = iHeight;
	m_iWidth = iWidth;

}//setFile

bool Cpgmfile::getData(unsigned char *pData, int iLen){

	if((m_pData == NULL) || (pData == NULL) || (iLen < 0) || (iLen != m_iHeight*m_iWidth))
		return false;

	memcpy(pData, m_pData, iLen);

	return true;
}//getData

int Cpgmfile::writeFile(char *pFilename, bool bASCII /* = false*/){
	if(pFilename == NULL)
		return -1;

	if(m_pData == NULL)
		return -1;

	if(bASCII){
		FILE *out = fopen(pFilename, "wt");
		if(out == NULL)
			return false;

		fprintf(out, "%s\n", PGM_MAGIC_ID_ASCII);
		fprintf(out, "# Created by pgmmod by Jason Plumb\n");
		fprintf(out, "%d %d\n", m_iWidth, m_iHeight);
		fprintf(out, "%d\n", m_chMaxGrayVal);
		int j=1;
		for(int i=0;i<m_iWidth*m_iHeight;i++){		//Loop for all values

			if(j>64){
				fprintf(out, "%d\n", m_pData[i]);
				j = 0;
			}
			else{
				fprintf(out, "%d ", m_pData[i]);
				if( m_pData[i] >= 100)
					j += 4;
				else if(m_pData[i] >= 10)
					j += 3;
				else 
					j+= 2;
			}//else		
		}//for
		fclose(out);
	}
	else{			//binary output
		FILE *out = fopen(pFilename, "wb");		//MUST be opened with 'b' for BINARY
		if(out == NULL)
			return false;
		fprintf(out, "%s\n", PGM_MAGIC_ID_BIN);
		fprintf(out, "# Created by pgmmod by Jason Plumb\n");
		fprintf(out, "%d %d\n", m_iWidth, m_iHeight);
		fprintf(out, "%d\n", m_chMaxGrayVal);
		fwrite(m_pData, sizeof(char), m_iHeight*m_iWidth, out);
		fclose(out);
	}//else

	return true;

}//writeFile

bool Cpgmfile::getData(unsigned char **pData, int iWidth, int iHeight){

	if((m_pData == NULL) || (pData == NULL) || (*pData == NULL) || (iWidth < 0) || (iHeight < 0))
		return false;

	if(iWidth > m_iWidth)
		iWidth = m_iWidth;

	if(iHeight > m_iHeight)
		iHeight = m_iHeight;

	for(int i=0;i<iHeight;i++){
		memcpy(pData[i], &m_pData[ (i*m_iWidth)], iWidth);
	}//for

	
	return true;
}//getData

//This will free up memory for the file currently in memory
void Cpgmfile::unloadFile()
{
	if(m_pData != NULL){
		free(m_pData);
		m_pData = NULL;
	}
	m_iWidth = 0;
	m_iHeight = 0;
}//unloadFile method
