#include "espresso.h"

/** MM: int na indikaciu chyby */
int set_pair(pPLA PLA)
{
    return set_pair1(PLA, TRUE3);
}

/** MM: int na indikaciu chyby */
/** MM: kontroluje hlavne fatal */
int set_pair1(pPLA PLA, int adjust_labels)
{
    int i, var, *paired, newvar;
    int old_num_vars, old_num_binary_vars, old_size, old_mv_start;
    int *new_part_size, new_num_vars, new_num_binary_vars, new_mv_start;
    ppair pair = PLA->pair;
    char scratch[1000], **oldlabel, *var1, *var1bar, *var2, *var2bar;

    if (adjust_labels)
    	makeup_labels(PLA);

    /* Check the pair structure for valid entries and see which binary
	variables are left unpaired
    */
    paired = ALLOC(bool3, cube->num_binary_vars);
    for(var = 0; var < cube->num_binary_vars; var++)
    	paired[var] = FALSE3;
    for(i = 0; i < pair->cnt; i++)
    	if ((pair->var1[i] > 0 && pair->var1[i] <= cube->num_binary_vars) && /** MM: kontroluje ci je premenna v rozsahu, kde su binarne premenne */
    	     (pair->var2[i] > 0 && pair->var2[i] <= cube->num_binary_vars)) {
    	    paired[pair->var1[i]-1] = TRUE3;
    	    paired[pair->var2[i]-1] = TRUE3;
    	} else{
    	    fatal2("can only pair binary-valued variables");
    	    FREE(paired);
    	    return -1;
        }

    PLA->F = delvar(pairvar(PLA->F, pair), paired);
    PLA->R = delvar(pairvar(PLA->R, pair), paired);
    PLA->D = delvar(pairvar(PLA->D, pair), paired);

    /* Now painfully adjust the cube size */
    old_size = cube->size;
    old_num_vars = cube->num_vars;
    old_num_binary_vars = cube->num_binary_vars;
    old_mv_start = cube->first_part[cube->num_binary_vars];
    /* Create the new cube->part_size vector and setup the cube structure */
    new_num_binary_vars = 0;
    for(var = 0; var < old_num_binary_vars; var++)
    	new_num_binary_vars += (paired[var] == FALSE3);
    new_num_vars = new_num_binary_vars + pair->cnt;
    new_num_vars += old_num_vars - old_num_binary_vars;
    new_part_size = ALLOC(int, new_num_vars);
    for(var = 0; var < pair->cnt; var++)
    	new_part_size[new_num_binary_vars + var] = 4;
    for(var = 0; var < old_num_vars - old_num_binary_vars; var++)
    	new_part_size[new_num_binary_vars + pair->cnt + var] =
    	    cube->part_size[old_num_binary_vars + var];
    setdown_cube();
    FREE(cube->part_size);
    cube->num_vars = new_num_vars; /** MM: upravy vykonane by nemali viest k zlym hodnotam premennych */
    cube->num_binary_vars = new_num_binary_vars;
    cube->part_size = new_part_size;
    cube_setup();

    /* hack with the labels to get them correct */
    if (adjust_labels) {
    	oldlabel = PLA->label;
    	PLA->label = ALLOC(char *, cube->size);
		for (int k = 0; k < cube->size; PLA->label[k++]=NULL); /** MM: NULLovanie pola */
    	for(var = 0; var < pair->cnt; var++) {
    	    newvar = cube->num_binary_vars*2 + var*4;
    	    var1 = oldlabel[ (pair->var1[var]-1) * 2 + 1];
    	    var2 = oldlabel[ (pair->var2[var]-1) * 2 + 1];
    	    var1bar = oldlabel[ (pair->var1[var]-1) * 2];
    	    var2bar = oldlabel[ (pair->var2[var]-1) * 2];
    	    (void) snprintf(scratch, sizeof(scratch), "%s+%s", var1bar, var2bar); /** MM: zmenene na secure version */
    	    PLA->label[newvar] = util_strsav(scratch);
    	    (void) snprintf(scratch, sizeof(scratch), "%s+%s", var1bar, var2); /** MM: zmenene na secure version */
    	    PLA->label[newvar+1] = util_strsav(scratch);
    	    (void) snprintf(scratch, sizeof(scratch), "%s+%s", var1, var2bar); /** MM: zmenene na secure version */
    	    PLA->label[newvar+2] = util_strsav(scratch);
    	    (void) snprintf(scratch, sizeof(scratch), "%s+%s", var1, var2); /** MM: zmenene na secure version */
    	    PLA->label[newvar+3] = util_strsav(scratch);
    	}
    	/* Copy the old labels for the unpaired binary vars */
    	i = 0;
    	for(var = 0; var < old_num_binary_vars; var++) {
    	    if (paired[var] == FALSE3) {
        		PLA->label[2*i] = oldlabel[2*var];
        		PLA->label[2*i+1] = oldlabel[2*var+1];
        		oldlabel[2*var] = oldlabel[2*var+1] = (char *) NULL;
        		i++;
    	    }
    	}
    	/* Copy the old labels for the remaining unpaired vars */
    	new_mv_start = cube->num_binary_vars*2 + pair->cnt*4;
    	for(i = old_mv_start; i < old_size; i++) {
    	    PLA->label[new_mv_start + i - old_mv_start] = oldlabel[i];
    	    oldlabel[i] = (char *) NULL;
    	}
    	/* free remaining entries in oldlabel */
    	for(i = 0; i < old_size; i++)
    	    if (oldlabel[i] != (char *) NULL)
        		FREE(oldlabel[i]);
    	FREE(oldlabel);
    }

    /* the paired variables should not be sparse (cf. mv_reduce/raise_in)*/
    for(var = 0; var < pair->cnt; var++)
    	cube->sparse[cube->num_binary_vars + var] = 0;
    FREE(paired);
    return 0;
}

