
 /* Platform isolation convenience functions */

/* 
 * Argyll Color Correction System
 *
 * Author: Graeme W. Gill
 * Date:   28/9/97
 *
 * Copyright 1997 - 2013 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
 * see the License2.txt file for licencing details.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#ifdef NT
# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
#  define _WIN32_WINNT 0x0501
# endif
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#include <conio.h>
#include <tlhelp32.h>
#include <direct.h>
#endif

#if defined(UNIX)
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>

/* select() defined, but not poll(), so emulate poll() */
#if defined(FD_CLR) && !defined(POLLIN)
#include "pollem.h"
#define poll_x pollem
#else
#include <sys/poll.h>	/* Else assume poll() is native */
#define poll_x poll
#endif
#endif

#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#else
#include "sa_config.h"
#endif
#include "numsup.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"

#ifdef __APPLE__
//#include <stdbool.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/IOBSD.h>
#include <mach/mach_init.h>
#include <mach/task_policy.h>
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
# include <AudioToolbox/AudioServices.h>
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
# include <objc/objc-auto.h>
#endif
#endif /* __APPLE__ */

#undef DEBUG

#ifdef DEBUG
# define DBG(xx)	a1logd(g_log, 0, xx )
# define DBGA g_log, 0 		/* First argument to DBGF() */
# define DBGF(xx)	a1logd xx
#else
# define DBG(xx)
# define DBGF(xx)
#endif

#ifdef __BORLANDC__
#define _kbhit kbhit
#endif

/* ============================================================= */
/*                           MS WINDOWS                          */  
/* ============================================================= */
#ifdef NT

/* wait for and then return the next character from the keyboard */
/* (If not_interactive, return getchar()) */
int next_con_char(void) {
	int c;

	if (not_interactive) {
		HANDLE stdinh;
  		char buf[1];
		DWORD bread;

		if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
			return 0;
		}
	
		/* Ignore end of line characters */
		for (;;) {
			if (ReadFile(stdinh, buf, 1, &bread, NULL)
			 && bread == 1
			 && buf[0] != '\r' && buf[0] != '\n') { 
				return buf[0];
			}
		}
	}

	c = _getch();
	return c;
}

/* Horrible hack to poll stdin when we're not interactive */
static int th_read_char(void *pp) {
	char *rp = (char *)pp;
	HANDLE stdinh;
  	char buf[1];
	DWORD bread;

	if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
		return 0;

	if (ReadFile(stdinh, buf, 1, &bread, NULL)
	 && bread == 1
	 && buf[0] != '\r' && buf[0] != '\n') { 
		*rp = buf[0];
	}

	return 0;
}

/* If there is one, return the next character from the keyboard, else return 0 */
/* (If not_interactive, always returns 0) */
int poll_con_char(void) {

	if (not_interactive) {		/* Can't assume that it's the console */
		athread *getch_thread = NULL;
		char c = 0;
	
		/* This is pretty horrible. The problem is that we can't use */
		/* any of MSWin's async file read functions, because we */
		/* have no way of ensuring that the STD_INPUT_HANDLE has been */
		/* opened with FILE_FLAG_OVERLAPPED. Used a thread instead... */
		if ((getch_thread = new_athread(th_read_char, &c)) != NULL) {
			HANDLE stdinh;

			if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
				return 0;
			}

			Sleep(1);			/* We just hope 1 msec is enough for the thread to start */
			CancelIo(stdinh);
			getch_thread->del(getch_thread);
			return c;
		}
		return 0;
	}

	/* Assume it's the console */
	if (_kbhit() != 0) {
		int c = next_con_char();
		return c;
	}
	return 0; 
}

/* Suck all characters from the keyboard */
/* (If not_interactive, does nothing) */
void empty_con_chars(void) {

	if (not_interactive) {
		return;
	}

	Sleep(50);					/* _kbhit seems to have a bug */
	while (_kbhit()) {
		if (next_con_char() == 0x3)	/* ^C Safety */
			break;
	}
}

/* Sleep for the given number of seconds */
void sleep(unsigned int secs) {
	Sleep(secs * 1000);
}

/* Sleep for the given number of msec */
void msec_sleep(unsigned int msec) {
	Sleep(msec);
}

/* Return the current time in msec since */
/* the first invokation of msec_time() */
/* (Is this based on timeGetTime() ? ) */
unsigned int msec_time() {
	unsigned int rv;
	static unsigned int startup = 0;

	rv =  GetTickCount();
	if (startup == 0)
		startup = rv;

	return rv - startup;
}

/* Return the current time in usec */
/* since the first invokation of usec_time() */
/* Return -1.0 if not available */
double usec_time() {
	double rv;
	LARGE_INTEGER val;
	static double scale = 0.0;
	static LARGE_INTEGER startup;

	if (scale == 0.0) {
		if (QueryPerformanceFrequency(&val) == 0)
			return -1.0;
		scale = 1000000.0/val.QuadPart;
		QueryPerformanceCounter(&val);
		startup.QuadPart = val.QuadPart;

	} else {
		QueryPerformanceCounter(&val);
	}
	val.QuadPart -= startup.QuadPart;

	rv = val.QuadPart * scale;
		
	return rv;
}

