/**
 * \brief Move Focuser to a given position
 *
 * This method ensures that the movement always comes from the same side.
 * If the current position below the new position, nothing needs to be
 * done. If however the current position is above the new position then
 * the focuser is first moved to the target position minus the backlash
 * amount before being moved to the target position.
 */
void	FocusWork::moveto(unsigned short position) {
	// ensure we are inside the interval
	if (position < min()) {
		throw std::runtime_error("internal error: Focuser move below min");
	}
	if (position > max()) {
		throw std::runtime_error("interval error: focuser move above max()");
	}

	// switch state to moving
	focusingstatus(Focusing::MOVING);

	// check whether backlash compensation is needed
	if ((backlash() > 0) && (focuser()->current() > position)) {
		unsigned short	compensated = position - backlash();
		if (position < backlash()) {
			debug(LOG_WARNING, DEBUG_LOG, 0,
				"not enough room for backlash: current = %hu, "
				"position = %hu, backlash = %hu",
				focuser()->current(), position, backlash());
			compensated = 0;
		}
		debug(LOG_DEBUG, DEBUG_LOG, 0,
			"moving to compensated position: %hu", compensated);
		focuser()->moveto(compensated);
	}

	// now move to the final position
	debug(LOG_DEBUG, DEBUG_LOG, 0, "move to final position: %hu",
		position);
	focuser()->moveto(position);
}
/**
 * \brief Find backlash amount from Focuser
 */
unsigned short	FocusWork::backlash() {
	return (focuser()) ? focuser()->backlash() : 0;
}
/**
 * \brief Main function of the Focusing process
 */
void	VCurveFocusWork::main(astro::thread::Thread<FocusWork>& /* thread */) {
	debug(LOG_DEBUG, DEBUG_LOG, 0, "start focusing work");
	if (!complete()) {
		focusingstatus(Focusing::FAILED);
		throw std::runtime_error("focuser not completely specified");
	}

	FocusCompute	fc;

	// determine how many intermediate steps we want to access

	if (min() < focuser()->min()) {
		throw std::runtime_error("minimum too small");
	}

	// based on the exposure specification, build an evaluator
	ImageSize	size = exposure().size();
	int	radius = std::min(size.width(), size.height()) / 2;
	FWHM2Evaluator	evaluator(size.center(), radius);

	unsigned long	delta = max() - min();
	for (int i = 0; i < steps(); i++) {
		// compute new position
		unsigned short	position = min() + (i * delta) / (steps() - 1);
		debug(LOG_DEBUG, DEBUG_LOG, 0, "measuring position %hu",
			position);

		// move to new position
		moveto(position);
		
		// get an image from the Ccd
		focusingstatus(Focusing::MEASURING);
		ccd()->startExposure(exposure());
		usleep(1000000 * exposure().exposuretime());
		ccd()->wait();
		ImagePtr	image = ccd()->getImage();
		
		// turn the image into a value
		FWHMInfo	fwhminfo = focusFWHM2_extended(image,
					size.center(), radius);
		double	value = fwhminfo.radius;

		// add the new value 
		fc.insert(std::pair<unsigned short, double>(position, value));

		// send the callback data
		callback(combine(image, fwhminfo), position, value);
	}

	// compute the best focus position
	double	focusposition = 0;
	try {
		focusposition = fc.focus();
	} catch (std::exception& x) {
		debug(LOG_DEBUG, DEBUG_LOG, 0, "no optimal focus position: %s",
			x.what());
		focusingstatus(Focusing::FAILED);
		return;
	}
	debug(LOG_DEBUG, DEBUG_LOG, 0, "optimal focus position: %f",
		focusposition);

	// plausibility check for the position
	if (!((focusposition >= min()) && (focusposition <= max()))) {
		focusingstatus(Focusing::FAILED);
		debug(LOG_DEBUG, DEBUG_LOG, 0, "focusing failed");
		return;
	}

	// move to the focus position
	unsigned short	targetposition = focusposition;
	moveto(targetposition);
	focusingstatus(Focusing::FOCUSED);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "target position reached");
}