// Copyright 2004 David Hilvert <dhilvert@auricle.dyndns.org>,
//                              <dhilvert@ugcs.caltech.edu>

/*  This file is part of the Anti-Lamenessing Engine.

    The Anti-Lamenessing Engine is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    The Anti-Lamenessing Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with the Anti-Lamenessing Engine; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef __ui_h__
#define __ui_h__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include "../ale_pos.h"
#include "../config.h"
#if HAVE_TIME_H
#include <time.h>
#endif
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <map>

/*
 * Time structures.
 */

namespace d2 {
	struct trans_multi;
	typedef trans_multi transformation;
	struct trans_abstract;
	struct trans_single;
}

class ale_timer_t {
#if HAVE_GETTIMEOFDAY
	struct timeval tv;
#else
	time_t tt;
#endif
	double total;

public:

	ale_timer_t() {
		total = 0;
	}

	void start() {
#if HAVE_GETTIMEOFDAY
		gettimeofday(&tv, NULL);
#else
		tt = time(NULL);
#endif
	}

	void stop() {
#if HAVE_GETTIMEOFDAY
		timeval t;

		gettimeofday(&t, NULL);

		t.tv_sec -= tv.tv_sec;
		t.tv_usec -= tv.tv_usec;
		
		total += t.tv_sec + ((double) 1 / (double) 1000000) * t.tv_usec;
#else
		time_t t = time(NULL);
		t -= tt;

		total += t;
#endif
	}

	double get_total() {
		return total;
	}
};

/*
 * User interface messages.
 */

class ui_wo;

class ui {
private:
	static ui *singleton;

	/*
	 * UI type
	 *
	 * 0. stream
	 * 1. tty
	 * 2. log
	 * 3. quiet
	 */

	static int type;

	static int output_performance_data;

protected:

	/*
	 * Data
	 */

	FILE *ui_stream;

	struct status_type {
		enum { 
			/*
			 * Special
			 */
			
			UNDEFINED, 
			
			/*
			 * Incremental rendering.
			 */
			
			LOAD_FILE, EXPOSURE_PASS_1,
			LODCLUSTER_CREATE, PREMATCH, ALIGN, GLOBAL_ALIGN, POSTMATCH,
			EXPOSURE_PASS_2, RENDERA, RENDERD, RENDERO, WRITED,
			WRITEO, FRAME_DONE, SET_DONE, MULTI,
			
			/*
			 * Irani-Peleg rendering.
			 */
			
			IP_RENDER, IP_STEP_DONE, IP_UPDATE, IP_WRITE,

			/*
			 * 3D.
			 */

			D3_CONTROL_POINT_SOLVE, D3_SUBDIVIDING_SPACE,
			D3_UPDATING_OCCUPANCY, D3_RENDER

		} code, orender_current;

		int arender_current;
		double match_value;
		int onum;
		int steps;
		int steps_completed;
		int step_type;
		double exp_multiplier[3];
		double perturb_size;
		double align_lod;
		unsigned int frame_num;
		unsigned int irani_peleg_stage;
		unsigned int secondary_frame_num;
		unsigned int view_num;
		unsigned int x_coordinate, y_coordinate;;
		unsigned int filtering, focusing;
		unsigned int space_num;
		unsigned int total_spaces;
		double cp_max_perturb;
		double cp_min_perturb;
		double cp_cur_perturb;
		double cp_cur_error;
		int cache;

		status_type() {
			code = UNDEFINED;
			steps_completed = 0;
			cache = 1;

			for (int k = 0; k < 3; k++)
				exp_multiplier[k] = 1;
		}
	} status;

	/*
	 * Performance data
	 */

	ale_timer_t d2_align_sample;
	ale_timer_t d2_align_sim;
	ale_timer_t d2_incremental;
	ale_timer_t d2_irani_peleg;
	std::map<double,ale_timer_t> perturb_timers;

	/*
	 * Constructor
	 */

	ui() {
		ui_stream = stderr;
	}

	/*
	 * Print function
	 */

	virtual void printf(const char *format, ...) = 0;

	/*
	 * UI update function
	 */

	virtual void update() = 0;

	/*
	 * Match format strings for textual UIs.
	 */

	const char *format_string_ok() {
		return " okay (%9.6f%% match)";
	}
	const char *format_string_no_match() {
		return " no match (%9.6f%% match)";
	}
	const char *format_string_working() {
		return "      (%9.6f%% match)";
	}

public:

