/*
 *  main.c - Initialization before main-loop and cleanup before exit.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/soundcard.h>
#include <sys/stat.h>
#include <dirent.h>
#include <termios.h>
#include <signal.h>
#include <string.h>
#include <time.h>

#include "mod.h"

SEQ_DECLAREBUF();
extern int seqfd, gus_dev;
extern struct options default_opt;
extern char quit;

int total_time;

struct mod_info M;         /* The module */
char workdir[PATH_MAX+1];  /* The directory mod was started from */
int ack_pipe[2];           /* Player sends acks to main through this one */
int player2main_synced[2]; /* Message from player to main (synced) */
int main2player[2];        /* Message from main to player */

pid_t pid_main, pid_player;

int main(int argc, char *argv[])
{
    struct termios tio, orig_tio;
    char tmp, day, hour, min, sec;

    srand(time((time_t)0));    /* Randomize */
    getcwd(workdir, PATH_MAX); /* Store working directory */

    if(!strcmp("mod_mklist", argv[0]))
	mklist(argc, argv);    /* Must be after workdir is set */
    
    if(mkdir(TMP_DIR , 0777) && errno != EEXIST)
	error("Unable to create " TMP_DIR ".\n");
    
    chmod(TMP_DIR, 0777); /* This is needed as mode&umask is used in mkdir */
    
    if(argc >= 2 && !strncmp(argv[1], "-k", 2))
	kill_mod();

    /* Special case to allow help if mod is already running */
    if(argc >= 2 && !strncmp(argv[1], "-h", 2)) {
	print_helptext(argv[0]);
	exit(0);
    }
    
    init_sound(); /* Make sure audio-resources are available and grab them */
    init_options(argc, argv);
    
    pid_main=getpid();
    
    /* Create communication-pipes */
    if(pipe(player2main_synced) || pipe(main2player) || pipe(ack_pipe))
        error("Unable to create pipes.");

    /* Player should never block */
    fcntl(player2main_synced[PIPE_WRITE], F_SETFL, O_NONBLOCK);

    /* Set up terminal if verbose (turn off linebuffering and echo) */
    if(default_opt.verbose) {
	tcgetattr(STDIN_FILENO, &orig_tio);
	tio=orig_tio;
	tio.c_lflag&=~(ICANON|ECHO);
	tio.c_cc[VMIN]=1;
	tio.c_cc[VTIME]=0;
	tcsetattr(STDIN_FILENO, TCSADRAIN, &tio);
    }
    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); /* Never block on keyboard */
  
    if(default_opt.interactive) {
	init_screen(); /* This one changes signal-handlers ! */
    }

    /* Install signal handlers _after_ ncurses initscr() */
    pid_player=1;        /* Make sure handlers know we are the main process */
    install_signalhandlers();

    cleanup_sound();  /* Free resources before entering main loop */
    zero_resources(); /* Make sure nothing undefined get free()'d */
    total_time=0;

    main_loop();      /* Do it! */

    /* Clean up */
    cleanup_options();
    
    if(default_opt.verbose)
	tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_tio);
    
    if(default_opt.interactive)
	cleanup_screen();

    chdir(TMP_DIR);
    chdir("..");
    my_removedir(TMP_DIR);  /* Try to delete all garbage-files in TMP_DIR */
    
    if(!default_opt.quiet && total_time && quit != QUIT_SIGNAL) {
	day=total_time/(24*3600);
	hour=(total_time/3600)%24;
	min=(total_time/60)%60;
	sec=total_time%60;

	printf("Total time spent playing was ");
	tmp=0;
	if(day) {
	    if(day == 1)
		printf("1 day, ");
	    else
		printf("%d days, ", day);
	    tmp=1;
	}
	if(tmp || hour) {	
	    if(hour == 1)
		printf("1 hour, ");
	    else
		printf("%d hours, ", hour);
	    tmp=1;
	}
	if(tmp || min) {
	    if(min == 1)
		printf("1 minute and ");
	    else
		printf("%d minutes and ", min);
	    tmp=1;
	}
	if(sec == 1)
	    printf("1 second.\n");
	else
	    printf("%d seconds.\n", sec);
    }
    return 0;
}

/* Removes a directory (recursively) located in the _current directory_ */

void my_removedir(char *name)
{
    DIR *d;
    struct dirent *de;
    struct stat s;

    if(!chdir(name)) {
	if((d=opendir("."))) {
	    while((de=readdir(d))) {
		if(strcmp(de->d_name, ".") && strcmp(de->d_name, "..") &&
		   strcmp(de->d_name, "mod.pid")) {
		    if(stat(de->d_name, &s))
			continue;
		    chmod(de->d_name, 0777); /* a+rwx */
		    if(S_ISDIR(s.st_mode)) {
			my_removedir(de->d_name);
		    }
		    else
			unlink(de->d_name);
		}
	    }
	    chmod("mod.pid", 0777); /* a+rwx */
	    unlink("mod.pid"); /* Remove this one last if it exists.
				* Can't remember WHY I wait with that one...
				*/
	    closedir(d);        
	}
	chdir("..");
    }
    rmdir(name); /* May or may not work depending on owner of dir */
}

void write_pid(pid_t p)
{
    int fd;
    char buf[10];

    if((fd=open(TMP_DIR"/mod.pid", O_RDONLY)) != -1) {
	error("There is a copy of mod running in the background, try mod -k.\n");
    }

    if((fd=open(TMP_DIR"/mod.pid", O_CREAT|O_TRUNC|O_WRONLY, 0777)) == -1)
	error("Unable to write 'mod.pid'", errno);
    sprintf(buf, "%09d\n", p);
    write(fd, buf, 10);
    close(fd);
    chmod(TMP_DIR"/mod.pid", 0777);
}


void kill_mod(void)
{
    int fd, p;
    char buf[10];

    if((fd=open(TMP_DIR"/mod.pid", O_RDONLY)) == -1)
	error("No copy of mod running in the background.\n");

    if(read(fd, buf, 12) != 10)
	error("Trouble reading 'mod.pid'.\n");
    close(fd);
    p=atoi(buf);
    if(kill(p, SIGTERM)) {
	if(errno == EPERM)
	    error("You don't have permission to kill the running copy of"
		  " 'mod'.\n");
	else if(errno == ESRCH) {
	    if(!unlink(TMP_DIR"/mod.pid")) {
		printf("Stale lock removed.\n");
		exit(0);
	    }
	    else
		error("Unable to remove stale lock.");
	}
	else
	    error("No copy of mod running in the background.\n");
    }
    printf("Killed.\n");
    exit(0);
}


/* Main-process:  Set quit-flag, kill child.
 * Child-process: Terminate. Maybe we should kill() father also?
 */

void handler_quit(int sig)
{
    if(pid_player) {
	signal(sig, handler_quit);
	kill(pid_player, sig);
	quit=QUIT_SIGNAL;
    }
    else {
	cleanup_sound();
	exit(0);
    }
}


void install_signalhandlers(void)
{
    signal(SIGHUP, handler_quit);
    signal(SIGINT, handler_quit);
    signal(SIGQUIT, handler_quit);
    signal(SIGTERM, handler_quit);
    signal(SIGTSTP, SIG_IGN);     /* Maybe support suspension ? */
}
