#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <hfs/hfsplus.h>

static inline void flipBSDInfo(HFSPlusBSDInfo* info) {
  FLIPENDIAN(info->ownerID);
  FLIPENDIAN(info->groupID);
  FLIPENDIAN(info->fileMode);
  FLIPENDIAN(info->special);
}

static inline void flipPoint(Point* point) {
  FLIPENDIAN(point->v);
  FLIPENDIAN(point->h);
}

static inline void flipRect(Rect* rect) {
  FLIPENDIAN(rect->top);
  FLIPENDIAN(rect->left);
  FLIPENDIAN(rect->bottom);
  FLIPENDIAN(rect->right);
}

static inline void flipFolderInfo(FolderInfo* info) {
  flipRect(&info->windowBounds);
  FLIPENDIAN(info->finderFlags);
  flipPoint(&info->location);
}

static inline void flipExtendedFolderInfo(ExtendedFolderInfo* info) {
  flipPoint(&info->scrollPosition);
  FLIPENDIAN(info->extendedFinderFlags);
  FLIPENDIAN(info->putAwayFolderID);
}

static inline void flipFileInfo(FileInfo* info) {
  FLIPENDIAN(info->fileType);
  FLIPENDIAN(info->fileCreator);
  FLIPENDIAN(info->finderFlags);
  flipPoint(&info->location);
}

static inline void flipExtendedFileInfo(ExtendedFileInfo* info) {
  FLIPENDIAN(info->extendedFinderFlags);
  FLIPENDIAN(info->putAwayFolderID);
}

void flipCatalogFolder(HFSPlusCatalogFolder* record) {
  FLIPENDIAN(record->recordType);
  FLIPENDIAN(record->flags);
  FLIPENDIAN(record->valence);
  FLIPENDIAN(record->folderID);
  FLIPENDIAN(record->createDate);
  FLIPENDIAN(record->contentModDate);
  FLIPENDIAN(record->attributeModDate);
  FLIPENDIAN(record->accessDate);
  FLIPENDIAN(record->backupDate);
  
  flipBSDInfo(&record->permissions);
  flipFolderInfo(&record->userInfo);
  flipExtendedFolderInfo(&record->finderInfo);
  
  FLIPENDIAN(record->textEncoding);
  FLIPENDIAN(record->folderCount);
}

void flipCatalogFile(HFSPlusCatalogFile* record) {
  FLIPENDIAN(record->recordType);
  FLIPENDIAN(record->flags);
  FLIPENDIAN(record->fileID);
  FLIPENDIAN(record->createDate);
  FLIPENDIAN(record->contentModDate);
  FLIPENDIAN(record->attributeModDate);
  FLIPENDIAN(record->accessDate);
  FLIPENDIAN(record->backupDate);
  
  flipBSDInfo(&record->permissions);
  flipFileInfo(&record->userInfo);
  flipExtendedFileInfo(&record->finderInfo);
  
  FLIPENDIAN(record->textEncoding);
  
  flipForkData(&record->dataFork);
  flipForkData(&record->resourceFork);
}

void flipCatalogThread(HFSPlusCatalogThread* record, int out) {
  int i;
  int nameLength;
  
  FLIPENDIAN(record->recordType);
  FLIPENDIAN(record->parentID);
  if(out) {
    nameLength = record->nodeName.length;
    FLIPENDIAN(record->nodeName.length);
  } else {
    FLIPENDIAN(record->nodeName.length);
    nameLength = record->nodeName.length;
  }
  
  for(i = 0; i < nameLength; i++) {
    if(out) {
	  if(record->nodeName.unicode[i] == ':') {
	    record->nodeName.unicode[i] = '/';
	  }
	  FLIPENDIAN(record->nodeName.unicode[i]);
	} else {	
	  FLIPENDIAN(record->nodeName.unicode[i]);
	  if(record->nodeName.unicode[i] == '/') {
	    record->nodeName.unicode[i] = ':';
	  }
	}
  }
}

#define UNICODE_START (sizeof(uint16_t) + sizeof(HFSCatalogNodeID) + sizeof(uint16_t))

static void catalogKeyPrint(BTKey* toPrint) {
  HFSPlusCatalogKey* key;
  
  key = (HFSPlusCatalogKey*) toPrint;
  
  printf("%d:", key->parentID);
  printUnicode(&key->nodeName);
}

