
/* Instrument command line application support functions */

/* 
 * Argyll Color Management System
 *
 * Author: Graeme W. Gill
 * Date:   10/3/2001
 *
 * Copyright 2001 - 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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#else
#include "sa_config.h"
#endif /* !SALONEINSTLIB */
#include "numsup.h"
#include "cgats.h"
#include "xspect.h"
#include "conv.h"
#include "insttypes.h"

#include "icoms.h"
#include "inst.h"
#include "rspec.h"
#include "insttypeinst.h"
#include "instappsup.h"

/* ================================================================= */
/* a default user interaction handler */

typedef struct _uicontext {
	int emit_ret;				/* Emit \n on inst_triggered */
	int cut;					/* The character that caused the termination */
	int uih[256];				/* User interrupt handling key table. Value can be: */
								/* DUIH_OK, DUIH_ABORT, DUIH_TERM, DUIH_TRIG, DUIH_CMND */
} uicontext;

static uicontext def_uicntx = { 1, 0, { 0 } };

static inst_code def_uicallback(void *cntx, inst_ui_purp purp) {
	uicontext *p = (uicontext *)cntx;

	if (purp == inst_triggered) {
		if (p->emit_ret)
			printf("\n");
		return inst_ok;

	} else if (purp == inst_negcoms
	        || purp == inst_armed
	        || purp == inst_measuring) {
		int c;

		c = poll_con_char();
		if (c != 0) {
			p->cut = c;
			c = p->uih[c];
			if (c & (DUIH_ABORT | DUIH_TERM | DUIH_CMND))
				return inst_user_abort;
			if (c & DUIH_TRIG)
				return inst_user_trig;
		}

	} else if (purp == inst_measuring) {
		return inst_ok;
	}
	return inst_ok;
}

/* Return the default uicallback function */
inst_code (*inst_get_uicallback())(void *, inst_ui_purp) {
	return &def_uicallback;
}

/* Return the default uicallback context */
void *inst_get_uicontext() {
	return (void *)&def_uicntx;
}

/* Install the default uicallback function in the given inst */
void inst_set_uicallback(inst *p) {
	p->set_uicallback(p, def_uicallback, (void *)&def_uicntx);
}

/* Set the return on trigger to true or false */
void inst_set_uicb_trigret(int set) {
	uicontext *p = &def_uicntx;
	p->emit_ret = set;
}

/* Reset user interaction handling to default (Esc, ^C, q or 'Q' = Abort) */
void inst_reset_uih() {
	uicontext *p = &def_uicntx;
	int i;

	for (i = 0; i < 255; i++)
		p->uih[i] = DUIH_NONE;

	p->uih[0x1b] = DUIH_ABORT;	/* Escape */
	p->uih['q']  = DUIH_ABORT;	/* q */
	p->uih['Q']  = DUIH_ABORT;	/* Q */
	p->uih[0x03] = DUIH_ABORT;	/* ^C */
}

/* Set a key range to the given handling type */
/* min & max are between 0 and 255, status is one of */
/* DUIH_OK, DUIH_USER, DUIH_TERM, DUIH_TRIG, DUIH_CMND */
void inst_set_uih(
int min,		/* Start key code */
int max,		/* End key code (inclusive) */
int status		/* ICOM_OK, ICOM_USER, ICOM_TERM, ICOM_TRIG, ICOM_CMND */
) {
	uicontext *p = &def_uicntx;
	int i;

	if (min < 0)
		min = 0;
	else if (min > 255)
		min = 255;
	if (max < 0)
		max = 0;
	else if (max > 255)
		max = 255;

	if (status != DUIH_NONE
	 && status != DUIH_ABORT
	 && status != DUIH_TERM
	 && status != DUIH_CMND
	 && status != DUIH_TRIG)
		status = DUIH_NONE;

	for (i = min; i <= max; i++) {
		p->uih[i] = status;
	}
}

/* Get the character that caused the user interrupt */
/* + its key type in the upper 8 bits. */
/* Clear it to 0x00 after reading it. */
int inst_get_uih_char() {
	uicontext *p = &def_uicntx;
	int c = p->cut;
	c |= p->uih[c];
	p->cut = 0;
	return c;
}

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

