/*
 * Revision Control Information
 *
 * $Source$
 * $Author$
 * $Revision$
 * $Date$
 *
 */
/*
 *  Main driver for espresso
 *
 *  Old style -do xxx, -out xxx, etc. are still supported.
 */

#include "espresso.h"
#include "main.h"		/* table definitions for options */

static FILE *last_fp;
static int input_type = FD_type;


main(argc, argv)
int argc;
char *argv[];
{
    int i, j, first, last, strategy, out_type, option;
    pPLA PLA, PLA1;
    pcover F, Fold, Dold;
    pset last1, p;
    cost_t cost;
    bool error, exact_cover;
    long start;
    extern char *util_optarg;
    extern int util_optind;

    start = ptime();

    error = FALSE;
    init_runtime();
#ifdef RANDOM
    srandom(314973);
#endif

    option = 0;			/* default -D: ESPRESSO */
    out_type = F_type;		/* default -o: default is ON-set only */
    debug = 0;			/* default -d: no debugging info */
    verbose_debug = FALSE;	/* default -v: not verbose */
    print_solution = TRUE;	/* default -x: print the solution (!) */
    summary = FALSE;		/* default -s: no summary */
    trace = FALSE;		/* default -t: no trace information */
    strategy = 0;		/* default -S: strategy number */
    first = -1;			/* default -R: select range */
    last = -1;
    remove_essential = TRUE;	/* default -e: */
    force_irredundant = TRUE;
    unwrap_onset = TRUE;
    single_expand = FALSE;
    pos = FALSE;
    recompute_onset = FALSE;
    use_super_gasp = FALSE;
    use_random_order = FALSE;
    kiss = FALSE;
    echo_comments = TRUE;
    echo_unknown_commands = TRUE;
    exact_cover = FALSE;	/* for -qm option, the default */

    backward_compatibility_hack(&argc, argv, &option, &out_type);


    /* parse command line options*/
    while ((i = util_getopt(argc, argv, "D:S:de:o:r:stv:x")) != EOF) {
	switch(i) {
	    case 'D':		/* -Dcommand invokes a subcommand */
		for(j = 0; option_table[j].name != 0; j++) {
		    if (strcmp(util_optarg, option_table[j].name) == 0) {
			option = j;
			break;
		    }
		}
		if (option_table[j].name == 0) {
		    (void) fprintf(outStream, "%s: bad subcommand \"%s\"\n",
			argv[0], util_optarg);
		    exit(1);
		}
		break;

	    case 'o':		/* -ooutput selects and output option */
		for(j = 0; pla_types[j].key != 0; j++) {
		    if (strcmp(util_optarg, pla_types[j].key+1) == 0) {
			out_type = pla_types[j].value;
			break;
		    }
		}
		if (pla_types[j].key == 0) {
		    (void) fprintf(outStream, "%s: bad output type \"%s\"\n",
			argv[0], util_optarg);
		    exit(1);
		}
		break;

	    case 'e':		/* -eespresso selects an option for espresso */
		for(j = 0; esp_opt_table[j].name != 0; j++) {
		    if (strcmp(util_optarg, esp_opt_table[j].name) == 0) {
			*(esp_opt_table[j].variable) = esp_opt_table[j].value;
			break;
		    }
		}
		if (esp_opt_table[j].name == 0) {
		    (void) fprintf(outStream, "%s: bad espresso option \"%s\"\n",
			argv[0], util_optarg);
		    exit(1);
		}
		break;

	    case 'd':		/* -d turns on (softly) all debug switches */
		debug = debug_table[0].value;
		trace = TRUE;
		summary = TRUE;
		break;

	    case 'v':		/* -vdebug invokes a debug option */
		verbose_debug = TRUE;
		for(j = 0; debug_table[j].name != 0; j++) {
		    if (strcmp(util_optarg, debug_table[j].name) == 0) {
			debug |= debug_table[j].value;
			break;
		    }
		}
		if (debug_table[j].name == 0) {
		    (void) fprintf(outStream, "%s: bad debug type \"%s\"\n",
			argv[0], util_optarg);
		    exit(1);
		}
		break;

	    case 't':
		trace = TRUE;
		break;

	    case 's':
		summary = TRUE;
		break;

	    case 'x':		/* -x suppress printing of results */
		print_solution = FALSE;
		break;

	    case 'S':		/* -S sets a strategy for several cmds */
		strategy = atoi(util_optarg);
		break;

	    case 'r':		/* -r selects range (outputs or vars) */
		if (sscanf(util_optarg, "%d-%d", &first, &last) < 2) {
		    (void) fprintf(outStream, "%s: bad output range \"%s\"\n",
			argv[0], util_optarg);
		    exit(1);
		}
		break;

	    default:
		usage();
		exit(1);
	}
    }

    /* provide version information and summaries */
    if (summary || trace) {
	/* echo command line and arguments */
	fprintf(outStream, "#");
	for(i = 0; i < argc; i++) {
	    fprintf(outStream, " %s", argv[i]);
	}
	fprintf(outStream, "\n");
	fprintf(outStream, "# %s\n", VERSION);
    }

    /* the remaining arguments are argv[util_optind ... argc-1] */
    PLA = PLA1 = NIL(PLA_t);
    switch(option_table[option].num_plas) {
	case 2:
	    if (util_optind+2 < argc) fatal("trailing arguments on command line");
	    getPLA(util_optind++, argc, argv, option, &PLA, out_type);
	    getPLA(util_optind++, argc, argv, option, &PLA1, out_type);
	    break;
	case 1:
	    if (util_optind+1 < argc) fatal("trailing arguments on command line");
	    getPLA(util_optind++, argc, argv, option, &PLA, out_type);
	    break;
    }
    if (util_optind < argc) fatal("trailing arguments on command line");

    if (summary || trace) {
	if (PLA != NIL(PLA_t)) PLA_summary(PLA);
	if (PLA1 != NIL(PLA_t)) PLA_summary(PLA1);
    }

/*
 *  Now a case-statement to decide what to do
 */

    switch(option_table[option].key) {


/******************** Espresso operations ********************/

    case KEY_ESPRESSO:
	Fold = sf_save(PLA->F);
	PLA->F = espresso(PLA->F, PLA->D, PLA->R);
	EXECUTE(error=verify(PLA->F,Fold,PLA->D), VERIFY_TIME, PLA->F, cost);
	if (error) {
	    print_solution = FALSE;
	    PLA->F = Fold;
	    (void) check_consistency(PLA);
	} else {
	    free_cover(Fold);
	}
	break;

    case KEY_MANY_ESPRESSO: {
	int pla_type;
	do {
	    EXEC(PLA->F=espresso(PLA->F,PLA->D,PLA->R),"ESPRESSO   ",PLA->F);
	    if (print_solution) {
		fprint_pla(outStream, PLA, out_type);
		(void) fflush(outStream);
	    }
	    pla_type = PLA->pla_type;
	    free_PLA(PLA);
	    setdown_cube();
	    FREE(cube.part_size);
	} while (read_pla(last_fp, TRUE, TRUE, pla_type, &PLA) != EOF);
	exit(0);
    }

    case KEY_simplify:
	EXEC(PLA->F = simplify(cube1list(PLA->F)), "SIMPLIFY  ", PLA->F);
	break;

    case KEY_so:	        /* minimize all functions as single-output */
	if (strategy < 0 || strategy > 1) {
	    strategy = 0;
	}
	so_espresso(PLA, strategy);
	break;

    case KEY_so_both:		/* minimize all functions as single-output */
	if (strategy < 0 || strategy > 1) {
	    strategy = 0;
	}
	so_both_espresso(PLA, strategy);
	break;

    case KEY_expand:            /* execute expand */
	EXECUTE(PLA->F=expand(PLA->F,PLA->R,FALSE),EXPAND_TIME, PLA->F, cost);
	break;

    case KEY_irred:             /* extract minimal irredundant subset */
	EXECUTE(PLA->F = irredundant(PLA->F, PLA->D), IRRED_TIME, PLA->F, cost);
	break;

    case KEY_reduce:            /* perform reduction */
	EXECUTE(PLA->F = reduce(PLA->F, PLA->D), REDUCE_TIME, PLA->F, cost);
	break;

    case KEY_essen:             /* check for essential primes */
	foreach_set(PLA->F, last1, p) {
	    SET(p, RELESSEN);
	    RESET(p, NONESSEN);
	}
	EXECUTE(F = essential(&(PLA->F), &(PLA->D)), ESSEN_TIME, PLA->F, cost);
	free_cover(F);
	break;

    case KEY_super_gasp:
	PLA->F = super_gasp(PLA->F, PLA->D, PLA->R, &cost);
	break;

    case KEY_gasp:
	PLA->F = last_gasp(PLA->F, PLA->D, PLA->R, &cost);
	break;

    case KEY_make_sparse:       /* make_sparse step of Espresso */
	PLA->F = make_sparse(PLA->F, PLA->D, PLA->R);
	break;

    case KEY_exact:
	exact_cover = TRUE;

    case KEY_qm:
	Fold = sf_save(PLA->F);
	PLA->F = minimize_exact(PLA->F, PLA->D, PLA->R, exact_cover);
	EXECUTE(error=verify(PLA->F,Fold,PLA->D), VERIFY_TIME, PLA->F, cost);
	if (error) {
	    print_solution = FALSE;
	    PLA->F = Fold;
	    (void) check_consistency(PLA);
	}
	free_cover(Fold);
	break;

    case KEY_primes:            /* generate all prime implicants */
	EXEC(PLA->F = primes_consensus(cube2list(PLA->F, PLA->D)), 
						    "PRIMES     ", PLA->F);
	break;

    case KEY_map:               /* print out a Karnaugh map of function */
	map(PLA->F);
	print_solution = FALSE;
	break;



/******************** Output phase and bit pairing ********************/

    case KEY_opo:               /* sasao output phase assignment */
	phase_assignment(PLA, strategy);
	break;

    case KEY_opoall:		/* try all phase assignments (!) */
	if (first < 0 || first >= cube.part_size[cube.output]) {
	    first = 0;
	}
	if (last < 0 || last >= cube.part_size[cube.output]) {
	    last = cube.part_size[cube.output] - 1;
	}
	opoall(PLA, first, last, strategy);
	break;

    case KEY_pair:              /* find an optimal pairing */
	find_optimal_pairing(PLA, strategy);
	break;

    case KEY_pairall:		/* try all pairings !! */
	pair_all(PLA, strategy);
	break;



/******************** Simple cover operations ********************/

    case KEY_echo:				/* echo the PLA */
	break;

    case KEY_taut:				/* tautology check */
	fprintf(outStream, "ON-set is%sa tautology\n",
	    tautology(cube1list(PLA->F)) ? " " : " not ");
	print_solution = FALSE;
	break;

    case KEY_contain:				/* single cube containment */
	PLA->F = sf_contain(PLA->F);
	break;

    case KEY_intersect:				/* cover intersection */
	PLA->F = cv_intersect(PLA->F, PLA1->F);
	break;

    case KEY_union:				/* cover union */
	PLA->F = sf_union(PLA->F, PLA1->F);
	break;

    case KEY_disjoint:				/* make cover disjoint */
	PLA->F = make_disjoint(PLA->F);
	break;

    case KEY_dsharp:				/* cover disjoint-sharp */
	PLA->F = cv_dsharp(PLA->F, PLA1->F);
	break;

    case KEY_sharp:				/* cover sharp */
	PLA->F = cv_sharp(PLA->F, PLA1->F);
	break;

    case KEY_lexsort:				/* lexical sort order */
	PLA->F = lex_sort(PLA->F);
	break;

    case KEY_stats:				/* print info on size */
	if (! summary) PLA_summary(PLA);
	print_solution = FALSE;
	break;

    case KEY_minterms:				/* explode into minterms */
	if (first < 0 || first >= cube.num_vars) {
	    first = 0;
	}
	if (last < 0 || last >= cube.num_vars) {
	    last = cube.num_vars - 1;
	}
	PLA->F = sf_dupl(unravel_range(PLA->F, first, last));
	break;

    case KEY_d1merge:				/* distance-1 merge */
	if (first < 0 || first >= cube.num_vars) {
	    first = 0;
	}
	if (last < 0 || last >= cube.num_vars) {
	    last = cube.num_vars - 1;
	}
	for(i = first; i <= last; i++) {
	    PLA->F = d1merge(PLA->F, i);
	}
	break;

    case KEY_d1merge_in:		/* distance-1 merge inputs only */
	for(i = 0; i < cube.num_binary_vars; i++) {
	    PLA->F = d1merge(PLA->F, i);
	}
	break;

    case KEY_PLA_verify:		/* check two PLAs for equivalence */
	EXECUTE(error = PLA_verify(PLA, PLA1), VERIFY_TIME, PLA->F, cost);
	if (error) {
	    fprintf(outStream, "PLA comparison failed; the PLA's are not equivalent\n");
	    exit(1);
	} else {
	    fprintf(outStream, "PLA's compared equal\n");
	    exit(0);
	}
	break;	/* silly */

    case KEY_verify:			/* check two covers for equivalence */
	Fold = PLA->F;	Dold = PLA->D;	F = PLA1->F;
	EXECUTE(error=verify(F, Fold, Dold), VERIFY_TIME, PLA->F, cost);
	if (error) {
	    fprintf(outStream, "PLA comparison failed; the PLA's are not equivalent\n");
	    exit(1);
	} else {
	    fprintf(outStream, "PLA's compared equal\n");
	    exit(0);
	}	
	break;	/* silly */

    case KEY_check:			/* check consistency */
	(void) check_consistency(PLA);
	print_solution = FALSE;
	break;

    case KEY_mapdc:			/* compute don't care set */
	map_dcset(PLA);
	out_type = FD_type;
	break;

    case KEY_equiv:
	find_equiv_outputs(PLA);
	print_solution = FALSE;
	break;

    case KEY_separate:			/* remove PLA->D from PLA->F */
	PLA->F = complement(cube2list(PLA->D, PLA->R));
	break;

    case KEY_xor: {
	pcover T1 = cv_intersect(PLA->F, PLA1->R);
	pcover T2 = cv_intersect(PLA1->F, PLA->R);
	free_cover(PLA->F);
	PLA->F = sf_contain(sf_join(T1, T2));
	free_cover(T1);
	free_cover(T2);
	break;
    }

    case KEY_fsm: {
	disassemble_fsm(PLA, summary);
	print_solution = FALSE;
	break;
    }

    case KEY_test: {
	pcover T, E;
	T = sf_join(PLA->D, PLA->R);
	E = new_cover(10);
	sf_free(PLA->F);
	EXECUTE(PLA->F = complement(cube1list(T)), COMPL_TIME, PLA->F, cost);
	EXECUTE(PLA->F = expand(PLA->F, T, FALSE), EXPAND_TIME, PLA->F, cost);
	EXECUTE(PLA->F = irredundant(PLA->F, E), IRRED_TIME, PLA->F, cost);
	sf_free(T);
	T = sf_join(PLA->F, PLA->R);
	EXECUTE(PLA->D = expand(PLA->D, T, FALSE), EXPAND_TIME, PLA->D, cost);
	EXECUTE(PLA->D = irredundant(PLA->D, E), IRRED_TIME, PLA->D, cost);
	sf_free(T);
	sf_free(E);
	break;
    }


    }

    /* Print a runtime summary if trace mode enabled */
    if (trace) {
	runtime();
    }

    /* Print total runtime */
    if (summary || trace) {
	print_trace(PLA->F, option_table[option].name, ptime()-start);
    }

    /* Output the solution */
    if (print_solution) {
	EXECUTE(fprint_pla(outStream, PLA, out_type), WRITE_TIME, PLA->F, cost);
    }

    /* Crash and burn if there was a verify error */
    if (error) {
	fatal("cover verification failed");
    }

    /* cleanup all used memory */
    free_PLA(PLA);
    FREE(cube.part_size);
    setdown_cube();             /* free the cube/cdata structure data */
    sf_cleanup();               /* free unused set structures */
    sm_cleanup();               /* sparse matrix cleanup */

    exit(0);
}