static int catalogCompare(BTKey* vLeft, BTKey* vRight) {
  HFSPlusCatalogKey* left;
  HFSPlusCatalogKey* right;
  uint16_t i;

  uint16_t cLeft;
  uint16_t cRight;
  
  left = (HFSPlusCatalogKey*) vLeft;
  right =(HFSPlusCatalogKey*) vRight;
   
  if(left->parentID < right->parentID) {
    return -1;
  } else if(left->parentID > right->parentID) {
    return 1;
  } else {
    for(i = 0; i < left->nodeName.length; i++) {
      if(i >= right->nodeName.length) {
        return 1;
      } else {
		/* ugly hack to support weird : to / conversion on iPhone */
	    if(left->nodeName.unicode[i] == ':') {
			cLeft = '/';
		} else {
			cLeft = left->nodeName.unicode[i] ;
		}
		
		if(right->nodeName.unicode[i] == ':') {
			cRight = '/';
		} else {
			cRight = right->nodeName.unicode[i];
		}
		
        if(cLeft < cRight)
          return -1;
        else if(cLeft > cRight)
          return 1;
      }
    }
    
    if(i < right->nodeName.length) {
      return -1;
    } else {
      /* do a safety check on key length. Otherwise, bad things may happen later on when we try to add or remove with this key */
      /*if(left->keyLength == right->keyLength) {
        return 0;
      } else if(left->keyLength < right->keyLength) {
        return -1;
      } else {
        return 1;
      }*/
      return 0;
    }
  }
}

static int catalogCompareCS(BTKey* vLeft, BTKey* vRight) {
  HFSPlusCatalogKey* left;
  HFSPlusCatalogKey* right;
  uint16_t i;

  uint16_t cLeft;
  uint16_t cRight;
  
  left = (HFSPlusCatalogKey*) vLeft;
  right =(HFSPlusCatalogKey*) vRight;
   
  if(left->parentID < right->parentID) {
    return -1;
  } else if(left->parentID > right->parentID) {
    return 1;
  } else {
    return FastUnicodeCompare(left->nodeName.unicode, left->nodeName.length, right->nodeName.unicode, right->nodeName.length);
  }
}

static BTKey* catalogKeyRead(off_t offset, io_func* io) {
  HFSPlusCatalogKey* key;
  uint16_t i;
  
  key = (HFSPlusCatalogKey*) malloc(sizeof(HFSPlusCatalogKey));
  
  if(!READ(io, offset, UNICODE_START, key))
    return NULL;
  
  FLIPENDIAN(key->keyLength);
  FLIPENDIAN(key->parentID);
  FLIPENDIAN(key->nodeName.length);
   
  if(!READ(io, offset + UNICODE_START, key->nodeName.length * sizeof(uint16_t), ((unsigned char *)key) + UNICODE_START))
    return NULL;
    
  for(i = 0; i < key->nodeName.length; i++) {
    FLIPENDIAN(key->nodeName.unicode[i]);
	if(key->nodeName.unicode[i] == '/') /* ugly hack that iPhone seems to do */
		key->nodeName.unicode[i] = ':';
  }
  
  return (BTKey*)key;
}

static int catalogKeyWrite(off_t offset, BTKey* toWrite, io_func* io) {
  HFSPlusCatalogKey* key;
  uint16_t i;
  uint16_t keyLength;
  uint16_t nodeNameLength;
  
  keyLength = toWrite->keyLength + sizeof(uint16_t);
  key = (HFSPlusCatalogKey*) malloc(keyLength);
  memcpy(key, toWrite, keyLength);
  
  nodeNameLength = key->nodeName.length;
  
  FLIPENDIAN(key->keyLength);
  FLIPENDIAN(key->parentID);
  FLIPENDIAN(key->nodeName.length);
  
  for(i = 0; i < nodeNameLength; i++) {
	if(key->nodeName.unicode[i] == ':') /* ugly hack that iPhone seems to do */
		key->nodeName.unicode[i] = '/';

    FLIPENDIAN(key->nodeName.unicode[i]);
  }
 
  if(!WRITE(io, offset, keyLength, key))
    return FALSE;
  
  free(key);
    
  return TRUE;
}

