#ifndef DMG_H
#define DMG_H

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

#include <hfs/hfsplus.h>
#include "abstractfile.h"

#define CHECKSUM_CRC32 0x00000002
#define CHECKSUM_MKBLOCK 0x0002
#define CHECKSUM_NONE 0x0000

#define BLOCK_ZLIB 0x80000005
#define BLOCK_RAW 0x00000001
#define BLOCK_IGNORE 0x00000002
#define BLOCK_COMMENT 0x7FFFFFFE
#define BLOCK_TERMINATOR 0xFFFFFFFF

#define SECTOR_SIZE 512

#define DRIVER_DESCRIPTOR_SIGNATURE 0x4552
#define APPLE_PARTITION_MAP_SIGNATURE 0x504D
#define UDIF_BLOCK_SIGNATURE 0x6D697368
#define KOLY_SIGNATURE 0x6B6F6C79
#define HFSX_SIGNATURE 0x4858

#define ATTRIBUTE_HDIUTIL 0x0050

#define HFSX_VOLUME_TYPE "Apple_HFSX"

#define DDM_SIZE 0x1
#define PARTITION_SIZE 0x3f
#define ATAPI_SIZE 0x8
#define FREE_SIZE 0xa
#define EXTRA_SIZE (ATAPI_OFFSET + ATAPI_SIZE + FREE_SIZE)

#define DDM_OFFSET 0x0
#define PARTITION_OFFSET (DDM_SIZE)
#define ATAPI_OFFSET 64
#define USER_OFFSET (ATAPI_OFFSET + ATAPI_SIZE)

#define BOOTCODE_DMMY 0x444D4D59
#define BOOTCODE_GOON 0x676F6F6E

enum {
	kUDIFFlagsFlattened = 1
};

enum {
	kUDIFDeviceImageType = 1,
	kUDIFPartitionImageType = 2
};

typedef struct {
	uint32_t type;
	uint32_t size;
	uint32_t data[0x20];
} __attribute__((__packed__)) UDIFChecksum;

typedef struct {
	uint32_t data1; /* smallest */
	uint32_t data2;
	uint32_t data3;
	uint32_t data4; /* largest */
} __attribute__((__packed__)) UDIFID;

typedef struct {
	uint32_t fUDIFSignature;
	uint32_t fUDIFVersion;
	uint32_t fUDIFHeaderSize;
	uint32_t fUDIFFlags;
	
	uint64_t fUDIFRunningDataForkOffset;
	uint64_t fUDIFDataForkOffset;
	uint64_t fUDIFDataForkLength;
	uint64_t fUDIFRsrcForkOffset;
	uint64_t fUDIFRsrcForkLength;
	
	uint32_t fUDIFSegmentNumber;
	uint32_t fUDIFSegmentCount;
	UDIFID fUDIFSegmentID;  /* a 128-bit number like a GUID, but does not seem to be a OSF GUID, since it doesn't have the proper versioning byte */
	
	UDIFChecksum fUDIFDataForkChecksum;
	
	uint64_t fUDIFXMLOffset;
	uint64_t fUDIFXMLLength;
	
	uint8_t reserved1[0x78]; /* this is actually the perfect amount of space to store every thing in this struct until the checksum */
	
	UDIFChecksum fUDIFMasterChecksum;
	
	uint32_t fUDIFImageVariant;
	uint64_t fUDIFSectorCount;
	
	uint32_t reserved2;
	uint32_t reserved3;
	uint32_t reserved4;
	
} __attribute__((__packed__)) UDIFResourceFile;

typedef struct {
	uint32_t type;
	uint32_t reserved;
	uint64_t sectorStart;
	uint64_t sectorCount;
	uint64_t compOffset;
	uint64_t compLength;
} __attribute__((__packed__)) BLKXRun;

typedef struct {
	uint16_t version; /* set to 5 */
	uint32_t isHFS; /* first dword of v53(ImageInfoRec): Set to 1 if it's a HFS or HFS+ partition -- duh. */
	uint32_t unknown1; /* second dword of v53: seems to be garbage if it's HFS+, stuff related to HFS embedded if it's that*/
	uint8_t dataLen; /* length of data that proceeds, comes right before the data in ImageInfoRec. Always set to 0 for HFS, HFS+ */
	uint8_t data[255]; /* other data from v53, dataLen + 1 bytes, the rest NULL filled... a string? Not set for HFS, HFS+ */
	uint32_t unknown2; /* 8 bytes before volumeModified in v53, seems to be always set to 0 for HFS, HFS+  */
	uint32_t unknown3; /* 4 bytes before volumeModified in v53, seems to be always set to 0 for HFS, HFS+ */
	uint32_t volumeModified; /* offset 272 in v53 */
	uint32_t unknown4; /* always seems to be 0 for UDIF */
	uint16_t volumeSignature; /* HX in our case */
	uint16_t sizePresent; /* always set to 1 */
} __attribute__((__packed__)) SizeResource;