pcover pairvar(pset_family A, ppair pair)
{
    register pcube last, p;
    register int val, p1, p2, b1, b0;
    int insert_col, pairnum;

    insert_col = cube->first_part[cube->num_vars - 1];

    /* stretch the cover matrix to make room for the paired variables */
    A = sf_delcol(A, insert_col, -4*pair->cnt);

    /* compute the paired values */
    foreach_set(A, last, p) {
    	for(pairnum = 0; pairnum < pair->cnt; pairnum++) {
    	    p1 = cube->first_part[pair->var1[pairnum] - 1];
    	    p2 = cube->first_part[pair->var2[pairnum] - 1];
    	    b1 = is_in_set(p, p2+1);
    	    b0 = is_in_set(p, p2);
    	    val = insert_col + pairnum * 4;
    	    if (/* a0 */ is_in_set(p, p1)) {
        		if (b0)
        		    set_insert(p, val + 3);
        		if (b1)
        		    set_insert(p, val + 2);
    	    }
    	    if (/* a1 */ is_in_set(p, p1+1)) {
        		if (b0)
        		    set_insert(p, val + 1);
        		if (b1)
        		    set_insert(p, val);
    	    }
    	}
    }
    return A;
}


/* delvar -- delete variables from A, minimize the number of column shifts */
pcover delvar(pset_family A, int *paired)
{
    bool3 run;
    int first_run, run_length, var, offset = 0;

    run = FALSE3; run_length = 0;
    for(var = 0; var < cube->num_binary_vars; var++)
	if (paired[var])
	    if (run)
    		run_length += cube->part_size[var];
	    else {
    		run = TRUE3;
    		first_run = cube->first_part[var];
    		run_length = cube->part_size[var];
	    }
	else
	    if (run) {
    		A = sf_delcol(A, first_run-offset, run_length);
    		run = FALSE3;
    		offset += run_length;
	    }
    if (run)
    	A = sf_delcol(A, first_run-offset, run_length);
    return A;
}