static BTKey* catalogDataRead(off_t offset, io_func* io) {
  int16_t recordType;
  HFSPlusCatalogRecord* record;
  uint16_t nameLength;
  
  if(!READ(io, offset, sizeof(int16_t), &recordType))
    return NULL;
    
  FLIPENDIAN(recordType); fflush(stdout);

  switch(recordType) {
    case kHFSPlusFolderRecord:
      record = (HFSPlusCatalogRecord*) malloc(sizeof(HFSPlusCatalogFolder));
      if(!READ(io, offset, sizeof(HFSPlusCatalogFolder), record))
        return NULL;
      flipCatalogFolder((HFSPlusCatalogFolder*)record);
      break;
      
    case kHFSPlusFileRecord:
      record = (HFSPlusCatalogRecord*) malloc(sizeof(HFSPlusCatalogFile));
      if(!READ(io, offset, sizeof(HFSPlusCatalogFile), record))
        return NULL;
      flipCatalogFile((HFSPlusCatalogFile*)record);
      break;
      
    case kHFSPlusFolderThreadRecord:
    case kHFSPlusFileThreadRecord:
      record = (HFSPlusCatalogRecord*) malloc(sizeof(HFSPlusCatalogThread));
      
      if(!READ(io, offset + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t), sizeof(uint16_t), &nameLength))
        return NULL;

      FLIPENDIAN(nameLength);
      
      if(!READ(io, offset, sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint16_t) + (sizeof(uint16_t) * nameLength), record))
        return NULL;
      
      flipCatalogThread((HFSPlusCatalogThread*)record, FALSE);
      break;
  }

  return (BTKey*)record;
}

void ASCIIToUnicode(const char* ascii, HFSUniStr255* unistr) {
  int count;
  
  count = 0;
  while(ascii[count] != '\0') {
    unistr->unicode[count] = ascii[count];
    count++;
  }
  
  unistr->length = count;
}