typedef struct {
	uint16_t version; /* set to 1 */
	uint32_t type; /* set to 0x2 for MKBlockChecksum */
	uint32_t checksum;
} __attribute__((__packed__)) CSumResource;

typedef struct NSizResource {
	char isVolume;
	unsigned char* sha1Digest;
	uint32_t blockChecksum2;
	uint32_t bytes;
	uint32_t modifyDate;
	uint32_t partitionNumber;
	uint32_t version;
	uint32_t volumeSignature;
	struct NSizResource* next;
} NSizResource;

#define DDM_DESCRIPTOR 0xFFFFFFFF
#define ENTIRE_DEVICE_DESCRIPTOR 0xFFFFFFFE

typedef struct {
	uint32_t fUDIFBlocksSignature;
	uint32_t infoVersion;
	uint64_t firstSectorNumber;
	uint64_t sectorCount;
	
	uint64_t dataStart;
	uint32_t decompressBufferRequested;
	uint32_t blocksDescriptor;
	
	uint32_t reserved1;
	uint32_t reserved2;
	uint32_t reserved3;
	uint32_t reserved4;
	uint32_t reserved5;
	uint32_t reserved6;
	
	UDIFChecksum checksum;
	
	uint32_t blocksRunCount;
	BLKXRun runs[0];
} __attribute__((__packed__)) BLKXTable;

typedef struct {
	uint32_t ddBlock;
	uint16_t ddSize;
	uint16_t ddType;
} __attribute__((__packed__)) DriverDescriptor;

typedef struct {
	uint16_t pmSig;
	uint16_t pmSigPad;
	uint32_t pmMapBlkCnt;
	uint32_t pmPyPartStart;
	uint32_t pmPartBlkCnt;
	unsigned char pmPartName[32];
	unsigned char pmParType[32];
	uint32_t pmLgDataStart;
	uint32_t pmDataCnt;
	uint32_t pmPartStatus;
	uint32_t pmLgBootStart;
	uint32_t pmBootSize;
	uint32_t pmBootAddr;
	uint32_t pmBootAddr2;
	uint32_t pmBootEntry;
	uint32_t pmBootEntry2;
	uint32_t pmBootCksum;
	unsigned char pmProcessor[16];
	uint32_t bootCode;
	uint16_t pmPad[186];
} __attribute__((__packed__)) Partition;

typedef struct {
	uint16_t sbSig;
	uint16_t sbBlkSize;
	uint32_t sbBlkCount;
	uint16_t sbDevType;
	uint16_t sbDevId;
	uint32_t sbData;
	uint16_t sbDrvrCount;
	uint32_t ddBlock;
	uint16_t ddSize;
	uint16_t ddType;
	DriverDescriptor ddPad[0];
} __attribute__((__packed__)) DriverDescriptorRecord;

typedef struct ResourceData {
	uint32_t attributes;
	unsigned char* data;
	size_t dataLength;
	int id;
	char* name;
	struct ResourceData* next;
} ResourceData;

typedef void (*FlipDataFunc)(unsigned char* data, char out);
typedef void (*ChecksumFunc)(void* ckSum, const unsigned char* data, size_t len);

typedef struct ResourceKey {
	unsigned char* key;
	ResourceData* data;
	struct ResourceKey* next;
	FlipDataFunc flipData;
} ResourceKey;

#define SHA1_DIGEST_SIZE 20

typedef struct {
	uint32_t state[5];
	uint32_t count[2];
	uint8_t  buffer[64];
} SHA1_CTX;

typedef struct {
	uint32_t block;
	uint32_t crc;
	SHA1_CTX sha1;
} ChecksumToken;

static inline uint32_t readUInt32(AbstractFile* file) {
	uint32_t data;
	
	ASSERT(file->read(file, &data, sizeof(data)) == sizeof(data), "fread");
	FLIPENDIAN(data);
	
	return data;
}

static inline void writeUInt32(AbstractFile* file, uint32_t data) {
	FLIPENDIAN(data);
	ASSERT(file->write(file, &data, sizeof(data)) == sizeof(data), "fwrite");
}

static inline uint32_t readUInt64(AbstractFile* file) {
	uint64_t data;
	
	ASSERT(file->read(file, &data, sizeof(data)) == sizeof(data), "fread");
	FLIPENDIAN(data);
	
	return data;
}