/*
    find_optimal_pairing -- find which binary variables should be paired
    to maximally reduce the number of terms

    This is essentially the technique outlined by T. Sasao in the
    Trans. on Comp., Oct 1984.  We estimate the cost of pairing each
    pair individually using 1 of 4 strategies: (1) algebraic division
    of F by the pair (exactly T. Sasao technique); (2) strong division
    of F by the paired variables (using REDUCE/EXPAND/ IRREDUNDANT from
    espresso); (3) full minimization using espresso; (4) exact
    minimization.  These are in order of both increasing accuracy and
    increasing difficulty (!)

    Once the n squared pairs have been evaluated, T. Sasao proposes a
    graph covering of nodes by disjoint edges.  For now, I solve this
    problem exhaustively (complexity = (n-1)*(n-3)*...*3*1 for n
    variables when n is even).  Note that solving this problem exactly
    is the same as evaluating the cost function for all possible
    pairings.

			       n       pairs

			     1, 2           1
			     3, 4           3
			     5, 6          15
			     7, 8         105
			     9,10         945
			    11,12      10,395
			    13,14     135,135
			    15,16   2,027,025
			    17,18  34,459,425
			    19,20 654,729,075
*/
/** MM: int na detekciu chyby; -1 pre chybu */
/** kontroluje pair_best_cost, find_pairing_cost a espresso */
int find_optimal_pairing(pPLA PLA, int strategy)
{
    int i, j, **cost_array, tmp;

    /** MM: ak chyba - spustane s 0 binarnymi premennymi */
    if (cube->num_binary_vars == 0){
        fatal2("Can not use pair() with 0 binary variables!");
        return -1;
    }
    cost_array = find_pairing_cost(PLA, strategy);
    if (cost_array == NULL){ /** MM: ak chyba vo find_pairing_cost */
        return -1;
    }

    if (summary) {
        if (tracesumoutput!=NULL){
        	fprintf(tracesumoutput, "    ");
        	for(i = 0; i < cube->num_binary_vars; i++)
        	    fprintf(tracesumoutput, "%3d ", i+1);
        	fprintf(tracesumoutput, "\n");
        	for(i = 0; i < cube->num_binary_vars; i++) {
        	    fprintf(tracesumoutput, "%3d ", i+1);
        	    for(j = 0; j < cube->num_binary_vars; j++)
            		fprintf(tracesumoutput, "%3d ", cost_array[i][j]);
        	    fprintf(tracesumoutput, "\n");
        	}
        }
    }

    if (PLA->pair!=NULL) { pair_free(PLA->pair); PLA->pair=NULL;} /** MM: pridane kvoli moznosti opakovania */
    if (cube->num_binary_vars <= 14) {
    	PLA->pair = pair_best_cost(cost_array);
    } else {
    	(void) greedy_best_cost(cost_array, &(PLA->pair));
    }
    if (PLA->pair == NULL){ /** MM: ak chyba v pair_best_cost alebo greedy_best_cost */
        fatal2("error generating pairs in pair()!");
        goto cleanup;
    }
    
    if (standardoutput!=NULL) {
        fprintf(standardoutput, "# ");
        print_pair(PLA->pair, standardoutput);
    }
	
    for(i = 0; i < cube->num_binary_vars; i++)
    	FREE(cost_array[i]);
    FREE(cost_array);

    tmp = set_pair(PLA);
    if (tmp == -1){ /** MM: ak fatal v set_pair1 */
        return -1;
    }
    
    EXEC_S(PLA->F = espresso(PLA->F,PLA->D,PLA->R),"ESPRESSO  ",PLA->F);

    return (PLA->F==NULL)?-1:0; /** MM: ak chyba v espresso() */

/** MM: zmaze cost_array a vrati -1 */
cleanup:
    for(i = 0; i < cube->num_binary_vars; i++)
    	FREE(cost_array[i]);
    FREE(cost_array);
    return -1;
}