getPLA(opt, argc, argv, option, PLA, out_type)
int opt;
int argc;
char *argv[];
int option;
pPLA *PLA;
int out_type;
{
    FILE *fp;
    int needs_dcset, needs_offset;
    char *fname;

    if (opt >= argc) {
	fp = stdin;
	fname = "(stdin)";
    } else {
	fname = argv[opt];
	if (strcmp(fname, "-") == 0) {
	    fp = stdin;
	} else if ((fp = fopen(argv[opt], "r")) == NULL) {
	    (void) fprintf(outStream, "%s: Unable to open %s\n", argv[0], fname);
	    exit(1);
	}
    }
    if (option_table[option].key == KEY_echo) {
	needs_dcset = (out_type & D_type) != 0;
	needs_offset = (out_type & R_type) != 0;
    } else {
	needs_dcset = option_table[option].needs_dcset;
	needs_offset = option_table[option].needs_offset;
    }

    if (read_pla(fp, needs_dcset, needs_offset, input_type, PLA) == EOF) {
	(void) fprintf(outStream, "%s: Unable to find PLA on file %s\n", argv[0], fname);
	exit(1);
    }
    (*PLA)->filename = util_strsav(fname);
    filename = (*PLA)->filename;
/*    (void) fclose(fp);*/
/* hackto support -Dmany */
    last_fp = fp;
}