HFSPlusCatalogRecord* getRecordByCNID(HFSCatalogNodeID CNID, Volume* volume) {
	HFSPlusCatalogKey key;
	HFSPlusCatalogThread* thread;
	HFSPlusCatalogRecord* record;
	int exact;
	
	key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
	key.parentID = CNID;
	key.nodeName.length = 0;
	
	thread = (HFSPlusCatalogThread*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
	
	if(thread == NULL) {
		return NULL;
	}
	
	if(exact == FALSE) {
		free(thread);
		return NULL;
	}
	
	key.parentID = thread->parentID;
    key.nodeName = thread->nodeName;
    
	free(thread);
	
    record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
	
	if(record == NULL || exact == FALSE)
		return NULL;
	else
		return record;
}

CatalogRecordList* getFolderContents(HFSCatalogNodeID CNID, Volume* volume) {
  BTree* tree;
  HFSPlusCatalogThread* record; 
  HFSPlusCatalogKey key;
  uint32_t nodeNumber;
  int recordNumber;
  
  BTNodeDescriptor* descriptor;
  off_t recordOffset;
  off_t recordDataOffset;
  HFSPlusCatalogKey* currentKey;
  
  CatalogRecordList* list;
  CatalogRecordList* lastItem;
  CatalogRecordList* item;
  
  tree = volume->catalogTree;
  
  key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
  key.parentID = CNID;
  key.nodeName.length = 0;
  
  list = NULL;
  
  record = (HFSPlusCatalogThread*) search(tree, (BTKey*)(&key), NULL, &nodeNumber, &recordNumber);
  
  if(record == NULL)
    return NULL;
  
  free(record);
  
  ++recordNumber;
  
  while(nodeNumber != 0) {    
    descriptor = readBTNodeDescriptor(nodeNumber, tree);
       
    while(recordNumber < descriptor->numRecords) {
      recordOffset = getRecordOffset(recordNumber, nodeNumber, tree);
      currentKey = (HFSPlusCatalogKey*) READ_KEY(tree, recordOffset, tree->io);
      recordDataOffset = recordOffset + currentKey->keyLength + sizeof(currentKey->keyLength);
      
      if(currentKey->parentID == CNID) {
        item = (CatalogRecordList*) malloc(sizeof(CatalogRecordList));
        item->name = currentKey->nodeName;
        item->record = (HFSPlusCatalogRecord*) READ_DATA(tree, recordDataOffset, tree->io);
        item->next = NULL;

        if(list == NULL) {
          list = item;
        } else {
          lastItem->next = item;
        }
        
        lastItem = item;
        free(currentKey);
      } else {
        free(currentKey);
        free(descriptor);
        return list;
      }
      
      recordNumber++;
    }
    
    nodeNumber = descriptor->fLink;
    recordNumber = 0;
	
	free(descriptor);
  }
  
  return list;
}

void releaseCatalogRecordList(CatalogRecordList* list) {
	CatalogRecordList* next;
	while(list) {
		next = list->next;
		free(list->record);
		free(list);
		list = next;
	}
}

HFSPlusCatalogRecord* getLinkTarget(HFSPlusCatalogRecord* record, HFSCatalogNodeID parentID, HFSPlusCatalogKey *key, Volume* volume) {
	io_func* io;
	char pathBuffer[1024];
	HFSPlusCatalogRecord* toReturn;

	if(record->recordType == kHFSPlusFileRecord && (((HFSPlusCatalogFile*)record)->permissions.fileMode & S_IFLNK) == S_IFLNK) {
		io = openRawFile(((HFSPlusCatalogFile*)record)->fileID, &(((HFSPlusCatalogFile*)record)->dataFork), record, volume);
		READ(io, 0, (((HFSPlusCatalogFile*)record)->dataFork).logicalSize, pathBuffer);
		CLOSE(io);
		pathBuffer[(((HFSPlusCatalogFile*)record)->dataFork).logicalSize] = '\0';
		toReturn = getRecordFromPath3(pathBuffer, volume, NULL, key, TRUE, TRUE, parentID);
		free(record);
		return toReturn;
	} else {
		return record;
	}
}

HFSPlusCatalogRecord* getRecordFromPath(const char* path, Volume* volume, char **name, HFSPlusCatalogKey* retKey) {
	return getRecordFromPath2(path, volume, name, retKey, TRUE);
}

HFSPlusCatalogRecord* getRecordFromPath2(const char* path, Volume* volume, char **name, HFSPlusCatalogKey* retKey, char traverse) {
	return getRecordFromPath3(path, volume, name, retKey, TRUE, TRUE, kHFSRootFolderID);
}

HFSPlusCatalogRecord* getRecordFromPath3(const char* path, Volume* volume, char **name, HFSPlusCatalogKey* retKey, char traverse, char returnLink, HFSCatalogNodeID parentID) {
  HFSPlusCatalogKey key;
  HFSPlusCatalogRecord* record;
  
  char* origPath;
  char* myPath;
  char* word;
  char* pathLimit;
  
  uint32_t realParent;
  char* lastWordDetectSlash; 
  int exact;
  
  if(path[0] == '\0' || (path[0] == '/' && path[1] == '\0')) {
    if(name != NULL)
      *name = (char*)path;
    
    key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
    key.parentID = kHFSRootFolderID;
    key.nodeName.length = 0;
    
    record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
    key.parentID = ((HFSPlusCatalogThread*)record)->parentID;
    key.nodeName = ((HFSPlusCatalogThread*)record)->nodeName;
    
    free(record);
	
    record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
    return record;
  }
  
  myPath = strdup(path);
  origPath = myPath;
  
  record = NULL;

  if(path[0] == '/') {
    key.parentID = kHFSRootFolderID;
  } else {
    key.parentID = parentID;
  }

  pathLimit = myPath + strlen(myPath);
  
  for(word = (char*)strtok(myPath, "/"); word && (word < pathLimit);
      word = ((word + strlen(word) + 1) < pathLimit) ? (char*)strtok(word + strlen(word) + 1, "/") : NULL) {

    if(name != NULL)
      *name = (char*)(path + (word - origPath));
    
    if(record != NULL) {
      free(record);
      record = NULL;
    }
      
    if(word[0] == '\0') {
      continue;
    }

    ASCIIToUnicode(word, &key.nodeName);
    
    key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length) + (sizeof(uint16_t) * key.nodeName.length);
    record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);

    if(record == NULL || exact == FALSE) {
      free(origPath);
      return NULL;
    }

    if(traverse) {
      lastWordDetectSlash = strchr(word, '/');
      if((lastWordDetectSlash && *(lastWordDetectSlash + 1) != '\0') || returnLink) {
        record = getLinkTarget(record, key.parentID, &key, volume);
        if(record == NULL || exact == FALSE) {
          free(origPath);
          return NULL;
        }
      }
    }
	
    if(record->recordType == kHFSPlusFileRecord) {	
      free(origPath);
      
      if(retKey != NULL) {
        memcpy(retKey, &key, sizeof(HFSPlusCatalogKey));
      }
      
      return record;
    }
    
    if(record->recordType != kHFSPlusFolderRecord)
      panic("inconsistent catalog tree!");
    
    realParent = key.parentID;
    key.parentID = ((HFSPlusCatalogFolder*)record)->folderID;
  }
  
  if(retKey != NULL) {
    memcpy(retKey, &key, sizeof(HFSPlusCatalogKey));
    retKey->parentID = realParent;
  }
  
  free(origPath);
  return record;
}