static inline void writeUInt64(AbstractFile* file, uint64_t data) {
	FLIPENDIAN(data);
	ASSERT(file->write(file, &data, sizeof(data)) == sizeof(data), "fwrite");
}

#ifdef __cplusplus
extern "C" {
#endif
	void outResources(AbstractFile* file, AbstractFile* out);

	uint32_t CRC32Checksum(uint32_t* crc, const unsigned char *buf, size_t len);
	uint32_t MKBlockChecksum(uint32_t* ckSum, const unsigned char* data, size_t len);

	void BlockSHA1CRC(void* token, const unsigned char* data, size_t len);
	void BlockCRC(void* token, const unsigned char* data, size_t len);
	void CRCProxy(void* token, const unsigned char* data, size_t len);

	void SHA1Init(SHA1_CTX* context);
	void SHA1Update(SHA1_CTX* context, const uint8_t* data, const size_t len);
	void SHA1Final(uint8_t digest[SHA1_DIGEST_SIZE], SHA1_CTX* context);

	void flipUDIFChecksum(UDIFChecksum* o, char out);
	void readUDIFChecksum(AbstractFile* file, UDIFChecksum* o);
	void writeUDIFChecksum(AbstractFile* file, UDIFChecksum* o);
	void readUDIFID(AbstractFile* file, UDIFID* o);
	void writeUDIFID(AbstractFile* file, UDIFID* o);
	void readUDIFResourceFile(AbstractFile* file, UDIFResourceFile* o);
	void writeUDIFResourceFile(AbstractFile* file, UDIFResourceFile* o);

	ResourceKey* readResources(AbstractFile* file, UDIFResourceFile* resourceFile);
	void writeResources(AbstractFile* file, ResourceKey* resources);
	void releaseResources(ResourceKey* resources);

	NSizResource* readNSiz(ResourceKey* resources);
	ResourceKey* writeNSiz(NSizResource* nSiz);
	void releaseNSiz(NSizResource* nSiz);

	extern const char* plistHeader;
	extern const char* plistFooter;

	ResourceKey* getResourceByKey(ResourceKey* resources, const char* key);
	ResourceData* getDataByID(ResourceKey* resource, int id);
	ResourceKey* insertData(ResourceKey* resources, const char* key, int id, const char* name, const char* data, size_t dataLength, uint32_t attributes);
	ResourceKey* makePlst();
	ResourceKey* makeSize(HFSPlusVolumeHeader* volumeHeader);

	void flipDriverDescriptorRecord(DriverDescriptorRecord* record, char out);
	void flipPartition(Partition* partition, char out, unsigned int BlockSize);
	void flipPartitionMultiple(Partition* partition, char multiple, char out, unsigned int BlockSize);

	void readDriverDescriptorMap(AbstractFile* file, ResourceKey* resources);
	DriverDescriptorRecord* createDriverDescriptorMap(uint32_t numSectors, unsigned int BlockSize);
	int writeDriverDescriptorMap(int pNum, AbstractFile* file, DriverDescriptorRecord* DDM, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources);
	void readApplePartitionMap(AbstractFile* file, ResourceKey* resources, unsigned int BlockSize);
	Partition* createApplePartitionMap(uint32_t numSectors, const char* volumeType, unsigned int BlockSize);
	int writeApplePartitionMap(int pNum, AbstractFile* file, Partition* partitions, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn);
	int writeATAPI(int pNum, AbstractFile* file, unsigned int BlockSize, ChecksumFunc dataForkChecksum, void* dataForkToken, ResourceKey **resources, NSizResource** nsizIn);
	int writeFreePartition(int pNum, AbstractFile* outFile, uint32_t offset, uint32_t numSectors, ResourceKey** resources);

	void extractBLKX(AbstractFile* in, AbstractFile* out, BLKXTable* blkx);
	BLKXTable* insertBLKX(AbstractFile* out, AbstractFile* in, uint32_t firstSectorNumber, uint32_t numSectors, uint32_t blocksDescriptor,
				uint32_t checksumType, ChecksumFunc uncompressedChk, void* uncompressedChkToken, ChecksumFunc compressedChk,
				void* compressedChkToken, Volume* volume, int addComment);


	int extractDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, int partNum);
	int buildDmg(AbstractFile* abstractIn, AbstractFile* abstractOut, unsigned int BlockSize);
	int convertToISO(AbstractFile* abstractIn, AbstractFile* abstractOut);
	int convertToDMG(AbstractFile* abstractIn, AbstractFile* abstractOut);
#ifdef __cplusplus
}
#endif

#endif