/** MM: pridana kontrola kvoli set_pair, espresso, expand a irredundant */
/** MM: PLA pri chybe nemodifikuje */
int **find_pairing_cost(pPLA PLA, int strategy)
{
    int var1, var2, **cost_array;
    int i, j, xnum_binary_vars, xnum_vars, *xpart_size, cost, tmp;
    pcover T, Fsave, Dsave, Rsave;
    pset mask;

    /* data is returned in the cost array */
    cost_array = ALLOC(int *, cube->num_binary_vars); /** MM: alokovanie pola ukazatelov */
    for(i = 0; i < cube->num_binary_vars; i++)
    	cost_array[i] = ALLOC(int, cube->num_binary_vars); /** MM: alokovanie poli intov a ich pridelenie do predchadzajuceho pola ukazatelov */
    for(i = 0; i < cube->num_binary_vars; i++)
    	for(j = 0; j < cube->num_binary_vars; j++)
    	    cost_array[i][j] = 0;

    /* Setup the pair structure for pairing variables together */
	if (PLA->pair!=NULL) pair_free(PLA->pair); /** MM: pre istotu kvoli memory-leaku */
    PLA->pair = pair_new(1);
    PLA->pair->cnt = 1;

    for(var1 = 0; var1 < cube->num_binary_vars-1; var1++) {
    	for(var2 = var1+1; var2 < cube->num_binary_vars; var2++) {
    	    /* if anything but simple strategy, perform setup */
    	    if (strategy > 0) {
        		/* save the original covers */
        		Fsave = sf_save(PLA->F);
        		Dsave = sf_save(PLA->D);
        		Rsave = sf_save(PLA->R);
        
        		/* save the original cube structure */
        		xnum_binary_vars = cube->num_binary_vars;
        		xnum_vars = cube->num_vars;
        		xpart_size = ALLOC(int, cube->num_vars);
        		for(i = 0; i < cube->num_vars; i++)
        		    xpart_size[i] = cube->part_size[i];
        
        		/* pair two variables together */
        		PLA->pair->var1[0] = var1 + 1;
        		PLA->pair->var2[0] = var2 + 1;
        		tmp = set_pair1(PLA, /* adjust_labels */ FALSE3);
        		if (tmp == -1){ /** MM: ak chyba v set_pair1? */
        		  goto cleanup;
                }
    	    }
    
    
    	    /* decide how to best estimate worth of this pairing */
    	    switch(strategy) {
        		case 3:
        		    /*s = "exact minimization";*/
        		    PLA->F = minimize_exact(PLA->F, PLA->D, PLA->R, 1);
        		    if (PLA->F==NULL){ /** MM: ak chyba minimize_exact, PLA->F je uz dealokovane */
                        goto cleanup;
                    }
        		    cost = Fsave->count - PLA->F->count;
        		    break;
        		case 2:
        		    /*s = "full minimization";*/
        		    PLA->F = espresso(PLA->F, PLA->D, PLA->R);
        		    if (PLA->F==NULL) { /** MM: ak chyba v espresso() */
                        goto cleanup;
                    } 
        		    cost = Fsave->count - PLA->F->count;
        		    break;
        		case 1:
        		    /*s = "strong division";*/
        		    PLA->F = reduce(PLA->F, PLA->D);
        		    PLA->F = expand(PLA->F, PLA->R, FALSE3);
        		    if (PLA->F==NULL){ /** MM: ak chyba v expand; F je dealokovane */
                        goto cleanup;
                    }
        		    PLA->F = irredundant(PLA->F, PLA->D);
        		    if (PLA->F==NULL) { /** MM: ak chyba v irredundant() */
                        goto cleanup;
                    } 
        		    cost = Fsave->count - PLA->F->count;
        		    break;
        		case 0:
        		    /*s = "weak division";*/
        		    mask = new_cube();
        		    set_or(mask, cube->var_mask[var1], cube->var_mask[var2]);
        		    T = dist_merge(sf_save(PLA->F), mask);
        		    cost = PLA->F->count - T->count;
        		    sf_free(T);
        		    set_free(mask);
    	    }
    
    	    cost_array[var1][var2] = cost;
    
    	    if (strategy > 0) {
        		/* restore the original cube structure -- free the new ones */
        		setdown_cube();
        		FREE(cube->part_size);
        		cube->num_binary_vars = xnum_binary_vars; /** MM: ak bola pred upravami kocka v dobrom stave, bude aj teraz */
        		cube->num_vars = xnum_vars;
        		cube->part_size = xpart_size;
        		cube_setup();
        
        		/* restore the original cover(s) -- free the new ones */
        		sf_free(PLA->F);
        		sf_free(PLA->D);
        		sf_free(PLA->R);
        		PLA->F = Fsave;
        		PLA->D = Dsave;
        		PLA->R = Rsave;
    	    }
    	}
    }

    pair_free(PLA->pair);
    PLA->pair = NULL;
    return cost_array;
    
cleanup:
    /** MM: Rollback vsetkych zmien */
    /* restore the original cube structure -- free the new ones */
	setdown_cube();
	FREE(cube->part_size);
	cube->num_binary_vars = xnum_binary_vars; /** MM: ak bola pred upravami kocka v dobrom stave, bude aj teraz */
	cube->num_vars = xnum_vars;
	cube->part_size = xpart_size;
	cube_setup();
	/* restore the original cover(s) -- free the new ones */
	sf_free(PLA->F);
	sf_free(PLA->D);
	sf_free(PLA->R);
	PLA->F = Fsave;
	PLA->D = Dsave;
	PLA->R = Rsave;
	/** MM: zmazanie pair */
	pair_free(PLA->pair);
    PLA->pair = NULL;
    /** MM: zmazanie pola */
    for(i = 0; i < cube->num_binary_vars; i++)
       FREE(cost_array[i]);
    FREE(cost_array);
    return NULL;
}