int updateCatalog(Volume* volume, HFSPlusCatalogRecord* catalogRecord) {
  HFSPlusCatalogKey key;
  HFSPlusCatalogRecord* record;
  HFSPlusCatalogFile file;
  HFSPlusCatalogFolder folder;
  int exact;
  
  key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
  if(catalogRecord->recordType == kHFSPlusFolderRecord) {
    key.parentID = ((HFSPlusCatalogFolder*)catalogRecord)->folderID;
  } else if(catalogRecord->recordType == kHFSPlusFileRecord) {
    key.parentID = ((HFSPlusCatalogFile*)catalogRecord)->fileID;
  } else {
    /* unexpected */
    return FALSE;
  }
  key.nodeName.length = 0;
  
  record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
  
  key.parentID = ((HFSPlusCatalogThread*)record)->parentID;
  key.nodeName = ((HFSPlusCatalogThread*)record)->nodeName;
  key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length) + (sizeof(uint16_t) * key.nodeName.length);

  free(record);

  record = (HFSPlusCatalogRecord*) search(volume->catalogTree, (BTKey*)(&key), &exact, NULL, NULL);
  
  removeFromBTree(volume->catalogTree, (BTKey*)(&key));
  
  switch(record->recordType) {
    case kHFSPlusFolderRecord:
      memcpy(&folder, catalogRecord, sizeof(HFSPlusCatalogFolder));
	  flipCatalogFolder(&folder);
	  free(record);
      return addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFolder), (unsigned char *)(&folder));
      break;
      
    case kHFSPlusFileRecord:
      memcpy(&file, catalogRecord, sizeof(HFSPlusCatalogFile));
      flipCatalogFile(&file);
	  free(record);
      return addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFile), (unsigned char *)(&file));
      break;
  }
  
  return TRUE;
}

int move(const char* source, const char* dest, Volume* volume) {
  HFSPlusCatalogRecord* srcRec;
  HFSPlusCatalogFolder* srcFolderRec;
  HFSPlusCatalogFolder* destRec;
  char* destPath;
  char* destName;
  char* curChar;
  char* lastSeparator;
  
  int i;
  int threadLength;

  HFSPlusCatalogKey srcKey;
  HFSPlusCatalogKey destKey;
  HFSPlusCatalogThread* thread;
  
  srcRec = getRecordFromPath3(source, volume, NULL, &srcKey, TRUE, FALSE, kHFSRootFolderID);
  if(srcRec == NULL) {
    free(srcRec);
    return FALSE;
  }
  
  srcFolderRec = (HFSPlusCatalogFolder*) getRecordByCNID(srcKey.parentID, volume);
    
  if(srcFolderRec == NULL || srcFolderRec->recordType != kHFSPlusFolderRecord) {
    free(srcRec);
    free(srcFolderRec);
    return FALSE;
  }
    
  destPath = strdup(dest);
  
  curChar = destPath;
  lastSeparator = NULL;
  
  while((*curChar) != '\0') {
    if((*curChar) == '/')
      lastSeparator = curChar;
    curChar++;
  }
  
  if(lastSeparator == NULL) {
    destRec = (HFSPlusCatalogFolder*) getRecordFromPath("/", volume, NULL, NULL);
    destName = destPath;
  } else {
    destName = lastSeparator + 1;
    *lastSeparator = '\0';
    destRec = (HFSPlusCatalogFolder*) getRecordFromPath(destPath, volume, NULL, NULL);
    
    if(destRec == NULL || destRec->recordType != kHFSPlusFolderRecord) {
      free(destPath);
      free(srcRec);
      free(destRec);
      free(srcFolderRec);
      return FALSE;
    }
  }
  
  removeFromBTree(volume->catalogTree, (BTKey*)(&srcKey));
  
  srcKey.nodeName.length = 0;
  if(srcRec->recordType == kHFSPlusFolderRecord) {
    srcKey.parentID = ((HFSPlusCatalogFolder*)srcRec)->folderID;
  } else if(srcRec->recordType == kHFSPlusFileRecord) {
    srcKey.parentID = ((HFSPlusCatalogFile*)srcRec)->fileID;
  } else {
    /* unexpected */
    return FALSE;
  }
  srcKey.keyLength = sizeof(srcKey.parentID) + sizeof(srcKey.nodeName.length);
  
  removeFromBTree(volume->catalogTree, (BTKey*)(&srcKey));
  

  destKey.nodeName.length = strlen(destName);

  threadLength = sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint16_t) + (sizeof(uint16_t) * destKey.nodeName.length);
  thread = (HFSPlusCatalogThread*) malloc(threadLength);
  thread->reserved = 0;
  destKey.parentID = destRec->folderID;
  thread->parentID = destKey.parentID;  
  thread->nodeName.length = destKey.nodeName.length;
  for(i = 0; i < destKey.nodeName.length; i++) {
    destKey.nodeName.unicode[i] = destName[i];
    thread->nodeName.unicode[i] = destName[i];
  }
  
  destKey.keyLength = sizeof(uint32_t) + sizeof(uint16_t) + (sizeof(uint16_t) * destKey.nodeName.length);
  
  switch(srcRec->recordType) {
    case kHFSPlusFolderRecord:
      thread->recordType = kHFSPlusFolderThreadRecord;
      flipCatalogFolder((HFSPlusCatalogFolder*)srcRec);
      addToBTree(volume->catalogTree, (BTKey*)(&destKey), sizeof(HFSPlusCatalogFolder), (unsigned char *)(srcRec));
      break;
      
    case kHFSPlusFileRecord:
      thread->recordType = kHFSPlusFileThreadRecord;
      flipCatalogFile((HFSPlusCatalogFile*)srcRec);
      addToBTree(volume->catalogTree, (BTKey*)(&destKey), sizeof(HFSPlusCatalogFile), (unsigned char *)(srcRec));
      break;
  }
  
  destKey.nodeName.length = 0;
  destKey.parentID = srcKey.parentID;
  destKey.keyLength = sizeof(destKey.parentID) + sizeof(destKey.nodeName.length);
  
  flipCatalogThread(thread, TRUE);
  addToBTree(volume->catalogTree, (BTKey*)(&destKey), threadLength, (unsigned char *)(thread));
    
  /* adjust valence */
  srcFolderRec->valence--;
  updateCatalog(volume, (HFSPlusCatalogRecord*) srcFolderRec);
  destRec->valence++;
  updateCatalog(volume, (HFSPlusCatalogRecord*) destRec);
  
  free(thread);
  free(destPath);
  free(srcRec);
  free(destRec);
  free(srcFolderRec);
      
  return TRUE;
}

