/** Bag Operations Module
 *  This module contains the operations on bags. It is used when we use the bag model.
*   The operations takes place on the bdd level. Every bdd in a bag model, is a bag and hence these functions operate on bag (bdds).
*   The bags are sets, with mulitpicities of the elements.
*/
#include "bag.h"
#include <bdd.h>
#include <iostream>
#include <bitset>

using namespace std;

//#include "/tmp/config.h"

bitset<occ_bitsize> bs;
bdd temp1;


int powr(int x,int y)
{

	int result=1;

	for (int i=1;i<=y;i++)
	{
		result *=x;
	}

 return result;
}


/** Given an element, and a bag, this function get the number of occurances of that element in the bag.
 */
double getOcc(bdd feature, const bdd &family) {
		int levels = number_of_vars-occ_bitsize;
  int call = 0, occ =0, exp =0; 
  bdd occbdd;
  int node =1;
  occbdd = bdd_restrict(family, feature);
  //bdd_printdot(occbdd);
  if (occbdd != bddtrue)
  {
				while (node <= levels)
				{ 
						if (bdd_ithvar(node) != feature )
						{
						occbdd = bdd_restrict(occbdd, bdd_nithvar(node));   
						}

						node = node + 1;

				}

    if (( occbdd != bddtrue) & (occbdd !=bddfalse))
				{ 			
					for (int i=0;i<occ_bitsize; i++ )
					{

															//cout << "occ i= " << i << endl;
															if (bdd_low(occbdd) == bddtrue)
															{

																	occ = occ + 0 * powr(2,exp);

																	break;
															}
															else if (bdd_high(occbdd) == bddtrue)
															{
																	occ = occ + 1 * powr(2,exp);
																	break;
															}
																else if (bdd_high(occbdd) != bddfalse)
															{
																	occ = occ + 1 * powr(2,exp);
																	occbdd = bdd_high(occbdd);
															}
															else if (bdd_low(occbdd) != bddfalse)
															{
																	occ = occ + 0 * powr(2,exp);
																	occbdd = bdd_low(occbdd);
															}
															else
															{
																	break;
																	occ= 0;
															}
															exp = exp + 1;
					//cout << "occ = " << occ << endl;
					}
			}
			//cout << " occ= " << occ << endl;
   return occ;
  }
   return 0;
}

/** Given an element number, the number of occurances and a bag, family,
 * this funciton sets the number of occurances of the element in the family. The function returns a bag(bdd) with the occurance changed.
 */ 
bdd setOcc(int feature,int occ, bdd family){
	int levels = number_of_vars-occ_bitsize;
 bdd node = bdd_ithvar(feature);
	bdd u = bdd_restrict(family,!node);
 //cout << "occ =" << getOcc(node,family) <<  endl;
 bs = bitset<occ_bitsize>(occ); 
	bdd occurance;
	occurance = bddtrue;
 int curr_node;
 for (int j=0; j<bs.size(); j++)
	{
    //cout << "j loop" << bs[j] <<  endl;
	   curr_node = j+levels+1;
    
				if (bs[j] == 1) 
				 { 
				  occurance = (occurance & bdd_ithvar(curr_node)) ;
				 }
				else
				 { 
				  occurance = (occurance & bdd_nithvar(curr_node)) ;
				 }
    
	}
 //cout << "occurance " << endl;
 //bdd_printdot(occurance);
	u =  u | (occurance & node);
	return u;	
}


/** This functions unions two bags, b1 and b2, and returns the resultant bag containing the elements in the original two bags. 
 */
//bdd bagUnion (bdd b1, bdd b2){
//
//    bdd u;
//    int levels = number_of_vars-occ_bitsize;
//    for (int i =1; i<=levels;i++)
//    {  //cout << " i= " << i << " -- sum of occurances is = " <<  getOcc(bdd_ithvar(i), b1) + getOcc(bdd_ithvar(i), b2) << endl;
//      	bitset<3> bs(getOcc(bdd_ithvar(i), b1) + getOcc(bdd_ithvar(i), b2)); 
//      	temp1 = bdd_ithvar(i);
//      	for (int k=1 ; k<=levels; k++)
//       { //cout << " k= " << i  << endl;
//      		if (k != i)
//      		{ 
//      		  temp1 = temp1 & bdd_nithvar(k);
//      		}
//							}
//      	
//							for (int j=0; j<bs.size(); j++)
//							{
//									//cout << " j= " << i  << endl;
//									if (bs[j] == 1)
//										{ 
//											temp1 = temp1 & bdd_ithvar(levels+j+1); // 	temp1 = temp1 & bdd_ithvar(j+3);
//										}
//									else
//										{ 
//											temp1 = temp1  & bdd_nithvar(levels+j+1); // temp1 = temp1  & bdd_nithvar(j+3);
//										}
//							}
//						u = u | temp1;
//			 }
//	//bdd_printdot(u);
//	return u;
//}