static int best_cost;
static int **cost_array;
static ppair best_pair;
static pset best_phase;
static pPLA global_PLA;
static pcover best_F, best_D, best_R;
static int pair_minim_strategy;

/** MM: pridana kontrola fp */
void print_pair(ppair pair, FILE* fd)
{
    int i;

    if (fd){
        fprintf(fd, "pair is");
        for(i = 0; i < pair->cnt; i++)
        	fprintf (fd, " (%d %d)", pair->var1[i], pair->var2[i]);
        fprintf(fd, "\n");
    }
}

/** MM: ukazatel na ppair ukazuje na PLA->pair!!! */
/** MM: to ale nevadi, lebo pred volanim sa pripadne pair uvolni */
int greedy_best_cost(int **cost_array_local, ppair *pair_p)
{
    int i, j, besti, bestj, maxcost, total_cost;
    pset cand;
    ppair pair;

    pair = pair_new(cube->num_binary_vars);
    cand = set_full(cube->num_binary_vars);
    total_cost = 0;

    while (set_ord(cand) >= 2) {
    	maxcost = -1;
    	for(i = 0; i < cube->num_binary_vars; i++) {
    	    if (is_in_set(cand, i)) {
        		for(j = i+1; j < cube->num_binary_vars; j++) {
        		    if (is_in_set(cand, j)) {
            			if (cost_array_local[i][j] > maxcost) {
            			    maxcost = cost_array_local[i][j];
            			    besti = i;
            			    bestj = j;
            			}
        		    }
        		}
    	    }
    	}
    	pair->var1[pair->cnt] = besti+1;
    	pair->var2[pair->cnt] = bestj+1;
    	pair->cnt++;
    	set_remove(cand, besti);
    	set_remove(cand, bestj);
    	total_cost += maxcost;
    }
    set_free(cand);
    *pair_p = pair;
    return total_cost;
}

/** MM: ak chyba, vrati NULL */
ppair pair_best_cost(int **cost_array_local)
{
    ppair pair;
    pset candidate;
    int tmp;
    
    if (cost_array_local==NULL) return NULL; /** MM: kontrola parametra */

    best_cost = -1;
    best_pair = NULL;
    cost_array = cost_array_local;

    pair = pair_new(cube->num_binary_vars);
    candidate = set_full(cube->num_binary_vars);
    tmp = generate_all_pairs(pair, cube->num_binary_vars, candidate, find_best_cost);
    pair_free(pair);
    set_free(candidate);
    /** MM: best_pair == NULL <=> tmp == -1, takze podmienka by nemala byt nikdy splnena */
    if ( (tmp==-1) && (best_pair!=NULL) ) { /** MM: kontrola chyby mohla byt posunuta az sem */
        pair_free(best_pair); best_pair = NULL;
        /** MM: profi program => umiestnit tu vypis so ziadostou o zaslanie PLA, pri ktorom bola tato podmienka splnena */
    }
    
    return best_pair; 
}

