/* The main program of the X11 interface to Xconq.
   Copyright (C) 1987-1989, 1991-1999 Stanley T. Shebs.

Xconq 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 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#define side_has_local_display(side) ((side)->ui != NULL)
extern void notify_instructions(void);
extern int sendnow;
extern void auto_pick_new_research(Unit *unit);
extern int auto_pick_new_build_task(Unit *unit);
extern void auto_pick_new_plan(Unit *unit);
#include "kpublic.h"
#include "cmdline.h"
extern char *args_used;
extern char *default_player_spec;

#include "tcltk/tkconq.h"

#include <unistd.h>

extern int hosting;
extern char *connection_method_name;
extern int connection_method;

int you_player;

void host_the_game(char *option);
void try_join_game(char *hostport);

/* Local function declarations. */

extern void init_all_displays(void);

void init_x_signal_handlers(void);
static int handle_x_error(Display *dpy, XErrorEvent *evt);
static int handle_xio_error(Display *dpy);

void unit_advance_dialog(Unit *unit);
void global_advance_dialog(Side *side, Unit *unit, int s);
int unit_build_dialog(Unit *unit);
void unit_plan_dialog(Unit *unit);

int announced = FALSE;

char *announcemsg = NULL;

/* The main program. */

int
main(int argc, char *argv[])
{
    extern uid_t games_uid;

    /* Dummy reference to get libraries pulled in */
    if (argc == -1)
      cmd_error(NULL, NULL);
    /* Shift to being the user that started the game. */
    games_uid = geteuid();
    setuid(getuid());
    init_library_path(NULL);
    printf("\n              Welcome to X11 Xconq version %s\n\n",
	   version_string());
    printf("%s", license_string());
    print_any_news();
    /* Fiddle with game module structures. */
    clear_game_modules();
#ifdef DEBUGGING
    init_debug_to_stdout();
#endif /* DEBUGGING */
    /* Set up empty data structures. */
    init_data_structures();

    if (argc == 1)
      option_popup_new_game_dialog = TRUE;

    parse_command_line(argc, argv, general_options);

    initial_ui_init();

    /* If we're getting a startup dialog sequence, do it instead. */
    if (option_popup_new_game_dialog) {
	popup_game_dialog();
	ui_mainloop();
	/* Note that we never reach here. */
    }

    if (option_game_to_join != NULL) {
	parse_command_line(argc, argv, variant_options);
	set_variants_from_options();
	parse_command_line(argc, argv, player_options);
	set_players_from_options();
	/* Go through once more and complain about anything not used. */
	parse_command_line(argc, argv, leftover_options);
	try_join_game(option_game_to_join);
    } else {
	if (option_popup_new_game_dialog) {
	    /* nothing to do */
	} else {
	    load_all_modules();
	    check_game_validity();
	    parse_command_line(argc, argv, variant_options);
	    set_variants_from_options();
	    parse_command_line(argc, argv, player_options);
	    set_players_from_options();
	    make_trial_assignments();
	    /* Complain about anything that's left. */
	    parse_command_line(argc, argv, leftover_options);
	    /* (still need to merge some databases derived from display) */
	}
    }
    if (option_game_to_host != NULL) {
	host_the_game(option_game_to_host);
    }
    if (option_game_to_join == NULL) {
	launch_game();
    }
    ui_init();
    /* Get the displays set up, but don't draw anything yet. */
    init_all_displays();
    /* Now bring up the init data on each display. */
    init_redraws();
    /* Set up the signal handlers. */
    init_signal_handlers();
    init_x_signal_handlers();
    notify_all("Command was \"%s %s\"", argv[0], args_used);
    notify_instructions();
    /* Go into the main play loop. */
    ui_mainloop();

    /* Humor the compiler. */
    return 0;
}

/* The default (human) player is the current user on the current display. */

Player *
add_default_player()
{
    Player *player = add_player();
    
    player->name = getenv("USER");
    player->configname = getenv("XCONQCONFIG");
    player->displayname = getenv("DISPLAY");
    return player;
}

void
host_the_game(char *option)
{
    int i;
    Player *player;

    hosting = TRUE;
    for (i = 0; i < numsides; ++i) {
	if (assignments[i].side && (assignments[i].side)->ingame) {
	    player = assignments[i].player;
	    if (player->displayname != NULL) {
		player->rid = 1;
	    } else if (player->name) {
		player->remotewanted = player->name;
	    }
	}
    }
    open_remote_connection(option);
    master_rid = 1;
    while (numremotewaiting > 0) {
	receive_data(0);
	numremotewaiting = 0;
	for (i = 0; i < numsides; ++i) {
	    if (assignments[i].side && (assignments[i].side)->ingame) {
		player = assignments[i].player;
		if (player->remotewanted != NULL && player->rid == 0) {
		    ++numremotewaiting;
		}
	    }
	}
    }
}

void
try_join_game(char *hostport)
{
    int rslt;

    hosting = FALSE;
    open_remote_connection(copy_string(hostport));
    if (connection_method > 0) {
	sendnow = TRUE;
	rslt = send_join(default_player_spec);
	sendnow = FALSE;
	if (rslt) {
	    master_rid = 1;
	    while (TRUE) {
		receive_data(0);
		if (!beforestart) {
		    break;
		}
	    }
	} else {
	    close_remote_connection();
	    fprintf(stderr, "Unable to join game \"%s\", exiting\n",
		    option_game_to_join);
	    exit(1);
	}
    }
}

