///Alan Abram www.emiug.co.uk
// I apologise for the lack of comments and general shoddyness of this code,
// It was written in a short period of time out of boredom.
// Some of the features are half thought of, I will add them sometime

#include <stdio.h>
#include <assert.h>
#include <string.h>

typedef unsigned char		byte;

// BMP
struct BITMAPFILEHEADER 
{
    unsigned short	bfType;
    unsigned long	bfSize;
    unsigned short	bfReserved1;
    unsigned short	bfReserved2;
    unsigned long	bfOffBits;
};

struct BITMAPINFOHEADER
{
	unsigned long   biSize;
	long			biWidth;
	long			biHeight;
	unsigned short  biPlanes;
	unsigned short  biBitCount;
	unsigned long   biCompression;
	unsigned long   biSizeImage;
	long			biXPelsPerMeter;
	long			biYPelsPerMeter;
	unsigned long   biClrUsed;
	unsigned long   biClrImportant;
};
//End BMP Headers

struct BMPHEADERS
{
	BITMAPFILEHEADER aFileHeader;
	BITMAPINFOHEADER aInfoHeader;
};

struct SplitByte
{
	byte bFirst		: 2;
	byte bSecond	: 2;
	byte bThird		: 2;
	byte bFourth	: 2; //Normally Used for Padding
};

struct ByteBitMask
{
	byte bA : 1;
	byte bB : 1;
	byte bC : 1;
	byte bD : 1;
	byte bE : 1;
	byte bF : 1;
	byte bG : 1;
	byte bH : 1;
};

void EncodeByte(byte * pChar)
{
	byte cChar = *pChar;

	//New Line
	if(cChar == 10 || cChar == 13)
	{
		cChar = 38;	// Ampersand. 
	}

	if( cChar >= 97 && cChar <= 127)
	{
		cChar -= 32; //This effectively makes space char 0;
	}

	cChar -= 32; //This effectively makes space char 0;

	assert(cChar < 64 && "Char code must be less than 64 for this to work");
	*pChar = cChar;
};

void DecodeByte( byte * pChar )
{
	byte cChar = *pChar;

	cChar += 32;

	//New Line
	if(cChar == 38)
	{
		cChar = '\n';
	}

	*pChar = cChar;
}

struct RGB
{
	byte bR;
	byte bG;
	byte bB;
};

struct ImageData
{
	ImageData()
	: bIsBMP(true)
	, pData(0)
	, lDataLength(0)
	, pHeaderData(0)
	{};

	bool	bIsBMP;
	byte *	pData;
	long	lDataLength;
	void *	pHeaderData;
};

int encode( const char * pInputTextFile, ImageData & aInputImageData )
{
	//// Text file to encode
	FILE * pTextFile = 0;
	fopen_s(&pTextFile, pInputTextFile,"rb");

	if(pTextFile == 0)
	{
		printf("Failed to open %s, please ensure the file exists\n", pInputTextFile);
		return -1;
	}

	printf("File %s opened for Encode\n", pInputTextFile );
	
	fseek(pTextFile,0L,SEEK_END);
	long lTextDataSize = ftell(pTextFile);
	rewind(pTextFile);

	byte * pEncodeData = new byte[ lTextDataSize + 1 ];
	fread(pEncodeData, lTextDataSize, 1, pTextFile);
	pEncodeData[lTextDataSize] = '\0';

	fclose(pTextFile);

	for(int iChar = 0; iChar < lTextDataSize; iChar++)
	{
		EncodeByte(&pEncodeData[iChar]);
	}

	BMPHEADERS * pHeader = new BMPHEADERS;

	//Acquire Data
	
	byte * pDataBuff = aInputImageData.pData;

	memcpy(&pHeader->aFileHeader,pDataBuff, sizeof(BITMAPFILEHEADER));
	pDataBuff += 14;

	memcpy(&pHeader->aInfoHeader,pDataBuff, sizeof(BITMAPINFOHEADER));
	pDataBuff += sizeof(BITMAPINFOHEADER);
		
	aInputImageData.pHeaderData = pHeader;

	int iNumElemsRemaining = pHeader->aInfoHeader.biWidth * pHeader->aInfoHeader.biHeight;
	int iTextCharsRemaining = lTextDataSize;

	assert(iTextCharsRemaining < iNumElemsRemaining);

	RGB * pRGBData = (RGB *)pDataBuff;
	SplitByte * pTextData = (SplitByte *)pEncodeData;

	while( 	iNumElemsRemaining > 0 
	&&		iTextCharsRemaining > 0)
	{
		byte bR = pTextData->bFirst;		
		ByteBitMask * pBR = (ByteBitMask *) &pRGBData->bR;
		ByteBitMask * pBRe = (ByteBitMask *) &bR;
		pBR->bA = pBRe->bA;
		pBR->bB = pBRe->bB;

		byte bG = pTextData->bSecond;		
		ByteBitMask * pbG = (ByteBitMask *) &pRGBData->bG;
		ByteBitMask * pbGe = (ByteBitMask *) &bG;
		pbG->bA = pbGe->bA;
		pbG->bB = pbGe->bB;
		
		byte bB = pTextData->bThird;		
		ByteBitMask * pbB = (ByteBitMask *) &pRGBData->bB;
		ByteBitMask * pbBe = (ByteBitMask *) &bB;
		pbB->bA = pbBe->bA;
		pbB->bB = pbBe->bB;

		pRGBData++;
		pTextData++;
		iTextCharsRemaining--;
		iNumElemsRemaining--;
	}
	
	while( 0 &&	iNumElemsRemaining > 0 )
	{
		pRGBData->bR &= 11111100;
		pRGBData->bR |= ' ';
		
		pRGBData->bG &= 11111100;
		pRGBData->bG |= ' ';
		
		pRGBData->bB &= 11111100;
		pRGBData->bB |= ' ';

		pRGBData++;
		iNumElemsRemaining--;
	}

	FILE * pFileOut = 0;

	fopen_s(&pFileOut,"encode_out.bmp","wb");

	assert(pFileOut);

	fwrite(aInputImageData.pData,aInputImageData.lDataLength,1,pFileOut);

	fclose(pFileOut);
	return 0;
}

