#include <gtk/gtk.h>

/**
 * ??????
 */
struct block
{
	gint       count;  /* ??????? */
	gboolean   mine;   /* ?????? */
	gboolean   marked; /* ?????? */
	gboolean   opened; /* ?????? */
	GtkWidget *button;
};

/**
 * ????
 */
static struct block *map; /* ????? */
static gint width=10;     /* ????? */
static gint height=10;    /* ????? */
static gint mines=20;     /* ???? */

static GtkWidget *mine_label; /* ??????? */
static GtkWidget *time_label; /* ?????? */
static gint button_size=16;   /* button ?? */

static gint opened_count;  /* ???????? */
static gint marked_count;  /* ???????? */
static gboolean game_over; /* ??????? */

static GtkWidget *window; /* ??? */
static gint game_time; /* ???????? */

/**
 * g_timeout_add() ? callback function
 */
gboolean tick(gpointer data){
	gchar buf[8];

	if(game_over==TRUE) /* ??????? FALSE ???? */
		return FALSE;

	game_time++; /* ??????????? */
	g_snprintf(buf, 8, "%d", game_time);
	gtk_label_set_text(GTK_LABEL(time_label), buf);
	return TRUE; /* ?? TRUE ???? */
}

/**
 * ??????
 */
void game_reset()
{
	gint size=width*height;
	gint i=0;
	gchar buf[4];

	/* ??????? */
	opened_count=0;
	marked_count=0;
	game_over=FALSE;
	game_time=0;

	/* ??????? */
	g_snprintf(buf, 4, "%d", MAX(0, mines-marked_count));
	gtk_label_set_text(GTK_LABEL(mine_label), buf);

	/* ??????? */
	while(i<mines){
		gint index;
		gint row, col;
		index=g_random_int_range(0, size);
		if(map[index].mine==TRUE) /* ???? */
			continue;
		map[index].mine=TRUE;
		row=index/width;
		col=index%width;

		/* ????? count ?? */
		if(row>0){
/* ?? */		if(col>0) map[index-width-1].count++;
/* ?? */		map[index-width].count++;
/* ?? */		if(col<width-1) map[index-width+1].count++;
		}
/* ? */		if(col>0) map[index-1].count++;
/* ? */		if(col<width-1) map[index+1].count++;
		if(row<height-1){
/* ?? */		if(col>0) map[index+width-1].count++;
/* ?? */		map[index+width].count++;
/* ?? */		if(col<width-1) map[index+width+1].count++;
		}

		i++;
	}

	/* ???? */
	g_timeout_add(1000, (GSourceFunc)tick, NULL);
}

/**
 * ??????
 */
void gameover(gboolean won)
{
	GtkWidget *dialog;
	gchar msg[100];

	if(game_over==TRUE) return;

	game_over=TRUE;

	if(won==TRUE){ /* ????????? */
		g_snprintf(msg, 100, "You won! You have cleared"
			" the game in %3d seconds.", game_time);
	}else{ /* ????????? */
		g_snprintf(msg, 100, "Bad luck. Game over.");
	}

	/* ? GtkMessageDialog ???? */
	dialog=gtk_message_dialog_new(GTK_WINDOW(window), 0,
			GTK_MESSAGE_INFO, GTK_BUTTONS_OK, msg);
	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
}

/**
 * ???? (x,y)
 */
void open_block(gint x, gint y)
{
	gint index;
	GtkWidget *button;

	index=x+y*width;

	if(map[index].marked==TRUE) /* ?????????????????? */
		return;

	button=map[index].button;
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
			TRUE); /* ?? button ????? */

	if(map[index].opened==TRUE) /* ????????????? */
		return;

	map[index].opened=TRUE; /* ??????? */

	if(map[index].mine==TRUE){ /* ????? */
		gtk_button_set_label(GTK_BUTTON(button), "*");
		gameover(FALSE); /* ???????? */
		return;
	}

	if(map[index].count>0){ /* ?????? */
		gchar buf[2];
		g_snprintf(buf, 2, "%d", map[index].count);
		gtk_button_set_label(GTK_BUTTON(button), buf);
	}

	opened_count++; /* ??????????? */

	if(opened_count+mines==width*height){
		gameover(TRUE); /* ????????????? */
		return;
	}

	if(map[index].count==0){ /* ??????? */
		/* ?????? */
		if(y>0){
			if(x>0) open_block(x-1, y-1);
			open_block(x, y-1);
			if(x<width-1) open_block(x+1, y-1);
		}
		if(x>0) open_block(x-1, y);
		if(x<width-1) open_block(x+1, y);
		if(y<height-1){
			if(x>0) open_block(x-1, y+1);
			open_block(x, y+1);
			if(x<width-1) open_block(x+1, y+1);
		}
	}
}

