/* -*- linux-c -*-
 * Filename: bedlam.cpp
 * Revision: 19981008
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>

#include "./include/bedlam.h"
#include "./include/container.h"
#include "./include/shape.h"
#include "./include/templates.h"
#include "./include/errmsg.h"

#include "./include/config.h"

int g_argc;						// Argument count passed to the program
char **g_argv;					// Argument pointer array
int g_pid;						// This processes ID
char g_hostname[64];			// The hosts name
int g_start = 0;				// The start shape
int g_end = NUM_SHAPES-1;		// The end shape
bool g_savelock = false;		// FALSE enables saving, TRUE disables saving
bedlam g_solver;				// The bedlam cube solver object
int g_stdout;					// A copy of the stdout file descriptor

struct itimerval g_interval = {    // The save timer
	{ SAVE_TIME * 20, 0 },
	{ SAVE_TIME * 20, 0 }
};

int g_mail_count = 0;

// #ifdef _DEBUG
int g_save_count = 0;
int g_save_trig = 0;
// #endif // _DEBUG

// Signal handlers
void save_status (int);
void death_handler (int);
void segv_handler (int);

// E-mail routines
void solved_mail (void);
void solver_start (void);
void solver_error (void);
void solver_end (void);


//-----------------------------------------------------------
// Main
//-----------------------------------------------------------
int main (int argc, char **argv)
{
	char pid_name[256];
	ofstream pid_file;
	char *hostname;
	int i;

	// Get this processes ID
	g_pid = getpid ();

	// Copy stdout for use when mailing
	g_stdout = dup (1);

	// Get an identifier for this host
	hostname = getenv ("HOSTNAME");
	if (!hostname) hostname = getenv ("HOST");
	if (hostname) {
		strcpy (g_hostname, strtok (hostname, "."));
	} else {
		strcpy (g_hostname, "unknown");
	}

	// Parse the command line
	for (i=1; i<argc; i++) {
		// Print the version and quit
		if (! (strcmp (argv[i], "--version"))) {
			fprintf (stderr, "Bedlam cube solver %s\n", VERSION);
			exit (0);
		}

		// Set the start shape
		if (! (strncmp (argv[i], "--start=", 8))) {
			g_start = (int)atol (&argv[i][8]);
		}

		if (! (strncmp (argv[i], "--end=", 6))) {
			g_end = (int)atol (&argv[i][6]);
		}
	}
	
	// Create pid file for renicing (or killing)
	sprintf (pid_name, "%s/%s.pid", STATS_DIR, g_hostname);
	pid_file.open (pid_name);
	if (pid_file.is_open ()) {
		pid_file << g_pid << "\n";
		pid_file.close ();
	} else {
		fprintf (stderr, "Process id file creation failure\n");
	}

	// Catch the segment fault error
	signal (SIGSEGV, segv_handler);

	// Set up save timer
	signal (SIGALRM, save_status);
	setitimer (ITIMER_REAL, &g_interval, NULL);

	// Set up the SIGKILL handler
	signal (SIGKILL, death_handler);

	// Initialise the solvers lookup tables
	g_solver.init_shapes (templates);
	g_solver.remove_dupes ();

	// Start the solver
	g_solver.do_mail (solver_start);
	g_solver.solve (g_start, g_end);
	g_solver.do_mail (solver_end);
}

//-----------------------------------------------------------
// Signal handlers
//-----------------------------------------------------------
void save_status (int)
{
	fstream fs;
	char point_file[256];

#ifdef _DEBUG
	g_save_trig++;
	cerr << "save_status(): starting\n";
#endif

	// Re-capture signal
	signal (SIGALRM, save_status);

	if (!g_savelock) {	// Save allowed
		g_savelock = true;
		sprintf (point_file, "%s/%s.%d", STATS_DIR, g_hostname, g_pid);
		fs.open (point_file, ios::out, 0600);
		if (fs.is_open ()) {
#ifdef _DEBUG
			g_save_count++;
#endif
		        g_solver.save (fs);
		} else {
			fprintf (stderr, FAIL_OPENSAVE);
		}

		g_savelock = false;

		// No need to set the timervals here since below are timer
		// values and not reset values. Timer will still reset to the
		// defined values in config.h
	} else {
		// A structure is in use can't save yet
		// Try again in 20ms
		g_interval.it_value.tv_sec = 0;
		g_interval.it_value.tv_usec = 20;
		setitimer (ITIMER_REAL, &g_interval, NULL);
	}

#ifdef _DEBUG
	cerr << "save_status(): exiting\n";
#endif
}

void death_handler (int)
{
	/*
	 * Saving on kill may not occur. If SIGKILL is 
	 * received when g_savelock == true, nothing will be
	 * saved.
	 */

	save_status (0);
	exit (0);
}