int decode( ImageData & aInputImageData )
{
	BMPHEADERS * pHeader = new BMPHEADERS;

	//Acquire Data
	
	byte * pDataBuff = aInputImageData.pData;

	memcpy(&pHeader->aFileHeader,pDataBuff, sizeof(BITMAPFILEHEADER));
	pDataBuff += 14;

	memcpy(&pHeader->aInfoHeader,pDataBuff, sizeof(BITMAPINFOHEADER));
	pDataBuff += sizeof(BITMAPINFOHEADER);
		
	aInputImageData.pHeaderData = pHeader;

	int iNumElemsRemaining = pHeader->aInfoHeader.biWidth * pHeader->aInfoHeader.biHeight;

	RGB * pRGBData = (RGB *)pDataBuff;
	
	SplitByte aTmp;
	aTmp.bFourth = 0;

	char * pText = new char[ iNumElemsRemaining ];

	char * pTextData = pText;

	while(	iNumElemsRemaining > 0 )
	{
		byte bChar;
		ByteBitMask * pRecompiled = (ByteBitMask *) &bChar;

		ByteBitMask * pRMask = (ByteBitMask *) &pRGBData->bR;
		ByteBitMask * pGMask = (ByteBitMask *) &pRGBData->bG;
		ByteBitMask * pBMask = (ByteBitMask *) &pRGBData->bB;

		pRecompiled->bA = pRMask->bA;
		pRecompiled->bB = pRMask->bB;
		pRecompiled->bC = pGMask->bA;
		pRecompiled->bD = pGMask->bB;
		pRecompiled->bE = pBMask->bA;
		pRecompiled->bF = pBMask->bB;
		pRecompiled->bG = 0;
		pRecompiled->bH = 0;
		
		DecodeByte(&bChar);
		*pTextData = bChar;

		pRGBData++;
		pTextData++;
		iNumElemsRemaining--;
	}

	FILE * pOut = 0;
	fopen_s(&pOut,"decode.txt","wb");

	if(pOut)
	{
		fwrite(pText,pHeader->aInfoHeader.biWidth * pHeader->aInfoHeader.biHeight,1,pOut);
		fclose(pOut);
	}

	return 0;
};

int main( int argc, const char* argv[])
{
	//Arguments are as follows:

	const char * pCommand	= argv[1];
	const char * pFilepath	= argv[2];
	const char * pInputText	= argv[3];

	if(_stricmp(pCommand,"-encode") != 0
	&& _stricmp(pCommand,"-decode") != 0	)
	{
		printf("Invalid switch, please use -encode or -decode\n");
		return -1;
	}

	bool bEncode = (_stricmp(pCommand,"-encode") == 0);

	//// Image file to encode
	FILE * pFile = 0;
	fopen_s(&pFile, pFilepath,"rb");

	if(pFile == 0)
	{
		printf("Failed to open %s, please ensure the file exists\n", pFilepath);
		return -1;
	}

	printf("File %s opened for Encode\n", pFilepath );
	
	fseek(pFile,0L,SEEK_END);
	long lImageDataSize = ftell(pFile);
	rewind(pFile);

	byte * pImageData = new byte[ lImageDataSize ];
	fread(pImageData,lImageDataSize,1,pFile);
	fclose(pFile);

	int iFilenameLen = strlen(pFilepath);
	const char * pImageFileExt = pFilepath + iFilenameLen - 3;
	
	ImageData aData;
	aData.bIsBMP = _stricmp(pImageFileExt,"BMP") == 0;
	aData.lDataLength = lImageDataSize;
	aData.pData = pImageData;

	if(bEncode)
		return encode(pInputText,aData);
	else
		return decode(aData);
	
	return -1;
};