int removeFile(const char* fileName, Volume* volume) {
  HFSPlusCatalogRecord* record;
  HFSPlusCatalogRecord* parentRecord;
  HFSPlusCatalogKey key;
  HFSPlusCatalogKey parentKey;
  io_func* io;
  HFSPlusCatalogFolder* parentFolder;
  int exact;

  record = getRecordFromPath3(fileName, volume, NULL, &key, TRUE, FALSE, kHFSRootFolderID);
  if(record != NULL) {
    parentFolder = (HFSPlusCatalogFolder*) getRecordByCNID(key.parentID, volume);
    if(parentFolder != NULL) {
	if(parentFolder->recordType != kHFSPlusFolderRecord) {
		ASSERT(FALSE, "parent not folder");
		free(parentFolder);
		return FALSE;
	}
    } else {
	ASSERT(FALSE, "can't find parent");
	return FALSE;
    }

    if(record->recordType == kHFSPlusFileRecord) {
      io = openRawFile(((HFSPlusCatalogFile*)record)->fileID, &((HFSPlusCatalogFile*)record)->dataFork, record, volume);
      allocate((RawFile*)io->data, 0);
      CLOSE(io);

      removeFromBTree(volume->catalogTree, (BTKey*)(&key));
      
      key.nodeName.length = 0;
      key.parentID = ((HFSPlusCatalogFile*)record)->fileID;
      key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
      removeFromBTree(volume->catalogTree, (BTKey*)(&key));
      
      volume->volumeHeader->fileCount--;
    } else {
      if(((HFSPlusCatalogFolder*)record)->valence > 0) {
		free(record);
		free(parentFolder);
		ASSERT(FALSE, "folder not empty");
        return FALSE;
      } else {
        removeFromBTree(volume->catalogTree, (BTKey*)(&key));
        
        key.nodeName.length = 0;
        key.parentID = ((HFSPlusCatalogFolder*)record)->folderID;
        key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
        removeFromBTree(volume->catalogTree, (BTKey*)(&key));
      }
      
	  parentFolder->folderCount--;
      volume->volumeHeader->folderCount--;
    }
    parentFolder->valence--;
    updateCatalog(volume, (HFSPlusCatalogRecord*) parentFolder);
    updateVolume(volume);

	free(record);
	free(parentFolder);
	
    return TRUE;
  } else {
	free(parentFolder);
	ASSERT(FALSE, "cannot find record");
    return FALSE;
  }
}

int makeSymlink(const char* pathName, const char* target, Volume* volume) {
	io_func* io;
	HFSPlusCatalogFile* record;

	record = (HFSPlusCatalogFile*) getRecordFromPath3(pathName, volume, NULL, NULL, TRUE, FALSE, kHFSRootFolderID);

	if(!record) {
		newFile(pathName, volume);
		record = (HFSPlusCatalogFile*) getRecordFromPath(pathName, volume, NULL, NULL);
		if(!record) {
			return FALSE;
		}
		record->permissions.fileMode |= S_IFLNK;
		record->userInfo.fileType = kSymLinkFileType;
		record->userInfo.fileCreator = kSymLinkCreator;
		updateCatalog(volume, (HFSPlusCatalogRecord*) record);
	} else {
		if(record->recordType != kHFSPlusFileRecord || (((HFSPlusCatalogFile*)record)->permissions.fileMode & S_IFLNK) != S_IFLNK) {
			free(record);
			return FALSE;
		}
	}

	io = openRawFile(record->fileID, &record->dataFork, (HFSPlusCatalogRecord*) record, volume);
	WRITE(io, 0, strlen(target), (void*) target);
	CLOSE(io);

	return TRUE;
}