runtime()
{
    int i;
    long total = 1, temp;

    for(i = 0; i < TIME_COUNT; i++) {
	total += total_time[i];
    }
    for(i = 0; i < TIME_COUNT; i++) {
	if (total_calls[i] != 0) {
	    temp = 100 * total_time[i];
	    fprintf(outStream, "# %s\t%2d call(s) for %s (%2ld.%01ld%%)\n",
		total_name[i], total_calls[i], print_time(total_time[i]),
		    temp/total, (10 * (temp%total)) / total);
	}
    }
}


init_runtime()
{
    total_name[READ_TIME] =     "READ       ";
    total_name[WRITE_TIME] =    "WRITE      ";
    total_name[COMPL_TIME] =    "COMPL      ";
    total_name[REDUCE_TIME] =   "REDUCE     ";
    total_name[EXPAND_TIME] =   "EXPAND     ";
    total_name[ESSEN_TIME] =    "ESSEN      ";
    total_name[IRRED_TIME] =    "IRRED      ";
    total_name[GREDUCE_TIME] =  "REDUCE_GASP";
    total_name[GEXPAND_TIME] =  "EXPAND_GASP";
    total_name[GIRRED_TIME] =   "IRRED_GASP ";
    total_name[MV_REDUCE_TIME] ="MV_REDUCE  ";
    total_name[RAISE_IN_TIME] = "RAISE_IN   ";
    total_name[VERIFY_TIME] =   "VERIFY     ";
    total_name[PRIMES_TIME] =   "PRIMES     ";
    total_name[MINCOV_TIME] =   "MINCOV     ";
}