/** MM: zmeneny prototyp na int, aby to pasovalo do generate_all_pairs */
int find_best_cost(register ppair pair)
{
    register int i, cost;

    cost = 0;
    for(i = 0; i < pair->cnt; i++)
    	cost += cost_array[pair->var1[i]-1][pair->var2[i]-1];
    if (cost > best_cost) {
    	best_cost = cost;
    	if (best_pair != NULL) { /** MM: oprava pred memory leakom */
        	pair_free(best_pair);
       	}
    	best_pair = pair_save(pair, pair->cnt);
    }
    if ((debug & MINCOV) && trace) {
    	if (debugoutput!=NULL) {
            fprintf(debugoutput, "cost is %d ", cost);
            print_pair(pair, debugoutput);
        }
    }
    return 0;
}

/*
    pair_all: brute-force approach to try all possible pairings

    pair_strategy is:
	2) for espresso
	3) for minimize_exact
	4) for phase assignment
*/
/** MM: zmenene na int kvoli indikovani chyby */
/** MM: opravene generate_all_pairs, fatal a set_pair */
int pair_all(pPLA PLA, int pair_strategy)
{
    ppair pair;
    pset candidate;
    int tmp, ret = 0; /** MM: pociatocna inicializacia hodnoty ret */
    
    if (cube->num_binary_vars == 0){ /** MM: ak chyba cube->num_binary_vars */
        fatal2("Can not use pairall() with 0 binary variables!");
        return -1;
    }
    
    global_PLA = PLA;
    pair_minim_strategy = pair_strategy;
    best_cost = PLA->F->count + 1;
    best_pair = NULL;
    best_phase = NULL;
    best_F = best_D = best_R = NULL; /** MM: nastavuje best_X na NULL */
    pair = pair_new(cube->num_binary_vars);
    candidate = set_fill(set_new(cube->num_binary_vars), cube->num_binary_vars);

    tmp = generate_all_pairs(pair, cube->num_binary_vars, candidate, minimize_pair);

    pair_free(pair);
    set_free(candidate);
    if (tmp==-1){ /** MM: kontrola posunuta az sem */
        return -1;
    }
    /** MM: moze sa vykonat, len ak generate prebehlo bez problemov */
    /** MM: ak chyba v generate_all_pairs, tak ziadne best_X nie je alokovane ani nastavene */
	if (PLA->pair!=NULL) pair_free(PLA->pair); /** MM: pridane kvoli memory-leaku */
    PLA->pair = best_pair;
	if (PLA->phase!=NULL) FREE(PLA->phase); /** MM: pridane kvoli memory-leaku */
    PLA->phase = best_phase;
/* not really necessary
    if (phase != NULL)
	(void) set_phase(PLA->phase);
*/
    tmp = set_pair(PLA);
    if (tmp==-1){ /** MM: fatal v set_pair1; nic nie je zmenene */
        ret = -1; goto outro;
    }
    if (standardoutput){
        fprintf(standardoutput, "# ");
        print_pair(PLA->pair, standardoutput);
    }

outro:
    sf_free(PLA->F);
    sf_free(PLA->D);
    sf_free(PLA->R);
    PLA->F = best_F;
    PLA->D = best_D;
    PLA->R = best_R;
    return ret;
}


/*
 *  minimize_pair -- called as each pair is generated
 */