/* An init error needs to have the command re-run. */

void
low_init_error(str)
char *str;
{
    fprintf(stderr, "Error: %s.\n", str);
    fflush(stderr);
    exit(1);
}

/* A warning just gets displayed, no other action is taken. */

void
low_init_warning(str)
char *str;
{
    fprintf(stderr, "Warning: %s.\n", str);
    fflush(stderr);
}

void
low_run_error(str)
char *str;
{
    close_displays();
    fprintf(stderr, "Error: %s.\n", str);
    fflush(stderr);
    fprintf(stderr, "Saving the game...");
    write_entire_game_state(saved_game_filename());
    fprintf(stderr, " done.\n");
    exit(1);
}

/* Runtime warnings are for when it's important to bug the players,
   usually a problem with Xconq or a game design. */

void
low_run_warning(str)
char *str;
{
    notify_all("Warning: %s; continuing...", str);
}

void
print_form(form)
Obj *form;
{
    print_form_and_value(stdout, form);
}

void
end_printing_forms()
{
}

void
init_x_signal_handlers()
{
    XSetErrorHandler(handle_x_error);
    XSetIOErrorHandler(handle_xio_error);
}

/* Handlers for X catastrophes attempt to do a save first. */

static int
handle_x_error (dpy, evt)
Display *dpy;
XErrorEvent *evt;
{
    static int num_errors = 0;
    char buf[BUFSIZE];

    XGetErrorText(dpy, evt->error_code, buf, BUFSIZE);
    fprintf(stderr, "\nX error on display %s: %s\n", DisplayString(dpy), buf);
    if (++num_errors >= 10) {
        printf("\nX error: trying emergency save!\n");
	/* Note that if the save fails too, we're totally hosed. */
	/* (should use configurable name here) */
        write_entire_game_state("ack!.xconq");
	abort();
    }
    return 0;
}

static int
handle_xio_error (dpy)
Display *dpy;
{
    fprintf(stderr, "\nX IO error on display %s: trying emergency save!\n",
	   DisplayString(dpy));
    write_entire_game_state("ack!.xconq");
    abort();
    return 0;
}

/* Reading is usually pretty fast, so don't do anything special here. */

void
announce_read_progress()
{
}

/* Announce the start of a time-consuming computation. */

void
announce_lengthy_process(msg)
char *msg;
{
    n_seconds_elapsed(0);
    announcemsg = copy_string(msg);
    if (announcemsg) {
	printf("%s;", announcemsg);
	free(announcemsg);
	announcemsg = NULL;
	fflush(stdout);
	announced = TRUE;
    }
}

/* Announce the making of progress on the computation. */

void
announce_progress(percentdone)
int percentdone;
{
    if (n_seconds_elapsed(2)) {
	printf(" %d%%,", percentdone);
	fflush(stdout);
	announced = TRUE;
    }
}

/* Announce the end of the time-consuming computation. */

void
finish_lengthy_process()
{
    if (announced) {
	printf(" done.\n");
	announced = FALSE;
    }
}

/* This should be called before any sort of normal exit. */

void
exit_xconq(Side *side)
{
    close_displays();
    printf("\n%s\n", exit_commentary(side));
    printf("\n%s\n", get_scores(side));
    exit(0);
}

void
add_remote_player(name)
char *name;
{
    int i;
    Player *player;

    if (name == NULL)
      name = "somebody";
    for (i = 0; i < numsides; ++i) {
	if (assignments[i].side && (assignments[i].side)->ingame) {
	    player = assignments[i].player;
	    if (player->remotewanted != NULL && player->rid == 0) {
		player->displayname = copy_string(name);
		player->rid = 2; /* for now */
		sendnow = TRUE;
		send_version(player->rid);
		sendnow = FALSE;
		sleep(1);
		download_to_player(player);
		/* (should do user feedback here?) */
		return;
	    }
	}
    }
}

int
launch_game()
{
    /* Do the time-consuming part of setup calculations. */
    calculate_globals();
    run_synth_methods();
    final_init();
    assign_players_to_sides();
    print_instructions();
    run_game(0);
    return TRUE;
}

/* Set up all sides' displays all at once. */

void
init_all_displays()
{
    int numdisplays;
    Side *side;

    numdisplays = 0;
    for_all_sides(side) {
	if (side_has_display(side)) {
	    if (side_has_local_display(side))
	      init_display();
	    ++numdisplays;
	}
    }
    if (numdisplays == 0) {
	fprintf(stderr, "Must have at least one display to start.\n");
	exit(0);
    }
}

void
set_you_player(id)
int id;
{
    Player *player;

    you_player = id;
    for_all_players(player) {
	if (player->id == id)
	  player->rid = 2;
	else
	  player->rid = 1;
    }
}

void
unit_advance_dialog(Unit *unit)
{
    auto_pick_new_research(unit);
}

void
global_advance_dialog(Side *side, Unit *unit, int s)
{
    auto_pick_new_research(unit);
}

int
unit_build_dialog(Unit *unit)
{
    int u = auto_pick_new_build_task(unit);

    return u;
}

void
unit_plan_dialog(Unit *unit)
{
    auto_pick_new_plan(unit);
}