	/*
	 * Handle options and other user input.
	 */

	static void handle_input(int argc, const char *argv[], const char *package,
			const char *short_version, const char *version);

	static ui *get();

	static void set_stream() {
		assert(singleton == NULL);
		type = 0;
	}

	static void set_tty() {
		assert(singleton == NULL);
		type = 1;
	}

	static void set_log() {
		assert (singleton == NULL);
		type = 2;
	}

	static void set_quiet() {
		assert(singleton == NULL);
		type = 3;
	}

	static void set_profile() {
		output_performance_data = 1;
	}

	/*
	 * Messages from the engine
	 */

	virtual void identify_output(const char *name) {
		printf("Output file will be '%s'.\n", name);
	}

	virtual void d2_align_sim_start() {
		d2_align_sim.start();
	}

	virtual void d2_align_sim_stop() {
		d2_align_sim.stop();
	}

	virtual void d2_align_sample_start() {
		d2_align_sample.start();
	}

	virtual void d2_align_sample_stop() {
		d2_align_sample.stop();
	}

	virtual void d2_incremental_start() {
		d2_incremental.start();
	}

	virtual void d2_incremental_stop() {
		d2_incremental.stop();
	}

	virtual void d2_irani_peleg_start() {
		d2_irani_peleg.start();
	}

	virtual void d2_irani_peleg_stop() {
		d2_irani_peleg.stop();
	}

	virtual void exp_multiplier(double m0, double m1, double m2) {
		status.exp_multiplier[0] = m0;
		status.exp_multiplier[1] = m1;
		status.exp_multiplier[2] = m2;
	}

	void exp_multiplier(double mult[3]) {
		exp_multiplier(mult[0], mult[1], mult[2]);
	}

	virtual void set_steps(int count, int type = 0) {
		status.steps = count;
		status.step_type = type;
	}

	virtual void set_steps_completed(int count) {
		status.steps_completed = count;
	}

	virtual void set_match(double match) {
		status.match_value = (1 - match) * 100;
		update();
	}

	virtual void set_offset(d2::trans_single offset);
	virtual void set_offset(d2::trans_multi offset);

	virtual void gs_mo(ale_pos gs_mo) {
	}

	virtual void cache_status(unsigned int c) {
		status.cache = c;
	}

	virtual void loading_file() {
		status.code = status.LOAD_FILE;
		update();
	}

	virtual void exposure_1() {
		status.code = status.EXPOSURE_PASS_1;
		update();
	}

	virtual void exposure_2() {
		status.code = status.EXPOSURE_PASS_2;
		update();
	}

	virtual void prematching() {
		status.code = status.PREMATCH;
		update();
	}

	virtual void postmatching() {
		status.code = status.POSTMATCH;
		update();
	}

	virtual void constructing_lod_clusters(ale_pos lod) {
		status.code = status.LODCLUSTER_CREATE;
		status.align_lod = lod;
		update();
	}

	virtual void global_alignment(ale_pos perturb, ale_pos lod) {
		status.perturb_size = perturb;
		status.align_lod = lod;
		status.code = status.GLOBAL_ALIGN;
		update();
	}

	virtual void aligning(ale_pos perturb, ale_pos lod) {
		perturb_timers[perturb].start();
		status.perturb_size = perturb;
		status.align_lod = lod;
		status.code = status.ALIGN;
		update();
	}

	virtual void multi() {
		status.code = status.MULTI;
		update();
	}

	virtual void following() {
	}

	virtual void set_orender_current(int num) {
		status.onum = num;
		if (num == 0)
			status.orender_current = status.RENDERD;
		else
			status.orender_current = status.RENDERO;
	}

	virtual void set_arender_current() {
		status.arender_current = 1;
	}

	virtual void clear_arender_current() {
		status.arender_current = 0;
	}

	virtual void rendering() {
		/*
		 * Current alignment rendering tasks must complete
		 * before any current output rendering tasks can
		 * start.
		 */
		if (status.arender_current) {
			status.code = status.RENDERA;
			status.arender_current = 0;
		} else	{
			status.code = status.orender_current;
		}
		update();
	}

	virtual void writing_output(int num) {
		status.onum = num;
		if (num == 0)
			status.code = status.WRITED;
		else
			status.code = status.WRITEO;
		update();
	}