/** MM: int na kontrolu chyby; -1 pri chybe */
/** MM: uz kontroluje parametre, PLA a kocku si zalohuje a nemeni */
/** MM: ma nastavit best_X na nejaku hodnotu */
int minimize_pair(ppair pair)
{
    pcover Fsave, Dsave, Rsave;
    int i, xnum_binary_vars, xnum_vars, *xpart_size, ret = 0, tmp; /** MM: inicializacia ret na dobru hodnotu */
    
    if (pair==NULL) return -1;

    /* save the original covers */
    Fsave = sf_save(global_PLA->F);
    Dsave = sf_save(global_PLA->D);
    Rsave = sf_save(global_PLA->R);
    

    /* save the original cube structure */
    xnum_binary_vars = cube->num_binary_vars;
    xnum_vars = cube->num_vars;
    xpart_size = ALLOC(int, cube->num_vars);
    if ( (Fsave==NULL) || (Dsave==NULL) || (Rsave==NULL) || (xpart_size==NULL) ){
        sf_free(Fsave); sf_free(Dsave); sf_free(Rsave); FREE(xpart_size); /** MM: ak chyba alokacie */
        return -1;
    }
    for(i = 0; i < cube->num_vars; i++)
    	xpart_size[i] = cube->part_size[i];

    /* setup the paired variables */
	if (global_PLA->pair!=NULL) pair_free(global_PLA->pair); /** MM: kvoli memory-leaku */
    global_PLA->pair = pair;
    tmp = set_pair1(global_PLA, /* adjust_labels */ FALSE3);
    if (tmp==-1){ /** MM: ak chyba, nebolo este nic zmenene */
        sf_free(Fsave); sf_free(Dsave); sf_free(Rsave); FREE(xpart_size);
        global_PLA->pair = NULL;
        return -1;
    }
    /** MM: odtialto sa musi ist cez cleanup */

    /* call the minimizer */
    if (summary)
    	print_pair(pair, tracesumoutput);
    switch(pair_minim_strategy) {
    	case 2:
    	    EXEC_S(tmp = phase_assignment(global_PLA,0), "OPO       ", global_PLA->F);
    	    if (tmp==-1) {
                ret = -1; /** MM: nenastavi sa ziadne best_X!!! */
                goto cleanup; /** MM: ak chyba v phase_assignment */
            }
    	    if (summary){
        		if (tracesumoutput!=NULL) fprintf(tracesumoutput, "# phase is %s\n", pc1(global_PLA->phase));
            }
    	    break;
    	case 1:
    	    EXEC_S(global_PLA->F = minimize_exact(global_PLA->F, global_PLA->D,
        		global_PLA->R, 1), "EXACT     ", global_PLA->F);
    	    if (global_PLA->F==NULL){ /** MM: ak chyba minimize_exact, PLA->F je uz dealokovane */
                ret = -1;
                goto cleanup;
            }
    	    break;
    	case 0:
    	    EXEC_S(global_PLA->F = espresso(global_PLA->F, global_PLA->D,
    		global_PLA->R), "ESPRESSO  ", global_PLA->F);
    		if (global_PLA->F==NULL) { /** MM: ak chyba v espresso() */
                ret = -1;
                goto cleanup;
            }
    	    break;
    	default:
    	    break;
    }

    /* see if we have a new best solution */
    if (global_PLA->F->count < best_cost) {
    	best_cost = global_PLA->F->count;
    	if (best_pair != NULL) { /** MM: oprava pred memory leakom */
    		pair_free(best_pair);
    		best_pair = NULL;
    	}
    	best_pair = pair_save(pair, pair->cnt);
		FREE(best_phase); /** MM: osetrene proti memory-leaku */
    	best_phase = global_PLA->phase!=NULL?set_save(global_PLA->phase):NULL;
    	if (best_F != NULL) sf_free(best_F);
    	if (best_D != NULL) sf_free(best_D);
    	if (best_R != NULL) sf_free(best_R);
    	best_F = sf_save(global_PLA->F);
    	best_D = sf_save(global_PLA->D);
    	best_R = sf_save(global_PLA->R);
    }

cleanup: /** spolocna vetva pre dobry aj zly koniec */
    /* restore the original cube structure -- free the new ones */
    setdown_cube();
    FREE(cube->part_size);
    cube->num_binary_vars = xnum_binary_vars; /** MM: ak bola pred upravami kocka v dobrom stave, bude aj teraz */
    cube->num_vars = xnum_vars;
    cube->part_size = xpart_size;
    cube_setup();

    /* restore the original cover(s) -- free the new ones */
    sf_free(global_PLA->F);
    sf_free(global_PLA->D);
    sf_free(global_PLA->R);
    global_PLA->F = Fsave;
    global_PLA->D = Dsave;
    global_PLA->R = Rsave;
    global_PLA->pair = NULL; /** MM: ukazuje na strukturu, ktoru uvolnuje niekto iny */
    FREE(global_PLA->phase); /** MM: osetrenie proti memory-leaku */
    return ret;
}