HFSCatalogNodeID newFolder(const char* pathName, Volume* volume) {
  HFSPlusCatalogFolder* parentFolder;
  HFSPlusCatalogFolder folder;
  HFSPlusCatalogKey key;
  HFSPlusCatalogThread thread;
  
  uint32_t newFolderID;
  
  int threadLength;
  
  char* path;
  char* name;
  char* curChar;
  char* lastSeparator;
  
  path = strdup(pathName);
  
  curChar = path;
  lastSeparator = NULL;
  
  while((*curChar) != '\0') {
    if((*curChar) == '/')
      lastSeparator = curChar;
    curChar++;
  }
  
  if(lastSeparator == NULL) {
    parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath("/", volume, NULL, NULL);
    name = path;
  } else {
    name = lastSeparator + 1;
    *lastSeparator = '\0';
    parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath(path, volume, NULL, NULL);
    
    if(parentFolder == NULL || parentFolder->recordType != kHFSPlusFolderRecord) {
      free(path);
      free(parentFolder);
      return FALSE;
    }
  }
  
  newFolderID = volume->volumeHeader->nextCatalogID++;
  volume->volumeHeader->folderCount++;
  
  folder.recordType = kHFSPlusFolderRecord;
  folder.flags = kHFSHasFolderCountMask;
  folder.valence = 0;
  folder.folderID = newFolderID;
  folder.createDate = UNIX_TO_APPLE_TIME(time(NULL));
  folder.contentModDate = folder.createDate;
  folder.attributeModDate = folder.createDate;
  folder.accessDate = folder.createDate;
  folder.backupDate = folder.createDate;
  folder.permissions.ownerID = parentFolder->permissions.ownerID;
  folder.permissions.groupID = parentFolder->permissions.groupID;
  folder.permissions.adminFlags = 0;
  folder.permissions.ownerFlags = 0;
  folder.permissions.fileMode = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
  folder.permissions.special.iNodeNum = 0;
  memset(&folder.userInfo, 0, sizeof(folder.userInfo));
  memset(&folder.finderInfo, 0, sizeof(folder.finderInfo));
  folder.textEncoding = 0;
  folder.folderCount = 0;
  
  key.parentID = parentFolder->folderID;
  ASCIIToUnicode(name, &key.nodeName);
  key.keyLength = sizeof(key.parentID) + STR_SIZE(key.nodeName);
  
  thread.recordType = kHFSPlusFolderThreadRecord;
  thread.reserved = 0;
  thread.parentID = parentFolder->folderID;
  ASCIIToUnicode(name, &thread.nodeName);
  threadLength = sizeof(thread.recordType) + sizeof(thread.reserved) + sizeof(thread.parentID) + STR_SIZE(thread.nodeName);
  flipCatalogThread(&thread, TRUE);
  flipCatalogFolder(&folder);
  
  ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFolder), (unsigned char *)(&folder)), "addToBTree");
  key.nodeName.length = 0;
  key.parentID = newFolderID;
  key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
  ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), threadLength, (unsigned char *)(&thread)), "addToBTree");
  
  parentFolder->folderCount++;
  parentFolder->valence++;
  updateCatalog(volume, (HFSPlusCatalogRecord*) parentFolder);
  
  updateVolume(volume);
  
  free(parentFolder);
  free(path);
  
  return newFolderID;
}