	virtual void d3_control_point_data(double max_perturbation, double min_perturbation, double cur_perturbation, 
			double current_error) {
		status.cp_max_perturb = max_perturbation;
		status.cp_min_perturb = min_perturbation;
		status.cp_cur_perturb = cur_perturbation;
		status.cp_cur_error   = current_error;
		update();
	}

	virtual void d3_control_point_step() {
		printf(".");
		update();
	}

	virtual void d3_subdivision_status(unsigned int primary_frame, unsigned int secondary_frame,
			unsigned int i, unsigned int j) {
		status.code = status.D3_SUBDIVIDING_SPACE;
		status.frame_num = primary_frame;
		status.secondary_frame_num = secondary_frame;
		status.y_coordinate = i;
		status.x_coordinate = j;

		update();
	}

	virtual void d3_total_spaces(int total_spaces) {
		status.total_spaces = total_spaces;
	}

	virtual void d3_increment_spaces() {
		status.total_spaces++;
	}

	virtual void d3_occupancy_status(int frame) {
		status.code = status.D3_UPDATING_OCCUPANCY;
		status.frame_num = frame;
		status.space_num = 0;
		update();
	}

	virtual void d3_increment_space_num() {
		status.space_num++;
		update();
	}

	virtual void d3_render_status(int filter, int focus, int frame, int view, int i, int j, int space) {
		status.code = status.D3_RENDER;

		status.filtering = filter;
		status.focusing = focus;

		status.frame_num = frame;
		status.view_num = view;
		status.y_coordinate = i;
		status.x_coordinate = j;

		status.space_num = space;

		update();
	}


	/*
	 * Informational output
	 */

	virtual void ip_start() {
		printf("Iterating Irani-Peleg");
	}

	virtual void refilter_start() {
		printf("Re-filtering incremental results");
	}

	virtual void ip_frame_start(unsigned int num) {
		status.code = status.IP_RENDER;
		status.frame_num = num;
		status.irani_peleg_stage = 0;
		update();
	}

	virtual void ip_frame_simulate_start() {
		status.code = status.IP_RENDER;
		status.irani_peleg_stage = 1;
		update();
	}

	virtual void ip_frame_correct_start() {
		status.code = status.IP_RENDER;
		status.irani_peleg_stage = 2;
		update();
	}

	virtual void ip_update() {
		status.code = status.IP_UPDATE;
		update();
	}

	virtual void ip_write() {
		status.code = status.IP_WRITE;
		update();
	}

	virtual void ip_step_done() {
		status.code = status.IP_STEP_DONE;
		printf(".");
	}

	virtual void ip_done() {
		printf("\n");
	}

	virtual void refilter_done() {
		printf(".\n");
	}

	virtual void original_frame_start(const char *name) {
		status.code = status.UNDEFINED;
		printf("Original Frame:\n");
		printf(" '%s'", name);
	}

	virtual void original_frame_done() {
		status.code = status.FRAME_DONE;
		update();
	}

	virtual void supplemental_frame_start(const char *name) {
		static int section_announced = 0;

		if (!section_announced) {
			printf("Supplemental Frames:\n");
			section_announced = 1;
		}

		status.code = status.UNDEFINED;
		status.steps_completed = 0;
		printf(" '%s'", name);
	}

	virtual void supplemental_frame_done() {
		status.code = status.FRAME_DONE;
		update();
	}

	virtual void alignment_degree_complete(int degree) {
		if (status.step_type == 1) {
			status.steps_completed++;
			printf("*");
		}
	}

	virtual void alignment_perturbation_level(ale_pos perturb, ale_pos lod) {
		perturb_timers[status.perturb_size].stop();
		perturb_timers[perturb].start();
		status.perturb_size = perturb;
		status.align_lod = lod;
		if (status.step_type == 0) {
			status.steps_completed++;
			printf(".");
		}
	}

	virtual void alignment_dims(unsigned int hr, unsigned int wr, unsigned int hi, unsigned int wi) {
	}

	virtual void start_multi_alignment_element(d2::trans_multi &tm) {
	}

	virtual void alignment_match_ok() {
		status.code = status.UNDEFINED;
		printf(format_string_ok(), status.match_value);
	}

	virtual void alignment_no_match() {
		status.code = status.UNDEFINED;
		printf(format_string_no_match(), status.match_value);
	}