bdd bagUnion (const bdd &b1,const bdd &b2){
    bdd u;
    int levels = number_of_vars-occ_bitsize;
    int sum_occ;
				//bdd_printdot(b1);
				//cout << "\n" << endl;
				//bdd_printdot(b2);
    u = setUnion(b1,b2);
    for (int i =1; i<=levels;i++)
    {
       //cout <<"i= " << i <<endl;
       //cout <<"occ i in b1 " << getOcc(bdd_ithvar(i), b1) <<endl;
       //cout <<"occ i in b2 " << getOcc(bdd_ithvar(i), b2) <<endl;
       sum_occ = getOcc(bdd_ithvar(i), b1) + getOcc(bdd_ithvar(i), b2);
       u = setOcc(i,sum_occ,u);
			 }
	//bdd_printdot(bdd_simplify(u,bddtrue));
	return u;
}

/** This function returns the bag intersection between two given bags b1,b2. The the result will contain the elements that the given original bags contain.
 */ 
bdd bagIntersection (const bdd &b1, const bdd &b2){
	bdd u;
	int levels = number_of_vars-occ_bitsize;

	for (int i =1; i<=levels;i++)
	{
		if (getOcc(bdd_ithvar(i), b1) <= getOcc(bdd_ithvar(i), b2)) 
		{
						bs = bitset<occ_bitsize>(getOcc(bdd_ithvar(i), b1));
		}
		else
		{
			bs = bitset<occ_bitsize>(getOcc(bdd_ithvar(i), b2));	
		}

		temp1 = bdd_ithvar(i);
		
		for (int k=1 ; k<=levels; k++)
  {
			
			if (k != i)
			{ 
					temp1 = temp1 & bdd_nithvar(k);
			}
		}
								
			for (int j=0; j<bs.size(); j++)
			{
					
					if (bs[j] == 1)
						{ 
							temp1 = temp1 & bdd_ithvar(j+number_of_vars+1); // j+3
						}
					else
						{ 
							temp1 = temp1  & bdd_nithvar(j+number_of_vars+1); // j+3
						}
			}
			u = u | temp1;

	}	
	return u;
}



/** This function returns true if a given element, feature, is a member of a bag, family. It returns false otherwise.
 */ 
bool bagIsMember(bdd feature, const bdd &family){ 
   bdd temp;
   temp = (feature & family);
   if (getOcc(feature,family) > 0)
      return true;
   else
      return false;	


}

/** This function returns the bag complement of a given bag, family. There is theoritically no bag complement, but we can se our own. One example is to define a maximum number of occurances.
 */
bdd bagComplement(bdd &family){
	
	return bdd_not(family);
	
}

/** This function inserts an element, feature, into a bag, family.
 */ 
void bagInsert(bdd feature, bdd &family){
	
	family = feature | family;
}

/** This function removes an element, feature, from a bag, family. 
 */ 
void bagRemove(bdd feature, bdd &family){
	
	family = bdd_exist(family, feature);
}

/** This function checks if a bag, family, is empty; if so, it returns true and false otherwise.
 */
bool bagIsEmpty(bdd &family){
	
	if (family == bdd_true())
	   return true;
	else
	   return false;
}

/** This functions displays the content of a given bag, family. The format is like in this example: {(1,3),(2,5)}. This is element 1 of 3 occurances and element 2 of 5 occurances. 
 */
void bagEnumerate(const bdd &family){
     bool first = true;
     cout << "  {\n "; 
     for (int i = 1; i <= levels; i++) // 0 for one bdd to show
     {  
									//cout << "for bdd of i= " << i << " get occ = " << getOcc(bdd_ithvar(i), family) << endl;
							  if ( (getOcc(bdd_ithvar(i), family)>0) )
										  {
																if (first)
																			{
																					//cout << i ;
 												        cout << " (" << names[i] << "," << getOcc(bdd_ithvar(i), family) << ")" ;
																					first = false;
																			}
																else
																			{
																				//cout << ", " << i ;
 												       cout << " , (" << names[i] << "," << getOcc(bdd_ithvar(i), family) << ")" ;
																			}
            }

     }
	 cout << "\n  }" << endl;
}

bool bagEquals(const bdd &s1, const bdd &s2)
{
    if (bagIsMember(s1, s2) == bagIsMember(s2,s1) ) return true;
}

bool bagNumOccLess(const bdd &s1, const bdd &s2 )
{  bool occFlag;
			for (int i = 1; i <= levels; i++)
			{  occFlag = true;
						if (getOcc(bdd_ithvar(i), s1) > getOcc(bdd_ithvar(i),s2))
									{
												occFlag = false;
												break;
									}

			}
   return occFlag;

}

bool bagEqualOcc(const bdd &s1, const bdd &s2 )
{  bool occFlag;
			for (int i = 1; i <= levels; i++)
			{  occFlag = true;
						if (getOcc(bdd_ithvar(i), s1) != getOcc(bdd_ithvar(i),s2))
									{
												occFlag = false;
												break;
									}

			}
   return occFlag;

}