HFSCatalogNodeID newFile(const char* pathName, Volume* volume) {
  HFSPlusCatalogFolder* parentFolder;
  HFSPlusCatalogFile file;
  HFSPlusCatalogKey key;
  HFSPlusCatalogThread thread;
  
  uint32_t newFileID;
  
  int threadLength;
  
  char* path;
  char* name;
  char* curChar;
  char* lastSeparator;
  
  path = strdup(pathName);
  
  curChar = path;
  lastSeparator = NULL;
  
  while((*curChar) != '\0') {
    if((*curChar) == '/')
      lastSeparator = curChar;
    curChar++;
  }
  
  if(lastSeparator == NULL) {
    parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath("/", volume, NULL, NULL);
    name = path;
  } else {
    name = lastSeparator + 1;
    *lastSeparator = '\0';
    parentFolder = (HFSPlusCatalogFolder*) getRecordFromPath(path, volume, NULL, NULL);
    
    if(parentFolder == NULL || parentFolder->recordType != kHFSPlusFolderRecord) {
      free(path);
      free(parentFolder);
      return FALSE;
    }
  }
  
  newFileID = volume->volumeHeader->nextCatalogID++;
  volume->volumeHeader->fileCount++;
  
  file.recordType = kHFSPlusFileRecord;
  file.flags = kHFSThreadExistsMask;
  file.reserved1 = 0;
  file.fileID = newFileID;
  file.createDate = UNIX_TO_APPLE_TIME(time(NULL));
  file.contentModDate = file.createDate;
  file.attributeModDate = file.createDate;
  file.accessDate = file.createDate;
  file.backupDate = file.createDate;
  file.permissions.ownerID = parentFolder->permissions.ownerID;
  file.permissions.groupID = parentFolder->permissions.groupID;
  file.permissions.adminFlags = 0;
  file.permissions.ownerFlags = 0;
  file.permissions.fileMode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
  file.permissions.special.iNodeNum = 0;
  memset(&file.userInfo, 0, sizeof(file.userInfo));
  memset(&file.finderInfo, 0, sizeof(file.finderInfo));
  file.textEncoding = 0;
  file.reserved2 = 0;
  memset(&file.dataFork, 0, sizeof(file.dataFork));
  memset(&file.resourceFork, 0, sizeof(file.resourceFork));
  
  key.parentID = parentFolder->folderID;
  ASCIIToUnicode(name, &key.nodeName);
  key.keyLength = sizeof(key.parentID) + STR_SIZE(key.nodeName);
  
  thread.recordType = kHFSPlusFileThreadRecord;
  thread.reserved = 0;
  thread.parentID = parentFolder->folderID;
  ASCIIToUnicode(name, &thread.nodeName);
  threadLength = sizeof(thread.recordType) + sizeof(thread.reserved) + sizeof(thread.parentID) + STR_SIZE(thread.nodeName);
  flipCatalogThread(&thread, TRUE);
  flipCatalogFile(&file);
  
  ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), sizeof(HFSPlusCatalogFile), (unsigned char *)(&file)), "addToBTree");
  key.nodeName.length = 0;
  key.parentID = newFileID;
  key.keyLength = sizeof(key.parentID) + sizeof(key.nodeName.length);
  ASSERT(addToBTree(volume->catalogTree, (BTKey*)(&key), threadLength, (unsigned char *)(&thread)), "addToBTree");
  
  parentFolder->valence++;
  updateCatalog(volume, (HFSPlusCatalogRecord*) parentFolder);
  
  updateVolume(volume);
  
  free(parentFolder);
  free(path);
  
  return newFileID;
}

int chmodFile(const char* pathName, int mode, Volume* volume) {
  HFSPlusCatalogRecord* record;
   
  record = getRecordFromPath(pathName, volume, NULL, NULL);
  
  if(record == NULL) {
    return FALSE;
  }
  
  if(record->recordType == kHFSPlusFolderRecord) {
    ((HFSPlusCatalogFolder*)record)->permissions.fileMode = (((HFSPlusCatalogFolder*)record)->permissions.fileMode & 0770000) | mode;
  } else if(record->recordType == kHFSPlusFileRecord) {
    ((HFSPlusCatalogFile*)record)->permissions.fileMode = (((HFSPlusCatalogFolder*)record)->permissions.fileMode & 0770000) | mode;
  } else {
    return FALSE;
  }
  
  updateCatalog(volume, record);
  
  free(record);
  
  return TRUE;
}

int chownFile(const char* pathName, uint32_t owner, uint32_t group, Volume* volume) {
  HFSPlusCatalogRecord* record;
   
  record = getRecordFromPath(pathName, volume, NULL, NULL);
  
  if(record == NULL) {
    return FALSE;
  }
  
  if(record->recordType == kHFSPlusFolderRecord) {
    ((HFSPlusCatalogFolder*)record)->permissions.ownerID = owner;
    ((HFSPlusCatalogFolder*)record)->permissions.groupID = group;
  } else if(record->recordType == kHFSPlusFileRecord) {
    ((HFSPlusCatalogFile*)record)->permissions.ownerID = owner;
    ((HFSPlusCatalogFile*)record)->permissions.groupID = group;
  } else {
    return FALSE;
  }
  
  updateCatalog(volume, record);
  
  free(record);
  
  return TRUE;
}


BTree* openCatalogTree(io_func* file) {
  BTree* btree;

  btree = openBTree(file, &catalogCompare, &catalogKeyRead, &catalogKeyWrite, &catalogKeyPrint, &catalogDataRead);

  if(btree->headerRec->keyCompareType == kHFSCaseFolding) {
    btree->compare = &catalogCompareCS;
  }

  return btree;
}