	virtual void ale_2d_done(double value) {
		status.code = status.UNDEFINED;
		printf("Average match: %f%%", value);
		status.code = status.SET_DONE;
		update();
		if (output_performance_data) {
			printf("\n");
			printf("Real time measurements\n");
			printf("======================\n");
			printf("\n");
			printf("Alignment (sampling)   :  %f s\n", d2_align_sample.get_total());
			printf("Alignment (checking)   :  %f s\n", d2_align_sim.get_total());
			printf("Incremental rendering  :  %f s\n", d2_incremental.get_total());
			printf("Irani-Peleg rendering  :  %f s\n", d2_irani_peleg.get_total());
			printf("\n");

			printf("Details (local alignment)\n");
			printf("-------------------------\n");

			int have_details = 0;
			for (std::map<double,ale_timer_t>::iterator i = perturb_timers.begin(); 
			     i != perturb_timers.end(); i++) {
				if (i->second.get_total() == 0.0
				 && i == perturb_timers.begin())
					continue;

				printf("Alignment (perturb %f): %f s\n", 
						i->first, i->second.get_total());
				
				have_details = 1;
			}

			if (!have_details) {
				printf("No local alignment performed.\n");
			}

			printf("\n");
		}
	}

	virtual void d3_start() {
		status.code = status.UNDEFINED;
		printf("Rendering 3D");
		update();
	}

	virtual void d3_control_point_solve() {
		status.code = status.D3_CONTROL_POINT_SOLVE;
		update();
	}

	virtual void d3_init_view_angle(double angle) {
		status.code = status.UNDEFINED;
		update();
	}

	virtual void d3_final_view_angle(double angle) {
		status.code = status.UNDEFINED;
		update();
	}

	virtual void d3_control_point_solve_done() {
		status.code = status.UNDEFINED;
		update();
	}

	virtual void d3_subdividing_space() {
		status.code = status.D3_SUBDIVIDING_SPACE;
		update();
	}

	virtual void d3_subdividing_space_done() {
		status.code = status.UNDEFINED;
		update();
	}

	virtual void d3_updating_occupancy() {
		status.code = status.D3_UPDATING_OCCUPANCY;
		update();
	}

	virtual void d3_updating_occupancy_done() {
		status.code = status.UNDEFINED;
		update();
	}

	virtual void d3_writing_output(const char *name) {
		static int section_announced = 0;

		if (!section_announced) {
			printf(":\n");
			section_announced = 1;
		}

		printf(" '%s'", name);

		update();
	}

	virtual void d3_writing_output_done() {
		status.code = status.UNDEFINED;
		printf(".\n");
		update();
	}

	/*
	 * Warnings
	 */

	virtual void warn(const char *string) {
		printf("\n\n*** Warning: %s. ***\n\n\n");
	}

	/*
	 * Errors
	 */

	virtual void exec_failure(const char *exec, const char *arg1, const char *arg2) {
		printf("\n\n*** An error occurred while running `%s %s %s`. ***\n\n\n", exec, arg1, arg2);
		exit(1);
	}

	virtual void fork_failure(const char *location) {
		printf("\n\n*** Could not fork in %s.  ***\n\n\n", location);
		exit(1);
	}

	virtual void memory_error(const char *purpose) {
		printf("\n\n*** Unable to allocate memory for %s. ***\n\n\n", purpose);
		exit(1);
	}

	virtual void memory_error_location(const char *location) {
		printf("\n\n*** Unable to allocate memory in %s.\n\n\n", location);
		exit(1);
	}

	virtual void cli_not_enough(const char *option) {
		printf("\n\n*** Not enough arguments for `%s' ***\n\n\n", option);
		exit(1);
	}

	virtual void cli_bad_arg(const char *option) {
		printf("\n\n*** Bad argument to `%s' ***\n\n", option);
		exit(1);
	}

	virtual void error(const char *string) {
		printf("\n\n*** Error: %s. ***\n\n\n", string);
		exit(1);
	}

	virtual void illegal_option(const char *string) {
		printf("\n\n*** Error: illegal option %s ***\n\n", string);
		exit(1);
	}

	virtual void unknown_device(const char *string) {
		printf("\n\n*** Error: unknown device %s ***\n\n", string);
		exit(1);
	}

	virtual void error_hint(const char *error, const char *hint) {
		printf("\n\n*** Error: %s", error);
		printf(  "\n*** Hint:  %s\n\n\n", hint);
		exit(1);
	}

	virtual void cache(double usage, double max) {
	}

	virtual void log_message(const char *message) {
	}

	virtual ~ui() {
	}
};

#include "ui_wo.h"

#endif
