/**
 * \brief Main function for the snowfocus program
 */
int	main(int argc, char *argv[]) {
	snowstar::CommunicatorSingleton	cs(argc, argv);

	std::string	instrumentname;
	int	steps = 10;
	double	exposuretime = 1.0;
	double	temperature = std::numeric_limits<double>::quiet_NaN();
	std::string	binning;
	std::string	frame;
	std::string	filtername;
	astro::focusing::Focusing::method_type	method
		= astro::focusing::Focusing::FWHM;

	int	c;
	int	longindex;
	while (EOF != (c = getopt_long(argc, argv, "b:c:de:f:hi:m:r:t:",
		longopts, &longindex)))
		switch (c) {
		case 'b':
			binning = optarg;
			break;
		case 'c':
			astro::config::Configuration::set_default(optarg);
			break;
		case 'd':
			debuglevel = LOG_DEBUG;
			break;
		case 'e':
			exposuretime = std::stod(optarg);
			break;
		case 'f':
			filtername = optarg;
			break;
		case 'h':
			usage(argv[0]);
			return EXIT_SUCCESS;
		case 'i':
			instrumentname = optarg;
			break;
		case 'm':
			method = astro::focusing::Focusing::string2method(optarg);
			break;
		case 'r':
			frame = optarg;
			break;
		case 's':
			steps = std::stoi(optarg);
			break;
		case 't':
			temperature = std::stod(optarg);
			break;
		}

	// the next argument is the command
	if (argc <= optind) {
		throw std::runtime_error("not enough arguments");
	}
	std::string	command = argv[optind++];
	debug(LOG_DEBUG, DEBUG_LOG, 0, "command: %s", command.c_str()); 
	if (command == "help") {
		usage(argv[0]);
		return EXIT_SUCCESS;
	}


	// get the configuration
	ConfigurationPtr	config = Configuration::get();

	// check whether we have an instrument
	if (0 == instrumentname.size()) {
		throw std::runtime_error("instrument name not set");
	}
	RemoteInstrument	instrument(config->database(),
						instrumentname);
	// get the device names
	CcdPrx	ccdprx = instrument.ccd_proxy();
	std::string	ccdname = ccdprx->getName();
	FocuserPrx	focuserprx = instrument.focuser_proxy();
	std::string	focusername = focuserprx->getName();
	debug(LOG_DEBUG, DEBUG_LOG, 0, "ccd: %s focuser: %s", ccdname.c_str(),
		focusername.c_str());

	// first get a connection to the server
	Ice::CommunicatorPtr	ic = CommunicatorSingleton::get();
	astro::ServerName	servername
		= instrument.servername(astro::DeviceName::Ccd);
	Ice::ObjectPrx	base = ic->stringToProxy(
				servername.connect("FocusingFactory"));
	FocusingFactoryPrx	focusingfactory
		= FocusingFactoryPrx::checkedCast(base);

	// get the focusing interface
	FocusingPrx	focusing = focusingfactory->get(ccdname, focusername);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "got a focusing proxy");

	// creating a callback
	Ice::ObjectPtr	callback = new FocusCallbackI();
	CallbackAdapter	adapter(ic);
	Ice::Identity	ident = adapter.add(callback);
	focusing->ice_getConnection()->setAdapter(adapter.adapter());

	// handle the simple commands
	if (command == "status") {
		std::cout << "status: ";
		std::cout << focusingstate2string(focusing->status());
		std::cout << std::endl;
		return EXIT_SUCCESS;
	}
	if (command == "history") {
		show_history(focusing->history());
		return EXIT_SUCCESS;
	}
	if (command == "monitor") {
		std::cout << "current status: ";
		std::cout << focusingstate2string(focusing->status());
		std::cout << std::endl;
		focusing->registerCallback(ident);
		signal(SIGINT, handler);
		while (!signal_received) {
			sleep(1);
		}
		focusing->unregisterCallback(ident);
		return EXIT_SUCCESS;
	}

	if (command == "cancel") {
		focusing->cancel();
		std::cout << "cancel command sent" << std::endl;
		return EXIT_SUCCESS;
	}

	// throw exception for unknown commands
	if (command != "start") {
		throw std::runtime_error("unknown command");
	}

	// make sure temperature is set
	CoolerPrx	cooler;
	if (instrument.has(DeviceName::Cooler)) {
		cooler = instrument.cooler_proxy();
	}
	CoolerTask      coolertask(cooler, temperature);
	coolertask.wait();

	// next two arguments are the interval boundaries
	if ((argc - optind) < 2) {
		throw std::runtime_error("missing intervale arguments");
	}
	int	min = std::stoi(argv[optind++]);
	int	max = std::stoi(argv[optind++]);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "interval [%d,%d]", min, max);
	if (min >= max) {
		throw std::runtime_error("not an interval");
	}


	// ensure that focuser is ready
	FocusState	state = focusing->status();
	debug(LOG_DEBUG, DEBUG_LOG, 0, "current state = %d", state);
	if ((state == FocusMOVING) && (state == FocusMEASURING)) {
		throw std::runtime_error("already focusing");
	}

	// set up the exposure
	CcdTask	ccdtask(ccdprx);
	ccdtask.frame(frame);
	ccdtask.binning(binning);
	ccdtask.exposuretime(exposuretime);

	// set up the focusing
	focusing->setSteps(steps);
	focusing->setMethod(convert(method));

	// start the focusing process
	debug(LOG_DEBUG, DEBUG_LOG, 0, "starting between %d and %d", min, max);
	focusing->start(min, max);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "focusing started, status: %d",
		focusing->status());

	// wait for the process to complete
	bool	completed = false;
	signal(SIGINT, handler);
	do {
		sleep(1);
		switch (focusing->status()) {
        	case FocusIDLE:
        	case FocusMOVING:
        	case FocusMEASURING:
			break;
        	case FocusFOCUSED:
        	case FocusFAILED:
			completed = true;
			break;
		}
	} while ((!completed) && (!signal_received));
	if (completed) {
		std::cout << "final focus position: " << focuserprx->current();
		std::cout << std::endl;

		// display the history
		show_history(focusing->history());
	} else {
		std::cout << "focusing incomplete" << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}
