/*	SelectFolders.c -- select one or more folders.

 2012-09-13 Version 1.0 (Steve Nicholson)

Published under MIT License

Copyright (c) 2012 Impulse Devices, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "XOPStandardHeaders.h"			// Include ANSI headers, Mac headers, IgorXOP.h, XOP.h and XOPSupport.h

#include "SelectFolders.h"

#pragma pack(2)		// All structures passed to Igor are two-byte aligned.
struct selectFoldersParams  {
	Handle result;
};
typedef struct selectFoldersParams selectFoldersParams;
#pragma pack()		// Reset structure alignment to default.

#define MAX_RETURN_STRING_LEN 65535
extern "C" int
selectFolders(selectFoldersParams* p)				
{
	Handle str1;						/* output handle */
	int err=0;
	
	str1 = NULL;						/* if error occurs, result is NULL */
	
    OSStatus status;
    NavDialogCreationOptions myDialogOptions;
    NavDialogRef myDialog = NULL;
    NavReplyRecord dialogReply;
    CFStringRef windowTitle = NULL;
    
    //Create default dialog options
    if (NavGetDefaultDialogCreationOptions(&myDialogOptions)) {
        err = DIALOG_OPTIONS;
        goto done1;
    }
    
    //Customize window title
    windowTitle = CFStringCreateWithCString(NULL, "Choose one or more folders", kCFStringEncodingUTF8);
    myDialogOptions.windowTitle = windowTitle;
    
    //Create the dialog
    if (NavCreateChooseFolderDialog(&myDialogOptions, NULL, NULL, NULL, &myDialog)) {
        err = DIALOG_CREATE;
        goto done2;
    }
    
    //Present the dialog to the user
    if (NavDialogRun(myDialog)) {
        err = DIALOG_RUN;
        goto done3;
    }
    
    //Examine user's action
    if (kNavUserActionChoose != NavDialogGetUserAction(myDialog)) {
        //User didn't click Choose so return empty string
        str1 = NewEmptyHandle();
        goto done3;
    }
    
    //Get the reply information
    if (NavDialogGetReply(myDialog, &dialogReply)) {
        err = DIALOG_GET_REPLY;
        goto done3;
    }
    
    
    char paths[MAX_RETURN_STRING_LEN];
    paths[0] = 0;
    
    //Process the reply if it is valid
    if (dialogReply.validRecord) {
        UInt inx;
        UInt pathCount = 0; //Number of folder paths chosen
        char path[MAX_PATH_LEN]; //buffer for each chosen folder path
        
        long theCount; //Number of items in AEDescList dialogReply.selection
        Boolean aliasFileFlag, folderFlag; //Flags telling whether chosen item is alias or folder
        
        AECountItems(&dialogReply.selection, &theCount);
        
        for (inx=1; inx<=theCount; inx++) {
            FSRef tFSRef;
            
            //Get next FSRef
            status = AEGetNthPtr(&dialogReply.selection, inx, typeFSRef, NULL, NULL, &tFSRef, sizeof(FSRef), NULL);
            if (noErr != status) continue;
            
            //Get FSRef's attributes
            status = FSIsAliasFile(&tFSRef, &aliasFileFlag, &folderFlag);

            //Skip item if it couldn't be converted to FSRef or if it is a valid FSRef but isn't a folder
            if (noErr != status || !folderFlag) continue;
            
            //Use CFURL to convert POSIX to HFS
            CFURLRef cfurl = CFURLCreateFromFSRef(NULL, &tFSRef);
            CFStringRef hfsPath = CFURLCopyFileSystemPath(cfurl, kCFURLHFSPathStyle);
            
            //Copy HFS representation to path buffer
            CFStringGetCString(hfsPath, path, MAX_PATH_LEN, kCFStringEncodingUTF8);

            //Process path if it is non-zero length
			UInt pathLength = strlen(path);
            if (pathLength) {
                UInt pathInx;
                //Convert semi-colons to underscores so path doesn't appear as multiple items in returned string list
                for (pathInx=0; pathInx<pathLength; pathInx++) {
                    if (';' == path[pathInx]) {
                        path[pathInx] = '_';
                    }
                }
                if (pathCount) {
                    //after the first path, append a semi-colon to the list
                    strlcat(paths, ";", MAX_RETURN_STRING_LEN);
                }
                //append new path to string list
                strlcat(paths, path, MAX_RETURN_STRING_LEN);
                pathCount++;
            }
            
            CFRelease(hfsPath);
            CFRelease(cfurl);
        }
        if (pathCount) {
            str1 = NewHandle(strlen(paths));		/* get output handle */
            if (str1 == NULL) {
                err = NOMEM;
                goto done4;						/* out of memory */
            }
            memcpy(*str1, paths, strlen(paths));
        } else {
            //no paths selected, return empty string
            str1 = NewEmptyHandle();
        }
    } else {
		//Dialog reply was invalid, return empty string
		str1 = NewEmptyHandle();
	}
	
done4:
    NavDisposeReply(&dialogReply);
done3:
    NavDialogDispose(myDialog);
done2:
    CFRelease(windowTitle);
done1:

	p->result = str1;
	
	return(err);
}

static XOPIORecResult
RegisterFunction()
{
	int funcIndex;

	funcIndex = (int)GetXOPItem(0);			/* which function invoked ? */
	switch (funcIndex) {
		case 0:								/* str1 = selectFolders() */
			return (XOPIORecResult)selectFolders;	/* This uses the direct call method - preferred. */
	}
	return 0;
}

static int
DoFunction()
{
	int funcIndex;
	void *p;				/* pointer to structure containing function parameters and result */
	int err;				/* error code returned by function */

	funcIndex = (int)GetXOPItem(0);	/* which function invoked ? */
	p = (void*)GetXOPItem(1);		/* get pointer to params/result */
	switch (funcIndex) {
		case 1:						/* str1 = selectFolders() */
			err = selectFolders((selectFoldersParams*)p);
			break;
		default:
			err = UNKNOWN_XFUNC;
			break;
	}
	return(err);
}

/*	XOPEntry()

	This is the entry point from the host application to the XOP for all messages after the
	INIT message.
*/

extern "C" void
XOPEntry(void)
{	
	XOPIORecResult result = 0;

	switch (GetXOPMessage()) {
		case FUNCTION:								/* our external function being invoked ? */
			result = DoFunction();
			break;
			
		case FUNCADDRS:
			result = RegisterFunction();
			break;
	}
	SetXOPResult(result);
}

/*	main(ioRecHandle)

	This is the initial entry point at which the host application calls XOP.
	The message sent by the host must be INIT.
	main() does any necessary initialization and then sets the XOPEntry field of the
	ioRecHandle to the address to be called for future messages.
*/

HOST_IMPORT int
main(IORecHandle ioRecHandle)
{	
	XOPInit(ioRecHandle);							/* do standard XOP initialization */
	SetXOPEntry(XOPEntry);							/* set entry point for future calls */

	if (igorVersion < 600) {
		SetXOPResult(OLD_IGOR);
		return EXIT_FAILURE;
	}
	
	SetXOPResult(0);
	return EXIT_SUCCESS;
}