/** MM: pridana kontrola parametrov */
/** MM: int pre indikaciu chyby; -1 pri chybe */
/** MM: zmeneny prototyp z void (*action) na int (*action) [zmenene find_best_cost a minimize_pair]*/
int generate_all_pairs(ppair pair, int n, pset candidate, int (*action) (register ppair pair) )
{
    int i, j, ret = 0, tmp;
    pset recur_candidate;
    ppair recur_pair;
    
    if ( (pair==NULL) || (candidate==NULL) || (action==NULL) ) return -1; /** MM: ak chyba parametrov */

    if (set_ord(candidate) < 2) {
    	return (*action)(pair);
    }

    recur_pair = pair_save(pair, n);
    recur_candidate = set_save(candidate);

    /* Find first variable still in the candidate set */
    for(i = 0; i < n; i++)
    	if (is_in_set(candidate, i))
    	    break;

    /* Try all pairs of i with other variables */
    for(j = i+1; j < n; j++)
    	if (is_in_set(candidate, j)) {
    	    /* pair (i j) -- remove from candidate set for future pairings */
    	    set_remove(recur_candidate, i);
    	    set_remove(recur_candidate, j);
    
    	    /* add to the pair array */
    	    recur_pair->var1[recur_pair->cnt] = i+1;
    	    recur_pair->var2[recur_pair->cnt] = j+1;
    	    recur_pair->cnt++;
    
    	    /* recur looking for the end ... */
    	    tmp = generate_all_pairs(recur_pair, n, recur_candidate, action);
    	    if (tmp==-1){ /** MM: ak chyba */
                ret = -1; goto cleanup;
            }
    
    	    /* now break this pair, and restore candidate set */
    	    recur_pair->cnt--;
    	    set_insert(recur_candidate, i);
    	    set_insert(recur_candidate, j);
    	}

    /* if odd, generate all pairs which do NOT include i */
    if ((set_ord(candidate) % 2) == 1) {
    	set_remove(recur_candidate, i);
    	tmp = generate_all_pairs(recur_pair, n, recur_candidate, action);
    	if (tmp==-1) ret = -1; /** MM: ak chyba */
    }

cleanup: /** MM: spolocna vetva pre vsetky konce */
    pair_free(recur_pair);
    set_free(recur_candidate);
    return ret;
}

/** MM: osetrene, ze moze vratit NULL */
ppair pair_new(register int n)
{
    register ppair pair1;

    pair1 = ALLOC(pair_t, 1);
    if (pair1==NULL) return NULL; /** MM: ak chyba alokacie */
    pair1->cnt = 0;
    pair1->var1 = ALLOC(int, n);
    pair1->var2 = ALLOC(int, n);
    if ( ((pair1->var1==NULL) || (pair1->var2==NULL) ) && (n!=0) ){ /** MM: ak chyba alokacie */
        FREE(pair1->var1); FREE(pair1->var2); FREE(pair1); return NULL; 
    }
    return pair1;
}

/** MM: vytvori kopiu pair */
/** MM: pri chybe vrati NULL */
ppair pair_save(register ppair pair, register int n)
{
    register int k;
    register ppair pair1;

    pair1 = pair_new(n);
    if (pair1==NULL) return NULL; /** MM: ak chyba alokacie */
    pair1->cnt = pair->cnt;
    for(k = 0; k < pair->cnt; k++) {
        pair1->var1[k] = pair->var1[k];
        pair1->var2[k] = pair->var2[k];
    }
    return pair1;
}


/** MM: osetrene volanie s neplatnym ukazatelom */
void pair_free(register ppair pair)
{
	if (pair==NULL) return;
    FREE(pair->var1);
    FREE(pair->var2);
    FREE(pair);
}