/**
 * \brief Main function for the snowfocus program
 */
int	main(int argc, char *argv[]) {
	debug_set_ident("snowfocus");
	snowstar::CommunicatorSingleton	cs(argc, argv);
	Ice::CommunicatorPtr	ic = cs.get();

	bool	remote = false;
	int	steps = 10;
	double	exposuretime = 1.0;
	double	temperature = std::numeric_limits<double>::quiet_NaN();
	std::string	binning;
	std::string	frame;
	std::string	filtername;
	astro::focusing::Focusing::method_type	method
		= astro::focusing::Focusing::BRENNER;

	int	c;
	int	longindex;
	while (EOF != (c = getopt_long(argc, argv, "b:c:de:f:hi:m:r:Rt:",
		longopts, &longindex)))
		switch (c) {
		case 'b':
			binning = optarg;
			break;
		case 'c':
			astro::config::Configuration::set_default(optarg);
			break;
		case 'd':
			debuglevel = LOG_DEBUG;
			break;
		case 'e':
			exposuretime = std::stod(optarg);
			break;
		case 'f':
			filtername = optarg;
			break;
		case 'h':
			usage(argv[0]);
			return EXIT_SUCCESS;
		case 'm':
			method = astro::focusing::Focusing::string2method(optarg);
			break;
		case 'r':
			frame = optarg;
			break;
		case 'R':
			remote = true;
			break;
		case 's':
			steps = std::stoi(optarg);
			break;
		case 't':
			temperature = std::stod(optarg);
			break;
		default:
			throw std::runtime_error("unknown option");
		}

	// the next argument is the command
	if (argc <= optind) {
		short_usage(argv[0]);
		throw std::runtime_error("missing service argument");
	}
	std::string	argument(argv[optind++]);
	if ("help" == argument) {
		usage(argv[0]);
		return EXIT_SUCCESS;
	}
	astro::ServerName	servername(argument);
	if (argc <= optind) {
		short_usage(argv[0]);
		throw std::runtime_error("missing instrument name argument");
	}

	// make sure the server offers instruments and guiding
	if (!remote) {
		astro::discover::ServiceDiscoveryPtr     sd
			= astro::discover::ServiceDiscovery::get();
		sd->start();
		astro::discover::ServiceObject  so
			= sd->find(sd->waitfor(argument));
		if (!so.has(astro::discover::ServiceSubset::INSTRUMENTS)) {
			std::cerr << "service '" << argument;
			std::cerr << "' does not offer focusing service";
			std::cerr << std::endl;
			return EXIT_FAILURE;
		}
		if (!so.has(astro::discover::ServiceSubset::FOCUSING)) {
			std::cerr << "service '" << argument;
			std::cerr << "' does not offer focusing service";
			std::cerr << std::endl;
			return EXIT_FAILURE;
		}
	}

	std::string	instrumentname(argv[optind++]);
	if (argc <= optind) {
		short_usage(argv[0]);
		throw std::runtime_error("missing command argument");
	}
	std::string	command = argv[optind++];
	debug(LOG_DEBUG, DEBUG_LOG, 0, "command: %s", command.c_str()); 

	// get a proxy for the instruments
	Ice::ObjectPrx	base = ic->stringToProxy(
				servername.connect("Instruments"));
	InstrumentsPrx	instruments = InstrumentsPrx::checkedCast(base);

	// get the configuration
	astro::config::ConfigurationPtr	config
		= astro::config::Configuration::get();

	// check whether we have an instrument
	if (0 == instrumentname.size()) {
		throw std::runtime_error("instrument name not set");
	}
	RemoteInstrument	instrument(instruments, instrumentname);

	// make sure the server names for focuser and ccd are identical
	astro::ServerName	targetserver
					= instrument.servername(InstrumentCCD);
	if (targetserver != instrument.servername(InstrumentFocuser)) {
		throw std::runtime_error("ccd and focuser are on different "
			"servers");
	}

	// get the device names
	CcdPrx	ccdprx = instrument.ccd();
	std::string	ccdname = ccdprx->getName();
	FocuserPrx	focuserprx = instrument.focuser();
	std::string	focusername = focuserprx->getName();
	debug(LOG_DEBUG, DEBUG_LOG, 0, "ccd: %s focuser: %s", ccdname.c_str(),
		focusername.c_str());

	// first get a connection to the server
	Ice::ObjectPrx	fbase = ic->stringToProxy(
				targetserver.connect("FocusingFactory"));
	FocusingFactoryPrx	focusingfactory
		= FocusingFactoryPrx::checkedCast(fbase);

	// get the focusing interface
	FocusingPrx	focusing = focusingfactory->get(ccdname, focusername);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "got a focusing proxy");

	// creating a callback
	Ice::ObjectPtr	callback = new FocusCallbackI();
	CallbackAdapter	adapter(ic);
	Ice::Identity	ident = adapter.add(callback);
	focusing->ice_getConnection()->setAdapter(adapter.adapter());
	debug(LOG_DEBUG, DEBUG_LOG, 0, "callback installed");

	// handle the simple commands
	if (command == "help") {
		short_usage(argv[0]);
		return EXIT_SUCCESS;
	}

	if (command == "status") {
		std::cout << "status: ";
		FocusState	state = focusing->status();
		std::cout << focusingstate2string(state);
		switch (state) {
		case FocusFOCUSED:
			std::cout << " ";
			std::cout << focusing->getFocuser()->current();
			break;
		default:
			break;
		}
		std::cout << std::endl;
		return EXIT_SUCCESS;
	}

	if (command == "history") {
		show_history(focusing->history());
		return EXIT_SUCCESS;
	}

	if (command == "monitor") {
		std::cout << "current status: ";
		std::cout << focusingstate2string(focusing->status());
		std::cout << std::endl;
		focusing->registerCallback(ident);
		signal(SIGINT, handler);
		while (!signal_received) {
			sleep(1);
		}
		focusing->unregisterCallback(ident);
		return EXIT_SUCCESS;
	}

	if (command == "cancel") {
		focusing->cancel();
		std::cout << "cancel command sent" << std::endl;
		return EXIT_SUCCESS;
	}

	if (command == "repository") {
		if (optind < argc) {
			std::string	reponame(argv[optind]);
			focusing->setRepositoryName(reponame);
		} else {
			std::string	reponame
				= focusing->getRepositoryName();
			if (reponame.size() > 0) {
				std::cout << "repository: " << reponame;
				std::cout << std::endl;
			} else {
				std::cout << "repository not set" << std::endl;
			}
		}
		return EXIT_SUCCESS;
	}

	// throw exception for unknown commands
	if (command != "start") {
		short_usage(argv[0]);
		throw std::runtime_error("unknown command");
	}
	debug(LOG_DEBUG, DEBUG_LOG, 0, "executing start command");

	// make sure temperature is set
	CoolerTask	coolertask(instrument, temperature);
	coolertask.stop_on_exit(true);
	coolertask.wait();

	// next two arguments are the interval boundaries
	if ((argc - optind) < 2) {
		short_usage(argv[0]);
		throw std::runtime_error("missing interval arguments");
	}
	int	min = std::stoi(argv[optind++]);
	int	max = std::stoi(argv[optind++]);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "interval [%d,%d]", min, max);
	if (min >= max) {
		short_usage(argv[0]);
		throw std::runtime_error("not an interval");
	}
	debug(LOG_DEBUG, DEBUG_LOG, 0, "focusing in interval [%d,%d]",
		min, max);

	// ensure that focuser is ready
	FocusState	state = focusing->status();
	debug(LOG_DEBUG, DEBUG_LOG, 0, "current state = %d", state);
	if ((state == FocusMOVING) && (state == FocusMEASURING)) {
		short_usage(argv[0]);
		throw std::runtime_error("already focusing");
	}
	debug(LOG_DEBUG, DEBUG_LOG, 0, "focuser available");

	// set up the exposure
	astro::camera::Exposure	exposure;
	exposure.purpose(astro::camera::Exposure::focus);
	exposure.exposuretime(exposuretime);
	if (binning.size() > 0) {
		exposure.mode(astro::image::Binning(binning));
	}
	exposure.shutter(astro::camera::Shutter::OPEN);
	if (frame.size() > 0) {
		exposure.frame(astro::image::ImageRectangle(frame));
	}

	// set up the focusing
	focusing->setSteps(steps);
	focusing->setMethod(convert(method));
	focusing->setExposure(convert(exposure));
	debug(LOG_DEBUG, DEBUG_LOG, 0, "focusing set up %d steps, method %s",
		steps,
		astro::focusing::Focusing::method2string(method).c_str());

	// start the focusing process
	debug(LOG_DEBUG, DEBUG_LOG, 0, "starting between %d and %d", min, max);
	focusing->start(min, max);
	debug(LOG_DEBUG, DEBUG_LOG, 0, "focusing started, status: %d",
		focusing->status());

	// wait for the process to complete
	bool	completed = false;
	signal(SIGINT, handler);
	do {
		sleep(1);
		switch (focusing->status()) {
        	case FocusIDLE:
        	case FocusMOVING:
        	case FocusMEASURING:
			break;
        	case FocusFOCUSED:
        	case FocusFAILED:
			completed = true;
			break;
		}
	} while ((!completed) && (!signal_received));
	if (completed) {
		std::cout << "final focus position: " << focuserprx->current();
		std::cout << std::endl;

		// display the history
		show_history(focusing->history());
	} else {
		std::cout << "focusing incomplete" << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}