/* A default calibration user interaction handler using the console. */
/* This handles both normal and display based calibration interaction */
/* with the instrument, if a disp_setup function and pointer to disp_win_info */
/* is provided. */
inst_code inst_handle_calibrate(
	inst *p,
	inst_cal_type calt,		/* Calibration type to do */
	inst_cal_cond calc,		/* Current current condition */
	inst_code (*disp_setup) (inst *p,inst_cal_cond calc, disp_win_info *dwi),
							/* Callback for handling a display calibration - May be NULL */
	disp_win_info *dwi,		/* Information to be able to open a display test patch - May be NULL */
	int doimmediately		/* If nz, don't wait for user, calibrate immediatley */
) {
	inst_code rv = inst_ok, ev;
	int usermes = 0;			/* User was given a message */
	inst_calc_id_type idtype;	/* Condition identifier type */
	char id[200];				/* Condition identifier */
	int ch;

	a1logd(p->log,1,"inst_handle_calibrate called\n");

	p->last_cal_ec = 0;

	/* Untill we're done with the calibration */
	for (;;) {

		a1logd(p->log,1,"About to call calibrate at top of loop\n");
	    ev = p->calibrate(p, &calt, &calc, &idtype, id);
		a1logd(p->log,1,"Calibrate returned calt 0x%x, calc 0x%x, ev 0x%x\n",calt,calc,ev);

		/* We're done */
		if ((ev & inst_mask) == inst_ok) {
			if ((calc & inst_calc_cond_mask) == inst_calc_message) {
				/* (Or could create our own message text based on value of idtype) */
				printf("%s\n",id);
			}
			if (usermes)
				printf("Calibration complete\n");
			a1logd(p->log,1,"inst_handle_calibrate done 0x%x\n",ev);
			return ev;
		}

		/* User aborted */
		if ((ev & inst_mask) == inst_user_abort) {
			a1logd(p->log,1,"inst_handle_calibrate user aborted 0x%x\n",ev);
			return ev;
		}

		/* Retry on an error */
		if ((ev & inst_mask) != inst_cal_setup) {
			if ((ev & inst_mask) == inst_unsupported) {
				a1logd(p->log,1,"inst_handle_calibrate err 0x%x, calibration type 0x%x not supported\n",ev, calt);
				return inst_unsupported;
			}

			printf("Calibration failed with '%s' (%s)\n",
				       p->inst_interp_error(p, ev), p->interp_error(p, ev));

			p->last_cal_ec = ev;		/* So caller can see what happened */

			if (doimmediately)
				return inst_user_abort;

			printf("Hit any key to retry, or Esc or Q to abort:\n");

			empty_con_chars();
			ch = next_con_char();
			printf("\n");
			if (ch == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
				a1logd(p->log,1,"inst_handle_calibrate user aborted 0x%x\n",inst_user_abort);
				return inst_user_abort;
			}

		} else {

			printf("\n");

			/* Get user to do/setup calibration */
			switch (calc & inst_calc_cond_mask) {
				case inst_calc_uop_ref_white:
					printf("Do a reflective white calibration,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_uop_trans_white:
					printf("Do a transmissive white calibration,\n");
					printf(" and then hit any key to continue,\n"); 
					break;
			
				case inst_calc_uop_trans_dark:
					printf("Do a transmissive dark calibration,\n");
					printf(" and then hit any key to continue,\n"); 
					break;
			
				case inst_calc_man_ref_white:
					printf("Place the instrument on its reflective white reference S/N %s,\n",id);
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_ref_whitek:
					printf("Click the instrument on its reflective white reference %s,\n",id);
					break;

				case inst_calc_man_ref_dark:
					printf("Place the instrument on light trap, or in the dark,\n");
					printf("and distant from any surface,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_dark_gloss:
					printf("Place the instrument on black gloss reference\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_em_dark:
					printf("Place cap on the instrument, or place on a dark surface,\n");
					printf("or place on the calibration reference,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_am_dark:
					printf("Place ambient adapter and cap on the instrument,\n");
					printf("or place on the calibration reference,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_cal_smode:
					printf("Set instrument sensor to calibration position,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_trans_white:
					printf("Place the instrument on its transmissive white source,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_man_trans_dark:
					printf("Use the appropriate tramissive blocking to block the transmission path,\n");
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_change_filter:
					printf("Change filter on instrument to %s,\n",id);
					printf(" and then hit any key to continue,\n"); 
					break;

				case inst_calc_message:
					printf("%s\n",id);
					printf(" Hit any key to continue,\n"); 
					break;

				case inst_calc_emis_white:
					if (disp_setup == NULL || dwi == NULL) { /* No way of creating a test window */
						printf("Place the instrument on a 100%% white test patch,\n");
						printf(" and then hit any key to continue,\n"); 
					} else {
						/* We need to display a 100% white patch to proceed with this */
						/* type of calibration */
						if ((rv = disp_setup(p, calc, dwi)) != inst_ok)
							return rv; 
					}
					break;

				case inst_calc_emis_80pc:
					if (disp_setup == NULL || dwi == NULL) { /* No way of creating a test window */
						printf("Place the instrument on a 80%% white test patch,\n");
						printf(" and then hit any key to continue,\n"); 
					} else {
						/* We need to display a 80% white patch to proceed with this */
						/* type of calibration */
						if ((rv = disp_setup(p, calc, dwi)) != inst_ok)
							return rv; 
					}
					break;

				case inst_calc_emis_grey:
				case inst_calc_emis_grey_darker: 
				case inst_calc_emis_grey_ligher:
					if (dwi == NULL) {	/* No way of creating a test window */
						if ((calc & inst_calc_cond_mask) == inst_calc_emis_grey) {
							p->cal_gy_level = 0.6;
							p->cal_gy_count = 0;
						} else if ((calc & inst_calc_cond_mask) == inst_calc_emis_grey_darker) {
							p->cal_gy_level *= 0.7;
							p->cal_gy_count++;
						} else if ((calc & inst_calc_cond_mask) == inst_calc_emis_grey_ligher) {
							p->cal_gy_level *= 1.4;
							if (p->cal_gy_level > 1.0)
								p->cal_gy_level = 1.0;
							p->cal_gy_count++;
						}
						if (p->cal_gy_count > 4) {
							printf("Cell ratio calibration failed - too many tries at setting grey level.\n");
							a1logd(p->log,1,"inst_handle_calibrate too many tries at setting grey level 0x%x\n",inst_internal_error);
							return inst_internal_error; 
						} else {
							printf("Place the instrument on a %d%% white test patch,\n", (int)(p->cal_gy_level * 100.0 + 0.5));
							printf(" and then hit any key to continue,\n"); 
						}
					} else {

						/* We need to display a test patch to proceed with this
						 * type of calibration. Typically this will be:
						 *
						 * inst_calc_xxxx_grey:
						 *		set p->cal_gy_level = 0.6
						 *		set p->cal_gy_count = 0;
						 *
						 * inst_calc_xxxx_grey_darker:
						 *		set p->cal_gy_level *= 0.7
						 *		set p->cal_gy_count++
						 *
						 * inst_calc_xxxx_grey_ligher:
						 *		set p->cal_gy_level *= 1.4
						 *		set p->cal_gy_count++
						 *
						 * and return failure if p->cal_gy_count > 4
						 */

						if ((rv = disp_setup(p, calc, dwi)) != inst_ok)
							return rv; 
					}
					break;

				default:
					/* Something isn't being handled */
					a1logd(p->log,1,"inst_handle_calibrate unhandled calc case 0x%x, err 0x%x\n",calc,inst_internal_error);
					return inst_internal_error;
			}
			if (calc & inst_calc_optional_flag)
				printf(" or hit Esc or Q to abort, or S to skip: %s",fl_end); 
			else
				printf(" or hit Esc or Q to abort: %s",fl_end); 
			do_fflush();

			usermes = 1;


			/* If we should wait for user to say we're in the right condition, */
			/* and this isn't a calibration that requires clicking the instrument */
			/* on the calibration tile, then wait for the an OK or abort or skip. */
			if (!doimmediately
			 && (calc & inst_calc_cond_mask) != inst_calc_man_ref_whitek) {
				empty_con_chars();
				ch = next_con_char();
				printf("\n");

				/* If optional calib. and user wants to skip it */
				/* Loop back to calibrate() with inst_calc_optional_flag still set */
				if ((calc & inst_calc_optional_flag) != 0 && (ch == 's' || ch == 'S')) {
					printf("Skipped\n");
					goto oloop;
				}
				if (ch == 0x1b || ch == 0x3 || ch == 'q' || ch == 'Q') {
					a1logd(p->log,1,"inst_handle_calibrate user aborted 0x%x\n",inst_user_abort);
					return inst_user_abort;
				}
			}
			/* Remove any skip flag and continue with the calibration */
			calc &= inst_calc_cond_mask;
		}
 oloop:;
	}
}

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

/* A helper function to display -y flag usage for each instrument type available */
/* Return accumulated capabilities2 of all the instruments */
/* Return all possible capabilities if there are no instruments */
/* If docbib is nz, then only display the base calibration display types */
/* If openallisnt is nz, then open even slow serial instruments for specific info. */
inst2_capability inst_show_disptype_options(FILE *fp, char *oline, icompaths *icmps,
                                            int docbib, int openallinst) { 
	int i, j;
	char buf[200], *bp;
	char extra[40];
	int olen, pstart;
	int onlyidinst = 1;			/* Only open instruments that can be id'd without opening */
	int notall = 0;				/* Not all instruments are USB */
	int gotone = 0;				/* Found at least one USB instrument */
	inst2_capability acap = 0;	/* Accumulate capabilities */

	if (icmps == NULL)
		return 0;

	if (openallinst)
		onlyidinst = 0;			/* Slowly ID and open all instrument */

	/* Locate the end of the option */
	for (bp = oline; *bp != '\000' && *bp == ' '; bp++)
		;
	for (; *bp != '\000' && *bp != ' '; bp++)
		;
	pstart = bp - oline;
	if (pstart > 10)
		pstart = 10;
	strncpy(buf, oline, pstart); 
	buf[pstart++] = ' ';

	olen = strlen(oline);		/* lenth of option part of line */

	for (i = 0; icmps != NULL && i < icmps->ndpaths[dtix_inst]; i++) {
		inst *it;
		inst2_capability cap;
		int k;

		if ((it = new_inst(icmps->dpaths[dtix_inst][i], onlyidinst, g_log, NULL, NULL)) == NULL) {
			notall = 1;
			continue;
		}
		gotone = 1;

		it->capabilities(it, NULL, &cap, NULL);
		acap |= cap;

		if (cap & inst2_disptype) {
			int nsel;
			inst_disptypesel *sels;

			if (it->get_disptypesel(it, &nsel, &sels, 1, 0) != inst_ok) {
				it->del(it);
				continue;
			}
			for (j = 0; j < nsel; j++) {
				int m;

				if (docbib && sels[j].cbid == 0)
					continue;			/* Skip non cbid type */

				/* Break up selector chars/pairs with '|' */
				m = pstart;
				for (k = 0; k < (INST_DTYPE_SEL_LEN-1); k++) {
					if (sels[j].sel[k] == '\000')
						break;
					if (m > pstart)
						buf[m++] = '|';
					buf[m++] = sels[j].sel[k];
					if (sels[j].sel[k] == '_')
						buf[m++] = sels[j].sel[++k];
				}
				while (m < (olen+1))	/* Indent it by 1 */
					buf[m++] = ' ';
				buf[m++] = '\000';
				
				extra[0] = '\000';
				if ((sels[j].flags & inst_dtflags_default) || sels[j].cbid != 0) {
					strcat(extra, " [");
			        if (sels[j].flags & inst_dtflags_default) {
						strcat(extra, "Default");
						if (sels[j].cbid != 0)
							strcat(extra, ",");
					}
					if (sels[j].cbid != 0) {
						sprintf(extra + strlen(extra), "CB%d",sels[j].cbid);
					}
					strcat(extra, "]");
				}

				fprintf(fp, "%s%s: %s%s\n",buf, inst_sname(it->dtype), sels[j].desc, extra);

				if (j == 0) {
					for (m = 0; m < pstart; m++)
						buf[m] = ' ';
				}
			}
		}
		it->del(it);
	}
	/* Output a default desciption if not all instruments are USB */
	if (notall) {
		int m = pstart;
		buf[m++] = 'l';
		buf[m++] = '|';
		buf[m++] = 'c';
		while (m < olen)
			buf[m++] = ' ';
		buf[m++] = '\000';
		fprintf(fp, "%s%s\n",buf, " Other: l = LCD, c = CRT");
	}
	if (!gotone)
		acap = ~0;

	return acap;
}

/* A helper function to turn a -y flag into a selection index */
/* If docbib is nz, then only allow base calibration display types */
/* c will be 16 bits ('_' + 'X') if a 2 char selector */
/* Return -1 on error */
int inst_get_disptype_index(inst *it, int ditype, int docbib) {
	inst2_capability cap;
	int j, k;

	it->capabilities(it, NULL, &cap, NULL);

	if (cap & inst2_disptype) {
		int nsel;
		inst_disptypesel *sels;

		if (it->get_disptypesel(it, &nsel, &sels, 1, 0) != inst_ok) {
			return -1;
		}
		for (j = 0; j < nsel; j++) {
			if (docbib && sels[j].cbid == 0)
				continue;			/* Skip non cbid type */

			for (k = 0; k < (INST_DTYPE_SEL_LEN-1); k++) {
				if (sels[j].sel[k] == '\000')
					break;
				if (sels[j].sel[k] == '_') {	/* 2 char selector */
					k++;
					if (sels[j].sel[k-1] == (0xff & (ditype >> 8))
					 && sels[j].sel[k] == (0xff & ditype)) {
						return j;
					}
				}
				if (sels[j].sel[k] == ditype) {
					return j;
				}
			}
		}
	}
	return -1;
}

/* Return a static string of the ditype flag char(s) */
char *inst_distr(int ditype) {
	static char buf[5];
	
	if ((ditype >> 8) & 0xff) {
		buf[0] = (ditype >> 8) & 0xff;
		buf[1] = ditype & 0xff;
		buf[2] = '\000';
	} else {
		buf[0] = ditype;
		buf[1] = '\000';
	}

	return buf;
}
/* ================================================================= */




