static athread *beep_thread = NULL;
static int beep_delay;
static int beep_freq;
static int beep_msec;

/* Delayed beep handler */
static int delayed_beep(void *pp) {
	msec_sleep(beep_delay);
	Beep(beep_freq, beep_msec);
	return 0;
}

/* Activate the system beeper */
void msec_beep(int delay, int freq, int msec) {
	if (delay > 0) {
		if (beep_thread != NULL)
			beep_thread->del(beep_thread);
		beep_delay = delay;
		beep_freq = freq;
		beep_msec = msec;
		if ((beep_thread = new_athread(delayed_beep, NULL)) == NULL)
			a1logw(g_log, "msec_beep: Delayed beep failed to create thread\n");
	} else {
		Beep(freq, msec);
	}
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef NEVER    /* Not currently needed, or effective */

/* Set the current threads priority */
int set_interactive_priority() {
	if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0)
		return 1;
	return 0;
}

int set_normal_priority() {
	if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL) == 0)
		return 1;
	return 0;
}

#endif /* NEVER */


#undef USE_BEGINTHREAD

/* Wait for the thread to exit. Return the result */
static int athread_wait(struct _athread *p) {

	if (p->finished)
		return p->result;

	WaitForSingleObject(p->th, INFINITE);

	return p->result;
}

/* Destroy the thread */
static void athread_del(
athread *p
) {
	DBG("athread_del called\n");

	if (p == NULL)
		return;

	if (p->th != NULL) {
		if (!p->finished) {
			DBG("athread_del calling TerminateThread() because thread hasn't finished\n");
			TerminateThread(p->th, (DWORD)-1);		/* But it is worse to leave it hanging around */
		}
		CloseHandle(p->th);
	}

	free(p);
}