void segv_handler (int)
{
	g_solver.do_mail (solver_error);
	exit (0);
}

//-----------------------------------------------------------
// E-mail routines
//-----------------------------------------------------------
void solved_mail (void)
{
	int i, max_volume;
	int tmp;

	// Prodcue the e-mail
	cout << "Subject: *** Cube Solved ***\n\n";

	cout << "Solution found on " << g_hostname << "\n\n";

	// Quick check on the integrity of the cube
	max_volume = CONT_WIDTH * CONT_HEIGHT * CONT_DEPTH;
	if (g_solver.boxes[g_solver.level-1]->volume () != max_volume) {
		cout << "*** Solution is invalid. ";
		cout << "Container has failed sanity check\n\n";

		cout << "Full solver status follows...\n\n";
		g_solver.status ();
	} else {
		// Dump each shapes status in order
		for (i=0; i<NUM_SHAPES; i++) {
			tmp = g_solver.find_used_shape (i);
			if (tmp == -1) {
				cout << "*** No shape used at level " << i << "!\n";
				cout << "\nFull solver status follows...\n\n";
				g_solver.status ();
				break;
			} else {
				g_solver.used_shapes[tmp]->status ();
			}
			cout << "\n";
		}
	}
}

void solver_start (void)
{
	cout << "Subject: *** Solver started ***\n\n";

	cout << "solve-cube has started:\n\n";

	cout << "       Host: " << g_hostname << "\n";
	cout << "        Pid: " << g_pid << "\n";
	cout << "Start shape: " << g_start << "\n";
	cout << "  End shape: " << g_end << "\n\n";
}

void solver_error (void)
{
	cout << "Subject: *** Solver error ***\n\n";

	cout << "Segmentation fault\n\n";

	cout << "       Host: " << g_hostname << "\n";
	cout << "        Pid: " << g_pid << "\n";
	cout << "Start shape: " << g_start << "\n";
	cout << "  End shape: " << g_end << "\n\n";

	g_solver.status ();
}

void solver_end (void)
{
	cout << "Subject: *** Solver finished ***\n\n";

	cout << "solve-cube has successfully attempted all\n"
		"combinations:\n\n";

	cout << "       Host: " << g_hostname << "\n";
	cout << "        Pid: " << g_pid << "\n";
	cout << "Start shape: " << g_start << "\n";
	cout << "  End shape: " << g_end << "\n\n";
}


//-----------------------------------------------------------
// Bedlam class source
//-----------------------------------------------------------
bedlam::bedlam()
{
	int i;

	all_shapes = NULL;
	level = 0;

	// Create space for the used shapes
	used_shapes = (shape **)calloc (NUM_SHAPES, sizeof (shape *));
	if (!used_shapes) {
		cerr << FAIL_MEM_ALLOC;
		return;
	}

	// Create space for the containers
	boxes = (container **)calloc (NUM_SHAPES, sizeof (container *));
	for (i=0; i<NUM_SHAPES; i++) {
		boxes[i] = new container;
		if (!boxes[i]) {
			cerr << FAIL_CONTAINER;
			return;
		}
	}
}

bedlam::bedlam (struct wire_frame *templates)
{
	int i;

	all_shapes = NULL;
	level = 0;

	// Create space for the used shapes pointers
	used_shapes = (shape **)calloc (NUM_SHAPES, sizeof (shape *));
	if (!used_shapes) {
		cerr << FAIL_MEM_ALLOC;
		return;
	}

	// Create space for the containers
	boxes = (container **)calloc (NUM_SHAPES, sizeof (container *));
	for (i=0; i<NUM_SHAPES; i++) {
		boxes[i] = new container;
		if (!boxes[i]) {
			cerr << FAIL_CONTAINER;
			return;
		}
	}

	// Setup the lookup table of all shapes in all orientations
	init_shapes (templates);

	// Eliminate duplicate orientations
	remove_dupes ();
}

bedlam::~bedlam()
{
	int i;

	if (all_shapes) {
		// Delete shapes that haven't been eliminated already
		for (i=0; i<NUM_SHAPES * 24; i++) {
			if (all_shapes[i]) delete all_shapes[i];
		}

		// Free the pointer array
		free (all_shapes);
	}

	if (used_shapes) free (used_shapes);
}

