/*
 *  Module: espresso.c
 *  Purpose: The main espresso algorithm
 *
 *  Returns a minimized version of the ON-set of a function
 *
 *  The following global variables affect the operation of Espresso:
 *
 *  MISCELLANEOUS:
 *      trace
 *          print trace information as the minimization progresses
 *
 *      remove_essential
 *          remove essential primes
 *
 *      single_expand
 *          if true, stop after first expand/irredundant
 *
 *  LAST_GASP or SUPER_GASP strategy:
 *      use_super_gasp
 *          uses the super_gasp strategy rather than last_gasp
 *
 *  SETUP strategy:
 *      recompute_onset
 *          recompute onset using the complement before starting
 *
 *      unwrap_onset
 *          unwrap the function output part before first expand
 *
 *  MAKE_SPARSE strategy:
 *      force_irredundant
 *          iterates make_sparse to force a minimal solution (used
 *          indirectly by make_sparse)
 *
 *      skip_make_sparse
 *          skip the make_sparse step (used by opo only)
 */

#include "espresso.h"

/** MM: uz kontroluje parametre */
/** MM: vytvara si vnutorne zalohy F a D, ale modifikuje F -> vzdy dealokuje parameter */
pcover espresso(pset_family F, pset_family D1, pset_family R)
{
    pcover E, D, Fsave;
    pset last, p;
    cost_t cost, best_cost;
    bool3 tmp_unwrap = unwrap_onset; /** MM: zaloha povodneho nastavenia */
    
    if ( (F==NULL) || (D1==NULL) || (R==NULL) ) return NULL; /** MM: ak chyba parametrov */

begin:
    Fsave = sf_save(F);		/* save original function */
    D = sf_save(D1);		/* make a scratch copy of D */
    if ( (Fsave==NULL) || (D==NULL) ) { /** MM: ak chyba alokacie */
        sf_free(Fsave); sf_free(D); unwrap_onset = tmp_unwrap; return NULL;
    }

    /* Setup has always been a problem */
    if (recompute_onset) {
        EXEC(E = simplify(cube1list(F)),     "SIMPLIFY   ", E);
        free_cover(F);
       	F = E;
    }
    cover_cost(F, &cost);
    if (unwrap_onset && (cube->part_size[cube->num_vars - 1] > 1)
      && (cost.out != cost.cubes*cube->part_size[cube->num_vars-1])
      && (cost.out < 5000))
        EXEC(F = sf_contain(unravel(F, cube->num_vars - 1)), "SETUP      ", F);

    /* Initial expand and irredundant */
    foreach_set(F, last, p) {
        RESET(p, PRIME);
    }
    EXECUTE(F = expand(F, R, FALSE3), EXPAND_TIME, F, cost);
    if (F==NULL) { /** MM: ak chyba v expand; F je dealokovane */
        sf_free(Fsave);
        free_cover(D); 
        unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
        return NULL;       
    }
    EXECUTE(F = irredundant(F, D), IRRED_TIME, F, cost);
    if (F==NULL){ /** MM: chyba v irredundant a F je uz dealokovane */
        sf_free(Fsave);
        free_cover(D); 
        unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
        return NULL;     
    }

    if ( !single_expand ) {
        if (remove_essential) {
            EXECUTE(E = essential(&F, &D), ESSEN_TIME, E, cost);
        } else {
            E = new_cover(0);
        }

    	cover_cost(F, &cost);
    	do {
    	    /* Repeat inner loop until solution becomes "stable" */
    	    do {
        		copy_cost(&cost, &best_cost);
        		EXECUTE(F = reduce(F, D), REDUCE_TIME, F, cost);
        		EXECUTE(F = expand(F, R, FALSE3), EXPAND_TIME, F, cost);
        		if (F==NULL) { /** MM: ak chyba v expand; F je dealokovane */
                    F = Fsave;
                    free_cover(D); 
                    unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
                    return NULL;       
                }
        		EXECUTE(F = irredundant(F, D), IRRED_TIME, F, cost);
    	    } 
            while (cost.cubes < best_cost.cubes);
    
    	    /* Perturb solution to see if we can continue to iterate */
    	    copy_cost(&cost, &best_cost);
    	    if (use_super_gasp) {
        		F = super_gasp(F, D, R, &cost);
        		if (F==NULL){ /** MM: ak chyba v super_gasp, F je uz dealokovane */
                    F = Fsave;
                    free_cover(D);
                    unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
                    return NULL;
                }
        		if (cost.cubes >= best_cost.cubes)
        		    break;
    	    } else {
                F = last_gasp(F, D, R, &cost);
                if (F==NULL){ /** MM: ak chyba v last_gasp, F je uz dealokovane */
                    F = Fsave;
                    free_cover(D);
                    unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
                    return NULL;
                }
    	    }
    
    	} 
        while (cost.cubes < best_cost.cubes ||
    	    (cost.cubes == best_cost.cubes && cost.total < best_cost.total));

    	/* Append the essential cubes to F */
        F = sf_append(F, E);                /* disposes of E */
        if (trace) size_stamp(F, "ADJUST     ");
    }

    /* Free the D which we used */
    free_cover(D);

    /* Attempt to make the PLA matrix sparse */
    if (! skip_make_sparse) {
        F = make_sparse(F, D1, R);
        if (F==NULL){ /** MM: ak chyba v make_sparse; F je dealokovane */
            F = Fsave;
            free_cover(D);
            unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
            return NULL;
        }
    }

    /*
     *  Check to make sure function is actually smaller !!
     *  This can only happen because of the initial unravel.  If we fail,
     *  then run the whole thing again without the unravel.
     */
    if (Fsave->count < F->count) {
    	free_cover(F);
    	F = Fsave;
    	unwrap_onset = FALSE3;
    	goto begin;
    } else {
        free_cover(Fsave);
    }
    unwrap_onset = tmp_unwrap; /** MM: obnova povodneho nastavenia */
    
    return F;
}