subcommands()
{
    int i, col;
    fprintf(outStream, "                ");
    col = 16;
    for(i = 0; option_table[i].name != 0; i++) {
	if ((col + strlen(option_table[i].name) + 1) > 76) {
	    fprintf(outStream, ",\n                ");
	    col = 16;
	} else if (i != 0) {
	    fprintf(outStream, ", ");
	}
	fprintf(outStream, "%s", option_table[i].name);
	col += strlen(option_table[i].name) + 2;
    }
    fprintf(outStream, "\n");
}


usage()
{
    fprintf(outStream, "%s\n\n", VERSION);
    fprintf(outStream, "SYNOPSIS: espresso [options] [file]\n\n");
    fprintf(outStream, "  -d        Enable debugging\n");
    fprintf(outStream, "  -e[opt]   Select espresso option:\n");
    fprintf(outStream, "                fast, ness, nirr, nunwrap, onset, pos, strong,\n");
    fprintf(outStream, "                eat, eatdots, kiss, random\n");
    fprintf(outStream, "  -o[type]  Select output format:\n");
    fprintf(outStream, "                f, fd, fr, fdr, pleasure, eqntott, kiss, cons\n");
    fprintf(outStream, "  -rn-m     Select range for subcommands:\n");
    fprintf(outStream, "                d1merge: first and last variables (0 ... m-1)\n");
    fprintf(outStream, "                minterms: first and last variables (0 ... m-1)\n");
    fprintf(outStream, "                opoall: first and last outputs (0 ... m-1)\n");
    fprintf(outStream, "  -s        Provide short execution summary\n");
    fprintf(outStream, "  -t        Provide longer execution trace\n");
    fprintf(outStream, "  -x        Suppress printing of solution\n");
    fprintf(outStream, "  -v[type]  Verbose debugging detail (-v '' for all)\n");
    fprintf(outStream, "  -D[cmd]   Execute subcommand 'cmd':\n");
    subcommands();
    fprintf(outStream, "  -Sn       Select strategy for subcommands:\n");
    fprintf(outStream, "                opo: bit2=exact bit1=repeated bit0=skip sparse\n");
    fprintf(outStream, "                opoall: 0=minimize, 1=exact\n");
    fprintf(outStream, "                pair: 0=algebraic, 1=strongd, 2=espresso, 3=exact\n");
    fprintf(outStream, "                pairall: 0=minimize, 1=exact, 2=opo\n");
    fprintf(outStream, "                so_espresso: 0=minimize, 1=exact\n");
    fprintf(outStream, "                so_both: 0=minimize, 1=exact\n");
}