/**
 * ??????? callback function
 */
gboolean on_mouse_click(GtkWidget *widget,
			GdkEventButton *event,
			gpointer data)
{
	gint index;
	gint row, col;
	gchar buf[4];

	if(game_over==TRUE) return TRUE; /* ????? */

	index=(gint)data;

	switch(event->button){
	case 1: /* ???? */
		/* ? index ??????????? */
		row=index/width;
		col=index%width;
		/* ???? */
		open_block(col, row);
		break;
	case 2: /* ???? */
		break;
	case 3: /* ???? */
		/* ?????????? */
		if(map[index].opened==TRUE)
			break;
		/* ????????, ??????? */
		if(map[index].marked!=TRUE){
			map[index].marked=TRUE;
			gtk_button_set_label(
				GTK_BUTTON(widget), "@");
			marked_count++;
		}else{
			map[index].marked=FALSE;
			gtk_button_set_label(
				GTK_BUTTON(widget), "");
			marked_count--;
		}
		/* ??????? */
		g_snprintf(buf, 4, "%d",
			   MAX(0, mines-marked_count));
		gtk_label_set_text(GTK_LABEL(mine_label), buf);
	}

    return TRUE;
}

/**
 * ????
 */
int main(int argc, char **argv)
{
	GtkWidget *vbox;
	GtkWidget *hbox;
	GtkWidget *label;
	gint i, j, index;

	/* initialize GTK library */
	gtk_init(&argc, &argv);

	/* ?????? map ???? */
	map=(struct block *)g_malloc0(sizeof(struct block)*
			      width*height);

	/* build the user interface */
	window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect(G_OBJECT(window), "delete_event",
		gtk_main_quit, NULL);

	vbox=gtk_vbox_new(FALSE, 0);

	/* ?? label ???? hbox */
	hbox=gtk_hbox_new(FALSE, 0);
	label=gtk_label_new("Mines:");
	gtk_box_pack_start(GTK_BOX(hbox), label,
			FALSE, FALSE, 4);
	mine_label=gtk_label_new("0");
	gtk_box_pack_start(GTK_BOX(hbox), mine_label,
			FALSE, FALSE, 2);
	label=gtk_label_new("Time:");
	gtk_box_pack_start(GTK_BOX(hbox), label,
			FALSE, FALSE, 4);
	time_label=gtk_label_new("0");
	gtk_box_pack_start(GTK_BOX(hbox), time_label,
			FALSE, FALSE, 2);
	gtk_widget_show_all(hbox);
	gtk_box_pack_start(GTK_BOX(vbox), hbox,
			FALSE, FALSE, 0);

	/* width x height ? button ??? */
	for(i=0, index=0; i<height; i++){
		gint j;
		hbox=gtk_hbox_new(FALSE, 0);
		for(j=0; j<width; j++){
			GtkWidget *button;
			button=gtk_toggle_button_new();
			gtk_widget_set_usize(button,
				button_size, button_size);
			g_object_set(G_OBJECT(button),
				"can-focus", FALSE, NULL);
			gtk_box_pack_start(GTK_BOX(hbox),
				button, FALSE, FALSE, 0);
			gtk_widget_show(button);
			g_signal_connect(G_OBJECT(button),
				"button-press-event",
				G_CALLBACK(on_mouse_click),
				(gpointer)index);
			map[index].button=button;
			index++;
		}
		gtk_box_pack_start(GTK_BOX(vbox), hbox,
				   FALSE, FALSE, 0);
		gtk_widget_show(hbox);
	}

	gtk_container_add(GTK_CONTAINER(window), vbox);
	gtk_widget_show(vbox);
	gtk_widget_show(window);

	game_reset();

	gtk_main();

	g_free(map); /* ?????????? */

	return 0;
}
