/**
 * \brief Perform a measurement at a certain focus position
 */
FocusValue	MeasureFocusWork::measureat(unsigned short pos) {
	debug(LOG_DEBUG, DEBUG_LOG, 0, "measurement at pos = %hu", pos);
	// move to the position
	focusingstatus(Focusing::MOVING);
	moveto(pos);

	// get an image
	focusingstatus(Focusing::MEASURING);
	ccd()->startExposure(exposure());
	ccd()->wait();
	ImagePtr	image = ccd()->getImage();

	// evaluate the image
	MeasureEvaluator	evaluator;
	double	value = evaluator(image);
	
	debug(LOG_DEBUG, DEBUG_LOG, 0, "pos = %hu, value = %g(%f)",
		pos, value, log10(value));

	// call the callback
	callback(evaluator.evaluated_image(), pos, value);

	// return the focus information
	return FocusValue(pos, value);
}
/**
 * \brief default main function for focusing
 */
void	FocusWork::main(astro::thread::Thread<FocusWork>& thread) {
	if (!complete()) {
		debug(LOG_ERR, DEBUG_LOG, 0,
			"FocusWork is not completely configured");
		focusingstatus(Focusing::FAILED);
		return;
	}
	debug(LOG_DEBUG, DEBUG_LOG, 0, "starting focus process in [%d,%d]",
		min(), max());

	// prepare the set of focus items to base the focus computation on
	FocusItems	focusitems;

	// prepare 
	for (int step = 0; step < steps(); step++) {
		// find position
		unsigned short	position
			= min() + (step * (max() - min())) / (steps() - 1);
		debug(LOG_DEBUG, DEBUG_LOG, 0, "next position: %hu", position);

		// move to this position
		moveto(position);

		// get an image
		focusingstatus(Focusing::MEASURING);
		ccd()->startExposure(exposure());
		usleep(1000000 * exposure().exposuretime());
		ccd()->wait();
		ImagePtr	image = ccd()->getImage();
		debug(LOG_DEBUG, DEBUG_LOG, 0, "got an image of size %s",
			image->size().toString().c_str());

		// evaluate the image
		double	value = (*evaluator())(image);
		debug(LOG_DEBUG, DEBUG_LOG, 0, "evaluated to %f", value);

		// callback with the evaluated image
		callback(evaluator()->evaluated_image(), position, value);

		// add the information to a set
		focusitems.insert(FocusItem(position, value));
	}

	// now solve we need a suitable solver for the method
	int	targetposition = solver()->position(focusitems);
	if ((targetposition < min()) || (targetposition > max())) {
		std::string	msg = stringprintf(
			"could not find a focus position: %d", targetposition);
		debug(LOG_ERR, DEBUG_LOG, 0, "%s", msg.c_str());
		focusingstatus(Focusing::FAILED);
		return;
	}

	// move to the final focus position
	focusingstatus(Focusing::MOVING);
	moveto(targetposition);
	focusingstatus(Focusing::FOCUSED);
}
/**
 * \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 Main function of the Focusing process
 */
void	MeasureFocusWork::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");
	}
	counter = 0;

	// move to the minimum
	debug(LOG_DEBUG, DEBUG_LOG, 0, "measure left end of interval: %hu",
		min());
	FocusValue	left = measureat(min());
	FocusValue	right = measureat(max());
	
	// perform measurements at both ends of the interval
	FocusInterval	interval(left, right);

	// perform subdivisions
	double	resolution = (max() - min()) / pow(2, steps());
	debug(LOG_DEBUG, DEBUG_LOG, 0, "target resolution: %f", resolution);
	std::list<FocusInterval>	intervals;
	intervals.push_back(interval);
	try {
		while (interval.length() > resolution) {
			try {
				interval = subdivide(interval);
				intervals.push_back(interval);
			} catch (wronginterval) {
				debug(LOG_DEBUG, DEBUG_LOG, 0,
					"retrying other interval");
				intervals.pop_back();
				interval = *intervals.rbegin() - interval;
				intervals.push_back(interval);
			}
			debug(LOG_DEBUG, DEBUG_LOG, 0, "new interval: %s",
				interval.toString().c_str());
		}
		focusingstatus(Focusing::FOCUSED);
	} catch (std::exception& x) {
		debug(LOG_DEBUG, DEBUG_LOG, 0, "focus failed: %s", x.what());
		focusingstatus(Focusing::FAILED);
	} catch (...) {
		debug(LOG_DEBUG, DEBUG_LOG, 0, "unknown exception during focus");
		focusingstatus(Focusing::FAILED);
	}
}
/**
 * \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");
}