/*
 *  guiKit.c
 *  GUIKit
 *
 *  Created by Colin Cornaby on 3/29/07.
 *  Copyright 2007 White Magic Labs. All rights reserved.
 *
 */
 
/*	
 *	This library is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *	
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *	
 *	You should have received a copy of the GNU Lesser General Public
 *	License along with this library; if not, write to the Free Software
 *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "guiKitLib.h"
#include "zlib.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>

#pragma mark Internal Methods
#pragma mark Decompress Data

CFDataRef copyDataForCompressedBytes(void *data, int length)
{
    CFDataRef	dataToReturn	= nil;
    
    const void	*cBuffer 	= data;
    unsigned	cLen		= length;
    Bytef	*uBuffer	= NULL;
    uLongf 	uLen		= cLen;
    int 	err;
    
    do {
	uLen *= 2;		// Double the buffer size each iteration through the loop
	
	if (uBuffer = malloc(uLen)) {
	    err = uncompress(uBuffer, &uLen, cBuffer, cLen);
	    
	    if (err == Z_OK)
		dataToReturn = CFDataCreate(NULL,uBuffer,uLen);
		
	    if (err == Z_MEM_ERROR || err == Z_DATA_ERROR)
		dataToReturn = nil;
	    
	    free(uBuffer);
	} else {
	    return nil;
	}
    } while (err == Z_BUF_ERROR);	// If the buffer wasn't big enough this time, repeat the loop
    
    return (CFDataRef)dataToReturn;   
}

CFStringRef copyStringFromCompressedData(void * ptr, int length)
{
	CFDataRef decompressedThemeName = copyDataForCompressedBytes(ptr+8,length-8);
	CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, CFDataGetBytePtr (decompressedThemeName), CFDataGetLength(decompressedThemeName), kCFStringEncodingUnicode, TRUE);
	CFRelease(decompressedThemeName);
	return (CFStringRef)string;
}

CFAttributedStringRef copyAttributedStringFromCompressData(void * ptr, int length)
{
	CFDataRef decompressedString = copyDataForCompressedBytes(ptr+8,length-8);
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	CFAttributedStringRef attributedString = (CFAttributedStringRef) [[NSAttributedString alloc] initWithData:(NSData *)decompressedString options:[NSDictionary dictionaryWithObject:NSRTFDTextDocumentType forKey:NSDocumentTypeDocumentOption] documentAttributes:nil error:nil];
	[pool release];
	CFRelease(decompressedString);
	return (CFAttributedStringRef)attributedString;
}

#pragma mark Attribute Metadata

SInt32 nameForAttribute(gkAttribRef ptr)
{
	SInt32 *name = ptr;
	return *name;
}

SInt32 lengthForAttribute(gkAttribRef ptr)
{
	SInt32 *length = ptr+4;
	return CFSwapInt32BigToHost((SInt32)*length);
}

#pragma mark Attribute Discovery

int countForAttribType(SInt32 type, gkAttribRef ptr, int length)
{
	int currentOffset = 8;
	int count = 0;
	while(length>currentOffset)
	{
		SInt32 attributeName = nameForAttribute(ptr+currentOffset);
		SInt32 attributeLength = lengthForAttribute(ptr+currentOffset);
		if(type==attributeName)
		{
			count++;
		}
		currentOffset += attributeLength;
	}
	return count;
}

void * ptrForAttribute(SInt32 type, gkAttribRef ptr, int length, int index)
{
	int currentOffset = 8;
	int currentIndex=0;
	while(length>currentOffset)
	{
		SInt32 attributeName = nameForAttribute(ptr+currentOffset);
		SInt32 attributeLength = lengthForAttribute(ptr+currentOffset);
		if(type==attributeName)
		{
			if(currentIndex==index)
				return (void *)(ptr+currentOffset);
			currentIndex++;
		}
		currentOffset += attributeLength;
	}
	return NULL;
}

#pragma mark Public Methods
#pragma mark Fetching attribute information

CFDataRef GKCopyImagePreviewOfAttribute(gkAttribRef gkRef)
{
	void *pictureAttribRef = ptrForAttribute(*((SInt32 *)(void *)"PRVW"),gkRef,lengthForAttribute(gkRef), 0);
	if(pictureAttribRef==NULL)
		return NULL;
	void *pictureDataRef = ptrForAttribute(*((SInt32 *)(void *)"DATA"),pictureAttribRef,lengthForAttribute(pictureAttribRef), 0);
	if(pictureDataRef==NULL)
		return NULL;
	return copyDataForCompressedBytes(pictureDataRef+8,lengthForAttribute(pictureDataRef)-8);
}

CFStringRef GKCopyNameOfAttribute(gkAttribRef gkRef)
{
	void *nameAttribRef = ptrForAttribute(*((SInt32 *)(void *)"NAME"),gkRef,lengthForAttribute(gkRef), 0);
	return copyStringFromCompressedData(nameAttribRef,lengthForAttribute(nameAttribRef));
}

CFAttributedStringRef GKCopyDescriptionOfAttribute(gkAttribRef gkRef)
{
	void *descAttribRef = ptrForAttribute(*((SInt32 *)(void *)"DESC"),gkRef,lengthForAttribute(gkRef), 0);
	if(descAttribRef!=NULL)
	{
		void *rtfdAttribRef = ptrForAttribute(*((SInt32 *)(void *)"RTFD"),descAttribRef,lengthForAttribute(descAttribRef), 0);
		if(rtfdAttribRef!=NULL)
			return copyAttributedStringFromCompressData(rtfdAttribRef,lengthForAttribute(rtfdAttribRef));
		void *unicAttribRef = ptrForAttribute(*((SInt32 *)(void *)"UNIC"),descAttribRef,lengthForAttribute(descAttribRef), 0);
		if(unicAttribRef!=NULL)
		{
			CFStringRef descriptionAsString = copyStringFromCompressedData(unicAttribRef,lengthForAttribute(unicAttribRef));
			CFAttributedStringRef descriptionAsRichString = CFAttributedStringCreate(NULL,descriptionAsString,NULL);
			CFRelease(descriptionAsString);
			return descriptionAsRichString;
		}
	}
	return nil;
}

#pragma mark Variation Methods

int GKThemeVariationCount(guiKitRef gkRef)
{
	gkAttribRef themeRef = ptrForAttribute(*((SInt32 *)(void *)"THME"),(gkRef->buffer)+20,(gkRef->length)-20,0);
	return countForAttribType(*((SInt32 *)(void *)"TVAR"),themeRef,lengthForAttribute(themeRef));
}

gkVariationRef GKThemeVariationAtIndex(int index, guiKitRef  gkRef)
{
	gkAttribRef themeRef = ptrForAttribute(*((SInt32 *)(void *)"THME"),(gkRef->buffer)+20,(gkRef->length)-20,0);
	return ptrForAttribute(*((SInt32 *)(void *)"TVAR"),themeRef,lengthForAttribute(themeRef), index);
}

int GKPatchCountForVariation(gkAttribRef gkRef)
{
	gkAttribRef patchesRef = ptrForAttribute(*((SInt32 *)(void *)"PTCS"),gkRef,lengthForAttribute(gkRef),0);
	return countForAttribType(*((SInt32 *)(void *)"PTCH"),patchesRef,lengthForAttribute(patchesRef));
}

gkPatchRef GKPatchForVariationAtIndex(int index, gkAttribRef gkRef)
{
	gkAttribRef patchesRef = ptrForAttribute(*((SInt32 *)(void *)"PTCS"),gkRef,lengthForAttribute(gkRef),0);
	return ptrForAttribute(*((SInt32 *)(void *)"PTCH"),patchesRef,lengthForAttribute(patchesRef), index);
}

#pragma mark "Patch" Methods

CFStringRef GKCopyPathOfPatch(gkAttribRef gkRef)
{
	void *patchAttribRef = ptrForAttribute(*((SInt32 *)(void *)"PATH"),gkRef,lengthForAttribute(gkRef), 0);
	return copyStringFromCompressedData(patchAttribRef,lengthForAttribute(patchAttribRef));
}

CFDataRef GKCopyFileOfPatch(gkAttribRef gkRef)
{
	void *fileAttribRef = ptrForAttribute(*((SInt32 *)(void *)"FILE"),gkRef,lengthForAttribute(gkRef), 0);
	//void *fileDataAttribRef = ptrForAttribute(*((SInt32 *)(void *)"DDAT"),fileAttribRef,lengthForAttribute(fileAttribRef), 0);
	return copyDataForCompressedBytes(fileAttribRef+16,lengthForAttribute(fileAttribRef)-16);
}

CFStringRef GKCopyApplicationIDOfPatch(gkAttribRef gkRef)
{
	void *apidAttribRef = ptrForAttribute(*((SInt32 *)(void *)"APID"),gkRef,lengthForAttribute(gkRef), 0);
	if(apidAttribRef==NULL)
		return NULL;
	return copyStringFromCompressedData(apidAttribRef,lengthForAttribute(apidAttribRef));
}

#pragma mark Checksum

int GKDoCheckSum(guiKitRef  gkRef)
{
	uLong checksum = (uLong)*((uLong *)(gkRef->buffer+8));
	checksum = NSSwapBigLongToHost(checksum);
	uLong adler = adler32(0L, Z_NULL, 0);
	adler = adler32(adler, gkRef->buffer+12, gkRef->length-12);
	if(adler!=checksum)
		return 0;
	return 1;
}