void bedlam::init_shapes (struct wire_frame *templates)
{
	int i, j;
	int k;

#ifdef _DEBUG
	cerr << "bedlam::init_shapes()\n";
#endif

	all_shapes = (shape **)calloc (NUM_SHAPES * 24, sizeof (shape *));
	if (!all_shapes) {
		cerr << FAIL_MEM_ALLOC;
		return;
	}
	
#ifdef _DEBUG
	cerr << "shape space allocated\n";
#endif

	for (i=0; i<NUM_SHAPES; i++) {		// For each shape
		all_shapes[i*24] = new shape (&templates[i]);
		if (!all_shapes[i*24]) {
			cerr << FAIL_CLASS_CREATE;
			return;
		}
		
#ifdef _DEBUG
		cerr << "master shape " << i << " - orientation 0 created\n";
#endif

		for (j=1; j<24; j++) {		// For each orientation except the first
			k = i*24 + j;
			all_shapes[k] = new shape;
			if (!all_shapes[k]) {
				cerr << FAIL_CLASS_CREATE;
				return;
			}
			all_shapes[k]->copy (all_shapes[k - 1]);
			all_shapes[k]->next ();
#ifdef _DEBUG
			cerr << "slave shape " << i << " - orientation " << j << " created\n";
#endif
		}
	}
}

void bedlam::remove_dupes (void)
{
	int i, j, o;
	shape *subject;

#ifdef _DEBUG
	cerr << "bedlam::remove_dupes()\n";
#endif

	// No shapes loaded
	if (!all_shapes) return;

	// Examine orientations of shapes for duplicates
	for (i=0; i<NUM_SHAPES; i++) {				// For each shape
		for (o=0; o<24; o++) {					// For each orientation
			subject = all_shapes[i*24 + o];

			// From the next orientation to the end
			for (j=o+1; j<24; j++) {
				// Go onto the next orientation to find dupes for
				if (!subject) break;

				// Only compare if there is something to compare with
				if (all_shapes[i*24 + j]) {
					if (! (memcmp (subject->cube_data,
							all_shapes[i*24 + j]->cube_data,
							FRAME_SIZE))) {
						// The same so eliminate the duplicate
						delete all_shapes[i*24 + j];
						all_shapes[i*24 + j] = NULL;
					}
				}
			} 
		}	// End orientations
    }	// End shapes
}

// Finds the first unused shape.
// The start of the recursive process
void bedlam::next_shape (void)
{
	int s;
	
#ifdef _DEBUG
	cerr << "bedlam::next_shape() - level = " << level << "\n";
#endif

	// Is the cube solved yet?
	if (level == NUM_SHAPES) {
		do_mail (solved_mail);
		return;
	}

	// If there is a previous container, get its contents otherwise clear it
	boxes[level]->copy (level ? boxes[level-1] : NULL);
  
	// We set the containers level so it knows where it is
	// This isn't used anywhere except in the container::status() routine
	// It's more of a hangover from the previous version when things were
	// done entirely different
	boxes[level]->level = level;

	// Find the first unused shape
	for (s=first_shape; s<=last_shape; s++) {
		if (used_shapes[s]) continue;
    
		// Shape chosen, deal with the orientations
		do_orientations (s);
	}

#ifdef _DEBUG
	cerr << "leaving bedlam::next_shape() - level = " << level << "\n";
#endif
}

// Cycles through the available orientations with specified shape
void bedlam::do_orientations (int shape_num)
{
	int o;

#ifdef _DEBUG
	cerr << "bedlam::do_orientations()\n";
#endif

	for (o=0; o<24; o++) {
		if (!all_shapes[shape_num*24 + o]) continue;

		// A unique orientation has been found, try placing it
		do_coords (shape_num, o);
	}

#ifdef _DEBUG
	cerr << "leaving bedlam::do_orientations()\n";
#endif
}

// Steps through the possible co-ordinates for the shape/orientation
void bedlam::do_coords (int s, int o)
{
	int x, z;
	int vx, vz;
	shape *piece;

#ifdef _DEBUG
	cerr << "bedlam::do_coords()\n";
#endif
	// Get a pointer from the lookup table
	piece = all_shapes[s*24 + o];

	// This should never happen but to play it safe
	if (!piece) {
		cerr << "bedlam::do_coords() - NULL supplied as piece\n";
		return;
	}

	vx = CONT_WIDTH - piece->width;
	vz = CONT_DEPTH - piece->depth;

	for (z=0; z<=vz; z++) {
		for (x=0; x<=vx; x++) {
			// Ensure shape is at the bottom of its frame
			g_savelock = true;
			piece->shift_down (piece->shape_y);
			g_savelock = false;

			// Drop the shape into this container
			if (boxes[level]->drop_shape (piece)) {
				g_savelock = true;
				piece->used = level++;
				used_shapes[s] = piece;
				g_savelock = false;

				next_shape ();

				// Mark the shape as unused
				g_savelock = true;
				used_shapes[s] = NULL;
				piece->used = -1;
				level--;
				boxes[level]->copy (level ? boxes[level-1] : NULL);
				g_savelock = false;
			}

			// Try the next column to the left
			g_savelock = true;
			piece->shift_left (1);
			g_savelock = false;
		}

		// Start the next row back
		g_savelock = true;
		piece->shift_right (piece->shape_x);
		piece->shift_back (1);
		g_savelock = false;
	}

	// Leave the shape as we found it
	piece->shift_right (piece->shape_x);
	piece->shift_down (piece->shape_y);
	piece->shift_forward (piece->shape_z);

#ifdef _DEBUG
	cerr << "leaving bedlam::do_coords()\n";
#endif
}