/* _beginthread doesn't leak memory, but */
/* needs to be linked to a different library */
#ifdef USE_BEGINTHREAD
/* Thread function */
static void __cdecl threadproc(
	void *lpParameter
) {
#else
DWORD WINAPI threadproc(
	LPVOID lpParameter
) {
#endif
	athread *p = (athread *)lpParameter;

	p->result = p->function(p->context);
	p->finished = 1;
#ifdef USE_BEGINTHREAD
#else
	return 0;
#endif
}
 

athread *new_athread(
	int (*function)(void *context),
	void *context
) {
	athread *p = NULL;

	DBG("new_athread called\n");

	if ((p = (athread *)calloc(sizeof(athread), 1)) == NULL) {
		a1loge(g_log, 1, "new_athread: calloc failed\n");
		return NULL;
	}

	p->function = function;
	p->context = context;
	p->wait = athread_wait;
	p->del = athread_del;

	/* Create a thread */
#ifdef USE_BEGINTHREAD
	p->th = _beginthread(threadproc, 0, (void *)p);
	if (p->th == -1) {
#else
	p->th = CreateThread(NULL, 0, threadproc, (void *)p, 0, NULL);
	if (p->th == NULL) {
#endif
		a1loge(g_log, 1, "new_athread: CreateThread failed with %d\n",GetLastError());
		p->th = NULL;
		athread_del(p);
		return NULL;
	}

	DBG("new_athread returning OK\n");
	return p;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* Delete a file */
void delete_file(char *fname) {
	_unlink(fname);
}

/* Given the path to a file, ensure that all the parent directories */
/* are created. return nz on error */
int create_parent_directories(char *path) {
	struct _stat sbuf;
	char *pp = path;

	if (*pp != '\000'		/* Skip drive number */
		&& ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
	    && pp[1] == ':')
		pp += 2;
	if (*pp == '/')
		pp++;			/* Skip root directory */
	for (;pp != NULL && *pp != '\000';) {
		if ((pp = strchr(pp, '/')) != NULL) {
			*pp = '\000';
			if (_stat(path,&sbuf) != 0)
			{
				if (_mkdir(path) != 0)
					return 1;
			}
			*pp = '/';
			pp++;
		}
	}
	return 0;
}

#endif /* NT */


/* ============================================================= */
/*                          UNIX/OS X                            */
/* ============================================================= */

#if defined(UNIX)

/* Wait for and return the next character from the keyboard */
/* (If not_interactive, return getchar()) */
int next_con_char(void) {
	struct pollfd pa[1];		/* Poll array to monitor stdin */
	struct termios origs, news;
	char rv = 0;

	if (!not_interactive) {
		/* Configure stdin to be ready with just one character */
		if (tcgetattr(STDIN_FILENO, &origs) < 0)
			a1logw(g_log, "next_con_char: tcgetattr failed with '%s' on stdin", strerror(errno));
		news = origs;
		news.c_lflag &= ~(ICANON | ECHO);
		news.c_cc[VTIME] = 0;
		news.c_cc[VMIN] = 1;
		if (tcsetattr(STDIN_FILENO,TCSANOW, &news) < 0)
			a1logw(g_log, "next_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
	}

	/* Wait for stdin to have a character */
	pa[0].fd = STDIN_FILENO;
	pa[0].events = POLLIN | POLLPRI;
	pa[0].revents = 0;

	if (poll_x(pa, 1, -1) > 0
	 && (pa[0].revents == POLLIN
		 || pa[0].revents == POLLPRI)) {
		char tb[3];
		if (read(STDIN_FILENO, tb, 1) > 0) {	/* User hit a key */
			rv = tb[0] ;
		}
	} else {
		if (!not_interactive)
			tcsetattr(STDIN_FILENO, TCSANOW, &origs);
		a1logw(g_log, "next_con_char: poll on stdin returned unexpected value 0x%x",pa[0].revents);
	}

	/* Restore stdin */
	if (!not_interactive && tcsetattr(STDIN_FILENO, TCSANOW, &origs) < 0) {
		a1logw(g_log, "next_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
	}

	return rv;
}

/* If here is one, return the next character from the keyboard, else return 0 */
/* (If not_interactive, always returns 0) */
int poll_con_char(void) {
	struct pollfd pa[1];		/* Poll array to monitor stdin */
	struct termios origs, news;
	char rv = 0;

	if (!not_interactive) {
		/* Configure stdin to be ready with just one character */
		if (tcgetattr(STDIN_FILENO, &origs) < 0)
			a1logw(g_log, "poll_con_char: tcgetattr failed with '%s' on stdin", strerror(errno));
		news = origs;
		news.c_lflag &= ~(ICANON | ECHO);
		news.c_cc[VTIME] = 0;
		news.c_cc[VMIN] = 1;
		if (tcsetattr(STDIN_FILENO,TCSANOW, &news) < 0)
			a1logw(g_log, "poll_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
	}

	/* Wait for stdin to have a character */
	pa[0].fd = STDIN_FILENO;
	pa[0].events = POLLIN | POLLPRI;
	pa[0].revents = 0;

	if (poll_x(pa, 1, 0) > 0
	 && (pa[0].revents == POLLIN
		 || pa[0].revents == POLLPRI)) {
		char tb[3];
		if (read(STDIN_FILENO, tb, 1) > 0) {	/* User hit a key */
			rv = tb[0] ;
		}
	}

	/* Restore stdin */
	if (!not_interactive && tcsetattr(STDIN_FILENO, TCSANOW, &origs) < 0)
		a1logw(g_log, "poll_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));

	return rv;
}

/* Suck all characters from the keyboard */
/* (If not_interactive, does nothing) */
void empty_con_chars(void) {
	if (not_interactive)
		return;

	tcflush(STDIN_FILENO, TCIFLUSH);
}

/* Sleep for the given number of msec */
void msec_sleep(unsigned int msec) {
#ifdef NEVER
	if (msec > 1000) {
		unsigned int secs;
		secs = msec / 1000;
		msec = msec % 1000;
		sleep(secs);
	}
	usleep(msec * 1000);
#else
	struct timespec ts;

	ts.tv_sec = msec / 1000;
	ts.tv_nsec = (msec % 1000) * 1000000;
	nanosleep(&ts, NULL);
#endif
}

/* Return the current time in msec */
/* since the first invokation of msec_time() */
unsigned int msec_time() {
	unsigned int rv;
	static struct timeval startup = { 0, 0 };
	struct timeval cv;

	/* Is this monotonic ? */
	/* On Linux, should clock_gettime with CLOCK_MONOTONIC be used instead ? */
	/* On OS X, should mach_absolute_time() be used ? */
	/* or host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clk) */
	gettimeofday(&cv, NULL);

	/* Set time to 0 on first invocation */
	if (startup.tv_sec == 0 && startup.tv_usec == 0)
		startup = cv;

	/* Subtract, taking care of carry */
	cv.tv_sec -= startup.tv_sec;
	if (startup.tv_usec > cv.tv_usec) {
		cv.tv_sec--;
		cv.tv_usec += 1000000;
	}
	cv.tv_usec -= startup.tv_usec;

	/* Convert usec to msec */
	rv = cv.tv_sec * 1000 + cv.tv_usec / 1000;

	return rv;
}

/* Return the current time in usec */
/* since the first invokation of usec_time() */
double usec_time() {
	double rv;
	static struct timeval startup = { 0, 0 };
	struct timeval cv;

	/* Is this monotonic ? */
	/* On Linux, should clock_gettime with CLOCK_MONOTONIC/CLOCK_REALTIME/CLOCK_REALTIME_HR ? */
	/* On OS X, should mach_absolute_time() be used ? */
	/* or host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clk) */
	gettimeofday(&cv, NULL);

	/* Set time to 0 on first invocation */
	if (startup.tv_sec == 0 && startup.tv_usec == 0)
		startup = cv;

	/* Subtract, taking care of carry */
	cv.tv_sec -= startup.tv_sec;
	if (startup.tv_usec > cv.tv_usec) {
		cv.tv_sec--;
		cv.tv_usec += 1000000;
	}
	cv.tv_usec -= startup.tv_usec;

	/* Convert to usec */
	rv = cv.tv_sec * 1000000.0 + cv.tv_usec;

	return rv;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef NEVER    /* Not currently needed, or effective */

/* Set the current threads priority */
int set_interactive_priority() {
#ifdef __APPLE__
#ifdef NEVER
    int rv = 0;
    struct task_category_policy tcatpolicy;

    tcatpolicy.role = TASK_FOREGROUND_APPLICATION;

    if (task_policy_set(mach_task_self(),
        TASK_CATEGORY_POLICY, (thread_policy_t)&tcatpolicy,
        TASK_CATEGORY_POLICY_COUNT) != KERN_SUCCESS)
		rv = 1;
//		a1logd(g_log, 8, "set_interactive_priority: set to forground got %d\n",rv);
	return rv;
#else
    int rv = 0;
    struct thread_precedence_policy tppolicy;

    tppolicy.importance = 500;

    if (thread_policy_set(mach_thread_self(),
        THREAD_PRECEDENCE_POLICY, (thread_policy_t)&tppolicy,
        THREAD_PRECEDENCE_POLICY_COUNT) != KERN_SUCCESS)
		rv = 1;
//		a1logd(g_log, 8, "set_interactive_priority: set to important got %d\n",rv);
	return rv;
#endif /* NEVER */
#else /* !APPLE */
	int rv;
	struct sched_param param;
	param.sched_priority = 32;

	/* This doesn't work unless we're running as su :-( */
	rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
//		a1logd(g_log, 8, "set_interactive_priority: set got %d\n",rv);
	return rv;
#endif /* !APPLE */
}

int set_normal_priority() {
#ifdef __APPLE__
#ifdef NEVER
    int rv = 0;
    struct task_category_policy tcatpolicy;

    tcatpolicy.role = TASK_UNSPECIFIED;

    if (task_policy_set(mach_task_self(),
        TASK_CATEGORY_POLICY, (thread_policy_t)&tcatpolicy,
        TASK_CATEGORY_POLICY_COUNT) != KERN_SUCCESS)
		rev = 1;
//		a1logd(g_log, 8, "set_normal_priority: set to normal got %d\n",rv);
#else
    int rv = 0;
    struct thread_precedence_policy tppolicy;

    tppolicy.importance = 1;

    if (thread_policy_set(mach_thread_self(),
        THREAD_STANDARD_POLICY, (thread_policy_t)&tppolicy,
        THREAD_STANDARD_POLICY_COUNT) != KERN_SUCCESS)
		rv = 1;
//		a1logd(g_log, 8, "set_normal_priority: set to standard got %d\n",rv);
	return rv;
#endif /* NEVER */
#else /* !APPLE */
	struct sched_param param;
	param.sched_priority = 0;
	int rv;

	rv = pthread_setschedparam(pthread_self(), SCHED_OTHER, &param);
//		a1logd(g_log, 8, "set_normal_priority: reset got %d\n",rv);
	return rv;
#endif /* !APPLE */
}

#endif /* NEVER */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static athread *beep_thread = NULL;
static int beep_delay;
static int beep_freq;
static int beep_msec;

/* Delayed beep handler */
static int delayed_beep(void *pp) {
	msec_sleep(beep_delay);
#ifdef __APPLE__
# if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
	AudioServicesPlayAlertSound(kUserPreferredAlert);
# else
	SysBeep((beep_msec * 60)/1000);
# endif
#else	/* UNIX */
	/* Linux is pretty lame in this regard... */
	fprintf(stdout, "\a"); fflush(stdout);
#endif 
	return 0;
}

/* Activate the system beeper */
void msec_beep(int delay, int freq, int msec) {
	if (delay > 0) {
		if (beep_thread != NULL)
			beep_thread->del(beep_thread);
		beep_delay = delay;
		beep_freq = freq;
		beep_msec = msec;
		if ((beep_thread = new_athread(delayed_beep, NULL)) == NULL)
			a1logw(g_log, "msec_beep: Delayed beep failed to create thread\n");
	} else {
#ifdef __APPLE__
# if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
			AudioServicesPlayAlertSound(kUserPreferredAlert);
# else
			SysBeep((msec * 60)/1000);
# endif
#else	/* UNIX */
		fprintf(stdout, "\a"); fflush(stdout);
#endif
	}
}


#ifdef NEVER
/* If we're UNIX and running on X11, we could do this */
/* sort of thing (from xset) to sound a beep, */
/* IF we were linking with X11: */

static void
set_bell_vol(Display *dpy, int percent) {
	XKeyboardControl values;
	XKeyboardState kbstate;
	values.bell_percent = percent;
	if (percent == DEFAULT_ON)
	  values.bell_percent = SERVER_DEFAULT;
	XChangeKeyboardControl(dpy, KBBellPercent, &values);
	if (percent == DEFAULT_ON) {
	  XGetKeyboardControl(dpy, &kbstate);
	  if (!kbstate.bell_percent) {
	    values.bell_percent = -percent;
	    XChangeKeyboardControl(dpy, KBBellPercent, &values);
	  }
	}
	return;
}

static void
set_bell_pitch(Display *dpy, int pitch) {
	XKeyboardControl values;
	values.bell_pitch = pitch;
	XChangeKeyboardControl(dpy, KBBellPitch, &values);
	return;
}

static void
set_bell_dur(Display *dpy, int duration) {
	XKeyboardControl values;
	values.bell_duration = duration;
	XChangeKeyboardControl(dpy, KBBellDuration, &values);
	return;
}

XBell(..);

#endif /* NEVER */

/* - - - - - - - - - - - - - - - - - - - - - - - - */

/* Wait for the thread to exit. Return the result */
static int athread_wait(struct _athread *p) {

	if (p->finished)
		return p->result;

	pthread_join(p->thid, NULL);

	return p->result;
}

/* Destroy the thread */
static void athread_del(
athread *p
) {
	DBG("athread_del called\n");

	if (p == NULL)
		return;

	if (!p->finished) {
		pthread_cancel(p->thid);
	}
	pthread_join(p->thid, NULL);
	free(p);
}

static void *threadproc(
	void *param
) {
	athread *p = (athread *)param;

	/* Register this thread with the Objective-C garbage collector */
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
	 objc_registerThreadWithCollector();
#endif

	p->result = p->function(p->context);
	p->finished = 1;

	return 0;
}
 

athread *new_athread(
	int (*function)(void *context),
	void *context
) {
	int rv;
	athread *p = NULL;

	DBG("new_athread called\n");

	if ((p = (athread *)calloc(sizeof(athread), 1)) == NULL) {
		a1loge(g_log, 1, "new_athread: calloc failed\n");
		return NULL;
	}

	p->function = function;
	p->context = context;
	p->wait = athread_wait;
	p->del = athread_del;

	/* Create a thread */
	rv = pthread_create(&p->thid, NULL, threadproc, (void *)p);
	if (rv != 0) {
		a1loge(g_log, 1, "new_athread: pthread_create failed with %d\n",rv);
		athread_del(p);
		return NULL;
	}

	DBG("About to exit new_athread()\n");
	return p;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - */

/* Delete a file */
void delete_file(char *fname) {
	unlink(fname);
}

/* Given the path to a file, ensure that all the parent directories */
/* are created. return nz on error */
int create_parent_directories(char *path) {
	struct stat sbuf;
	char *pp = path;
	mode_t mode = 0700;		/* Default directory mode */

	if (*pp == '/')
		pp++;			/* Skip root directory */
	for (;pp != NULL && *pp != '\000';) {
		if ((pp = strchr(pp, '/')) != NULL) {
			*pp = '\000';
			if (stat(path,&sbuf) != 0)
			{
				if (mkdir(path, mode) != 0)
					return 1;
			} else
				mode = sbuf.st_mode;
			*pp = '/';
			pp++;
		}
	}
	return 0;
}

#endif /* defined(UNIX) */

/* - - - - - - - - - - - - - - - - - - - - - - - - */
#if defined(__APPLE__) || defined(NT)

/* Thread to monitor and kill the named processes */
static int th_kkill_nprocess(void *pp) {
	kkill_nproc_ctx *ctx = (kkill_nproc_ctx *)pp;

	while(ctx->stop == 0) {
		kill_nprocess(ctx->pname, ctx->log);
		msec_sleep(20);			/* Don't hog the CPU */
	}
	ctx->done = 1;

	return 0;
}

static void kkill_nprocess_del(kkill_nproc_ctx *p) {
	int i;

	p->stop = 1;

	DBG("kkill_nprocess del called\n");
	for (i = 0; p->done == 0 && i < 100; i++) {
		msec_sleep(50);
	}

	if (p->done == 0) {			/* Hmm */
		a1logw(p->log,"kkill_nprocess del failed to stop - killing thread\n");
		p->th->del(p->th);
	}

	del_a1log(p->log);
	free(p);

	DBG("kkill_nprocess del done\n");
}

/* Start a thread to constantly kill a process. */
/* Call ctx->del() when done */
kkill_nproc_ctx *kkill_nprocess(char **pname, a1log *log) {
	kkill_nproc_ctx *p;

	DBG("kkill_nprocess called\n");
	if (log != NULL && log->debug >= 8) {
		int i;
		a1logv(log, 8, "kkill_nprocess called with");
		for (i = 0; pname[i] != NULL; i++)
			a1logv(log, 8, " '%s'",pname[i]);
		a1logv(log, 8, "\n");
	}

	if ((p = (kkill_nproc_ctx *)calloc(sizeof(kkill_nproc_ctx), 1)) == NULL) {
		a1loge(log, 1, "kkill_nprocess: calloc failed\n");
		return NULL;
	}

	p->pname = pname;
	p->log = new_a1log_d(log);
	p->del = kkill_nprocess_del;

	if ((p->th = new_athread(th_kkill_nprocess, p)) == NULL) {
		del_a1log(p->log);
		free(p);
		return NULL;
	}
	return p;
}

#ifdef NT

/* Kill a list of named process. */
/* Kill the first process found, then return */
/* return < 0 if this fails. */
/* return 0 if there is no such process */
/* return 1 if a process was killed */
int kill_nprocess(char **pname, a1log *log) {
	PROCESSENTRY32 entry;
	HANDLE snapshot;
	int j;

	/* Get a snapshot of the current processes */
	if ((snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)) == NULL) {
		return -1;
	}

	while(Process32Next(snapshot,&entry) != FALSE) {
		HANDLE proc;

		if (strcmp(entry.szExeFile, "spotread.exe") == 0
		 && (proc = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID)) != NULL) {
			if (TerminateProcess(proc,0) == 0) {
				a1logv(log, 8, "kill_nprocess: Failed to kill '%s'\n",entry.szExeFile);
			} else {
				a1logv(log, 8, "kill_nprocess: Killed '%s'\n",entry.szExeFile);
			}
			CloseHandle(proc);
		}
		for (j = 0;; j++) {
			if (pname[j] == NULL)	/* End of list */
				break;
			a1logv(log, 8, "kill_nprocess: Checking process '%s' against list '%s'\n",
			                                                  entry.szExeFile,pname[j]);
			if (strcmp(entry.szExeFile,pname[j]) == 0) {
				a1logv(log, 1, "kill_nprocess: killing process '%s' pid %d\n",
				                            entry.szExeFile,entry.th32ProcessID);

				if ((proc = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID)) == NULL
				 || TerminateProcess(proc,0) == 0) {
					a1logv(log, 1, "kill_nprocess: kill process '%s' failed with %d\n",
					                                             pname[j],GetLastError());
					CloseHandle(proc);
					CloseHandle(snapshot);
					return -1;
				}
				CloseHandle(proc);
				/* Stop on first one found ? */
				CloseHandle(snapshot);
				return 1;
			}
		}
	}
	CloseHandle(snapshot);
	return 0;
}

#endif /* NT */

#if defined(__APPLE__)

/* Kill a list of named process. */
/* Kill the first process found, then return */
/* return < 0 if this fails. */
/* return 0 if there is no such process */
/* return 1 if a process was killed */
int kill_nprocess(char **pname, a1log *log) {
	struct kinfo_proc *procList = NULL;
	size_t procCount = 0;
	static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
	size_t length;
	int rv = 0;
	int i, j;

	procList = NULL;
	for (;;) {
		int err;

		/* Establish the amount of memory needed */
		length = 0;
		if (sysctl(name, (sizeof(name) / sizeof(*name)) - 1,
                      NULL, &length,
                      NULL, 0) == -1) {
			DBGF((DBGA,"sysctl #1 failed with %d\n", errno));
			return -1;
		}
	
		/* Add some more entries in case the number of processors changed */
		length += 10 * sizeof(struct kinfo_proc);
		if ((procList = malloc(length)) == NULL) {
			DBGF((DBGA,"malloc failed for %d bytes\n", length));
			return -1;
		}

		/* Call again with memory */
		if ((err = sysctl(name, (sizeof(name) / sizeof(*name)) - 1,
                          procList, &length,
                          NULL, 0)) == -1) {
			DBGF((DBGA,"sysctl #1 failed with %d\n", errno));
			free(procList);
			return -1;
		}
		if (err == 0) {
			break;
		} else if (err == ENOMEM) {
			free(procList);
			procList = NULL;
		}
	}

	procCount = length / sizeof(struct kinfo_proc);

	/* Locate the processes */
	for (i = 0; i < procCount; i++) {
		for (j = 0;; j++) {
			if (pname[j] == NULL)	/* End of list */
				break;
			a1logv(log, 8, "kill_nprocess: Checking process '%s' against list '%s'\n",
			                                         procList[i].kp_proc.p_comm,pname[j]);
			if (strncmp(procList[i].kp_proc.p_comm,pname[j],MAXCOMLEN) == 0) {
				a1logv(log, 1, "kill_nprocess: killing process '%s' pid %d\n",
				                            pname[j],procList[i].kp_proc.p_pid);
				if (kill(procList[i].kp_proc.p_pid, SIGTERM) != 0) {
					a1logv(log, 1, "kill_nprocess: kill process '%s' failed with %d\n",
					                                                    pname[j],errno);
					free(procList);
					return -1;
				}
				/* Stop on first one found ? */
				free(procList);
				return 1;
			}
		}
	}
	free(procList);
	return rv;
}

#endif /* __APPLE__ */

#endif /* __APPLE__ || NT */

/* ============================================================= */

/* A very small subset of icclib, copied to here. */
/* This is just enough to support the standalone instruments */
#ifdef SALONEINSTLIB

sa_XYZNumber sa_D50 = {
    0.9642, 1.0000, 0.8249
};

void sa_SetUnity3x3(double mat[3][3]) {
	int i, j;
	for (j = 0; j < 3; j++) {
		for (i = 0; i < 3; i++) {
			if (i == j)
				mat[j][i] = 1.0;
			else
				mat[j][i] = 0.0;
		}
	}
	
}

void sa_Cpy3x3(double dst[3][3], double src[3][3]) {
	int i, j;

	for (j = 0; j < 3; j++) {
		for (i = 0; i < 3; i++)
			dst[j][i] = src[j][i];
	}
}

void sa_MulBy3x3(double out[3], double mat[3][3], double in[3]) {
	double tt[3];

	tt[0] = mat[0][0] * in[0] + mat[0][1] * in[1] + mat[0][2] * in[2];
	tt[1] = mat[1][0] * in[0] + mat[1][1] * in[1] + mat[1][2] * in[2];
	tt[2] = mat[2][0] * in[0] + mat[2][1] * in[1] + mat[2][2] * in[2];

	out[0] = tt[0];
	out[1] = tt[1];
	out[2] = tt[2];
}

void sa_Mul3x3_2(double dst[3][3], double src1[3][3], double src2[3][3]) {
	int i, j, k;
	double td[3][3];		/* Temporary dest */

	for (j = 0; j < 3; j++) {
		for (i = 0; i < 3; i++) {
			double tt = 0.0;
			for (k = 0; k < 3; k++)
				tt += src1[j][k] * src2[k][i];
			td[j][i] = tt;
		}
	}

	/* Copy result out */
	for (j = 0; j < 3; j++)
		for (i = 0; i < 3; i++)
			dst[j][i] = td[j][i];
}


/* Matrix Inversion by Richard Carling from "Graphics Gems", Academic Press, 1990 */
#define det2x2(a, b, c, d) (a * d - b * c)

static void adjoint(
double out[3][3],
double in[3][3]
) {
    double a1, a2, a3, b1, b2, b3, c1, c2, c3;

    /* assign to individual variable names to aid  */
    /* selecting correct values  */

	a1 = in[0][0]; b1 = in[0][1]; c1 = in[0][2];
	a2 = in[1][0]; b2 = in[1][1]; c2 = in[1][2];
	a3 = in[2][0]; b3 = in[2][1]; c3 = in[2][2];

    /* row column labeling reversed since we transpose rows & columns */

    out[0][0]  =   det2x2(b2, b3, c2, c3);
    out[1][0]  = - det2x2(a2, a3, c2, c3);
    out[2][0]  =   det2x2(a2, a3, b2, b3);
        
    out[0][1]  = - det2x2(b1, b3, c1, c3);
    out[1][1]  =   det2x2(a1, a3, c1, c3);
    out[2][1]  = - det2x2(a1, a3, b1, b3);
        
    out[0][2]  =   det2x2(b1, b2, c1, c2);
    out[1][2]  = - det2x2(a1, a2, c1, c2);
    out[2][2]  =   det2x2(a1, a2, b1, b2);
}

static double sa_Det3x3(double in[3][3]) {
    double a1, a2, a3, b1, b2, b3, c1, c2, c3;
    double ans;

	a1 = in[0][0]; b1 = in[0][1]; c1 = in[0][2];
	a2 = in[1][0]; b2 = in[1][1]; c2 = in[1][2];
	a3 = in[2][0]; b3 = in[2][1]; c3 = in[2][2];

    ans = a1 * det2x2(b2, b3, c2, c3)
        - b1 * det2x2(a2, a3, c2, c3)
        + c1 * det2x2(a2, a3, b2, b3);
    return ans;
}

#define SA__SMALL_NUMBER	1.e-8

int sa_Inverse3x3(double out[3][3], double in[3][3]) {
    int i, j;
    double det;

    /*  calculate the 3x3 determinant
     *  if the determinant is zero, 
     *  then the inverse matrix is not unique.
     */
    det = sa_Det3x3(in);

    if ( fabs(det) < SA__SMALL_NUMBER)
        return 1;

    /* calculate the adjoint matrix */
    adjoint(out, in);

    /* scale the adjoint matrix to get the inverse */
    for (i = 0; i < 3; i++)
        for(j = 0; j < 3; j++)
		    out[i][j] /= det;
	return 0;
}

#undef SA__SMALL_NUMBER	
#undef det2x2

/* - - - - - - - - - - - - - - - - - - - - - - - - */
/* Transpose a 3x3 matrix */
void sa_Transpose3x3(double out[3][3], double in[3][3]) {
	int i, j;
	if (out != in) {
		for (i = 0; i < 3; i++)
			for (j = 0; j < 3; j++)
				out[i][j] = in[j][i];
	} else {
		double tt[3][3];
		for (i = 0; i < 3; i++)
			for (j = 0; j < 3; j++)
				tt[i][j] = in[j][i];
		for (i = 0; i < 3; i++)
			for (j = 0; j < 3; j++)
				out[i][j] = tt[i][j];
	}
}

/* Scale a 3 vector by the given ratio */
void sa_Scale3(double out[3], double in[3], double rat) {
	out[0] = in[0] * rat;
	out[1] = in[1] * rat;
	out[2] = in[2] * rat;
}

/* Clamp a 3 vector to be +ve */
void sa_Clamp3(double out[3], double in[3]) {
	int i;
	for (i = 0; i < 3; i++)
		out[i] = in[i] < 0.0 ? 0.0 : in[i];
}

/* Return the normal Delta E given two Lab values */
double sa_LabDE(double *Lab0, double *Lab1) {
	double rv = 0.0, tt;

	tt = Lab0[0] - Lab1[0];
	rv += tt * tt;
	tt = Lab0[1] - Lab1[1];
	rv += tt * tt;
	tt = Lab0[2] - Lab1[2];
	rv += tt * tt;

	return sqrt(rv);
}

/* - - - - - - - - - - - - - - - - - - - - - - - - */
/* A sub-set of ludecomp code from numlib          */

int sa_lu_decomp(double **a, int n, int *pivx, double *rip) {
	int    i, j;
	double *rscale, RSCALE[10];		

	if (n <= 10)
		rscale = RSCALE;
	else
		rscale = dvector(0, n-1);

	for (i = 0; i < n; i++) {
		double big;
		for (big = 0.0, j=0; j < n; j++) {
			double temp;
			temp = fabs(a[i][j]);
			if (temp > big)
				big = temp;
		}
		if (fabs(big) <= DBL_MIN) {
			if (rscale != RSCALE)
				free_dvector(rscale, 0, n-1);
			return 1;		
		}
		rscale[i] = 1.0/big;	
	}

	for (*rip = 1.0, j = 0; j < n; j++) {
		double big;
		int k, bigi = 0;

		for (i = 0; i < j; i++) {
			double sum;
			sum = a[i][j];
			for (k = 0; k < i; k++)
				sum -= a[i][k] * a[k][j];
			a[i][j] = sum;
		}

		for (big = 0.0, i = j; i < n; i++) {
			double sum, temp;
			sum = a[i][j];
			for (k = 0; k < j; k++)
				sum -= a[i][k] * a[k][j];
			a[i][j] = sum;
			temp = rscale[i] * fabs(sum);	
			if (temp >= big) {
				big = temp;		
				bigi = i;		
			}
		}
		
		if (j != bigi) {
			{	
				double *temp;
				temp = a[bigi];
				a[bigi] = a[j];
				a[j] = temp;
			}
			*rip = -(*rip);				
			rscale[bigi] = rscale[j];	
		}
		
		pivx[j] = bigi;					
		if (fabs(a[j][j]) <= DBL_MIN) {
			if (rscale != RSCALE)
				free_dvector(rscale, 0, n-1);
			return 1; 					
		}

		if (j != (n-1)) {
			double temp;
			temp = 1.0/a[j][j];
			for (i = j+1; i < n; i++)
				a[i][j] *= temp;
		}
	}
	if (rscale != RSCALE)
		free_dvector(rscale, 0, n-1);
	return 0;
}

void sa_lu_backsub(double **a, int n, int *pivx, double  *b) {
	int i, j;
	int nvi;		
	
	for (nvi = -1, i = 0; i < n; i++) {
		int px;
		double sum;

		px = pivx[i];
		sum = b[px];
		b[px] = b[i];
		if (nvi >= 0) {
			for (j = nvi; j < i; j++)
				sum -= a[i][j] * b[j];
		} else {
			if (sum != 0.0)
				nvi = i;			
		}
		b[i] = sum;
	}

	for (i = (n-1); i >= 0; i--) {
		double sum;
		sum = b[i];
		for (j = i+1; j < n; j++)
			sum -= a[i][j] * b[j];
		b[i] = sum/a[i][i];
	}
}

int sa_lu_invert(double **a, int n) {
	int i, j;
	double rip;		
	int *pivx, PIVX[10];
	double **y;

	if (n <= 10)
		pivx = PIVX;
	else
		pivx = ivector(0, n-1);

	if (sa_lu_decomp(a, n, pivx, &rip)) {
		if (pivx != PIVX)
			free_ivector(pivx, 0, n-1);
		return 1;
	}

	y = dmatrix(0, n-1, 0, n-1);
	for (i = 0; i < n; i++) {
		for (j = 0; j < n; j++) {
			y[i][j] = a[i][j];
		}
	}

	for (i = 0; i < n; i++) {
		for (j = 0; j < n; j++)
			a[i][j] = 0.0;
		a[i][i] = 1.0;
		sa_lu_backsub(y, n, pivx, a[i]);
	}

	free_dmatrix(y, 0, n-1, 0, n-1);
	if (pivx != PIVX)
		free_ivector(pivx, 0, n-1);

	return 0;
}

int sa_lu_psinvert(double **out, double **in, int m, int n) {
	int rv = 0;
	double **tr;		
	double **sq;		

	tr = dmatrix(0, n-1, 0, m-1);
	matrix_trans(tr, in, m,  n);

	if (m > n) {
		sq = dmatrix(0, n-1, 0, n-1);
		if ((rv = matrix_mult(sq, n, n, tr, n, m, in, m, n)) == 0) {
			if ((rv = sa_lu_invert(sq, n)) == 0) {
				rv = matrix_mult(out, n, m, sq, n, n, tr, n, m);
			}
		}
		free_dmatrix(sq, 0, n-1, 0, n-1);
	} else {
		sq = dmatrix(0, m-1, 0, m-1);
		if ((rv = matrix_mult(sq, m, m, in, m, n, tr, n, m)) == 0) {
			if ((rv = sa_lu_invert(sq, m)) == 0) {
				rv = matrix_mult(out, n, m, tr, n, m, sq, m, m);
			}
		}
		free_dmatrix(sq, 0, m-1, 0, m-1);
	}

	free_dmatrix(tr, 0, n-1, 0, m-1);
	return rv;
}


#endif /* SALONEINSTLIB */
/* ============================================================= */