/*
 *  Hack for backward compatibility (ACK! )
 */

backward_compatibility_hack(argc, argv, option, out_type)
int *argc;
char **argv;
int *option;
int *out_type;
{
    int i, j;

    /* Scan the argument list for something to do (default is ESPRESSO) */
    *option = 0;
    for(i = 1; i < (*argc)-1; i++) {
	if (strcmp(argv[i], "-do") == 0) {
	    for(j = 0; option_table[j].name != 0; j++)
		if (strcmp(argv[i+1], option_table[j].name) == 0) {
		    *option = j;
		    delete_arg(argc, argv, i+1);
		    delete_arg(argc, argv, i);
		    break;
		}
	    if (option_table[j].name == 0) {
		(void) fprintf(outStream,
		 "espresso: bad keyword \"%s\" following -do\n",argv[i+1]);
		exit(1);
	    }
	    break;
	}
    }

    for(i = 1; i < (*argc)-1; i++) {
	if (strcmp(argv[i], "-out") == 0) {
	    for(j = 0; pla_types[j].key != 0; j++)
		if (strcmp(pla_types[j].key+1, argv[i+1]) == 0) {
		    *out_type = pla_types[j].value;
		    delete_arg(argc, argv, i+1);
		    delete_arg(argc, argv, i);
		    break;
		}
	    if (pla_types[j].key == 0) {
		(void) fprintf(outStream,
		   "espresso: bad keyword \"%s\" following -out\n",argv[i+1]);
		exit(1);
	    }
	    break;
	}
    }

    for(i = 1; i < (*argc); i++) {
	if (argv[i][0] == '-') {
	    for(j = 0; esp_opt_table[j].name != 0; j++) {
		if (strcmp(argv[i]+1, esp_opt_table[j].name) == 0) {
		    delete_arg(argc, argv, i);
		    *(esp_opt_table[j].variable) = esp_opt_table[j].value;
		    break;
		}
	    }
	}
    }

    if (check_arg(argc, argv, "-fdr")) input_type = FDR_type;
    if (check_arg(argc, argv, "-fr")) input_type = FR_type;
    if (check_arg(argc, argv, "-f")) input_type = F_type;
}


/* delete_arg -- delete an argument from the argument list */
delete_arg(argc, argv, num)
int *argc, num;
register char *argv[];
{
    register int i;
    (*argc)--;
    for(i = num; i < *argc; i++) {
	argv[i] = argv[i+1];
    }
}


/* check_arg -- scan argv for an argument, and return TRUE if found */
bool check_arg(argc, argv, s)
int *argc;
register char *argv[], *s;
{
    register int i;
    for(i = 1; i < *argc; i++) {
	if (strcmp(argv[i], s) == 0) {
	    delete_arg(argc, argv, i);
	    return TRUE;
	}
    }
    return FALSE;
}