void bedlam::do_mail (void (*mail_func)(void))
{
	int pipe_fd[2];
	pid_t pid;
	int status, retcode;

#ifdef _TRACING
	cerr << "bedlam::do_mail()\n";
#endif

	g_mail_count++;

	// Get a pair of pipe descriptors
	pipe (pipe_fd);

	// Produce a child
	pid = fork();
	if (pid == 0) {			// We are the child
		// Child receives so close stdout of the pipe and copy
		// the stdin of the pipe over stdin
		close (pipe_fd[1]);
		dup2 (pipe_fd[0], 0);

		// Run sendmail
		execlp (SENDMAIL, SENDMAIL, MAIL_TO, NULL);

		// We only get here if an error occured
		fprintf (stderr, "\nFailed to load sendmail\n");
		exit (-1);
	}

	// To get here we must be the parent process
	// Copy the stdout of the pipe over stdout
	// and close the stdin of the pipe
	dup2 (pipe_fd[1], 1);
	close (pipe_fd[0]);

	// Produce the e-mail
	cout << "From: solve-cube\nTo: " << MAIL_TO "\n";
	mail_func ();

	// End the e-mail
	cout << ".\n";
	cout.flush ();

	// Restore stdout
	close (1);
	dup2 (g_stdout, 1);

	// Wait for sendmail to finish before continuing
	// This will slow us down a bit but its better than crippling the host
	wait (&status);
	
	// Examine the status
	if (WIFEXITED (status)) {
		retcode = WEXITSTATUS (status);
		if (retcode) {
			cerr << "Mailer exited with code " << retcode << "\n";
			cerr << "Save count = " << g_save_count << "\n";
			cerr << "Save trigger = " << g_save_trig << "\n";
		}
	}
}

int bedlam::find_used_shape (int level)
{
	int i;

	for (i=0; i<NUM_SHAPES; i++) {
		if (used_shapes[i]->used == level) return i;
	}

	return -1;
}

void bedlam::solve (int start, int end)
{
#ifdef _DEBUG
	cerr << "starting to solve\n";
#endif

	if ( (!all_shapes) || (!used_shapes) || (!boxes)) return;

	first_shape = start;
	last_shape = end;

	// Start solving
	next_shape ();
}

void bedlam::load (fstream &fs)
{
	int i;

	fs >> first_shape;
	fs >> last_shape;
	fs >> level;
	
	// Load the shapes
	for (i=0; i<NUM_SHAPES; i++) {
		used_shapes[i]->load (fs);
	}

	// Load the containers
	for (i=0; i<level; i++) {
		boxes[i]->load (fs);
	}
}

void bedlam::save (fstream &fs)
{
	int i;

	fs << first_shape;
	fs << last_shape;
	fs << level;

	// Save the shapes
	for (i=0; i<NUM_SHAPES * 24; i++) {
		if (!all_shapes[i]) {
			fs << "N:\n";
			continue;
		}

		fs << "L:\n";
		all_shapes[i]->save (fs);
	}

	// Save which are used and where
	for (i=0; i<NUM_SHAPES; i++) {
		if (used_shapes[i]) {
			fs << "U:\n";
			used_shapes[i]->save (fs);
		} else {
			fs << "N:\n";
		}
	}

	// Save the containers
	for (i=0; i<level; i++) {
		boxes[i]->save (fs);
	}
}

// This routine is only meant to be used during debugging
// It dumps the status of all objects in the bedlam cube solver
void bedlam::status (void)
{
	int i;

	cout << "\n\nBedlam Cube Solver Dump\n=======================\n";
	cout << "\nUsed shapes array\n-----------------\n";
	for (i=0; i<NUM_SHAPES; i++) {
		if (used_shapes[i]) {
			used_shapes[i]->status ();
		} else {
			cout << "index " << i << " of used_shapes is NULL\n";
		}
	}

	cout << "\nContainer array\n---------------\n";
	for (i=0; i<NUM_SHAPES; i++) {
		if (boxes[i]) {
			boxes[i]->status ();
		} else {
			cout << "index " << i << " of boxes is NULL\n";
		}
	}

	cout << "\nLookup table\n------------\n";
	for (i=0; i<24*NUM_SHAPES; i++) {
		if (all_shapes[i]) {
			all_shapes[i]->status ();
		} else {
			cout << "orientation " << (i % 24) << " of shape " << (i / 24);
			cout << " has been eliminated\n";
		}
	}
	cout << "\nEnd Of Bedlam Cube Solver Dump\n==============================\n\n";
}
