package splar.core.fm.randomization;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;

import splar.core.constraints.BooleanVariableInterface;
import splar.core.constraints.PropositionalFormula;
import splar.core.fm.FeatureGroup;
import splar.core.fm.FeatureModel;
import splar.core.fm.FeatureModelException;
import splar.core.fm.FeatureTreeNode;
import splar.core.fm.GroupedFeature;
import splar.core.fm.RootNode;
import splar.core.fm.SolitaireFeature;


public class RandomFeatureTreeModel extends FeatureModel {

	private int numberOfFeatures;   // total number of features in the feature model not counting feature group nodes
	private int childFeaturesOdds;  // max number of children for a given node (opt, mand, and group count as one) 
	private int minChildFeature;    // min number of children for a given node (opt, mand, and group count as one)
	private int solitaireOdds;      // odds for solitaire features
	private int groupOdds;          // odds for feature groups
	private int minGroupCard;       // min number of grouped features in a group
	private int maxGroupCard;       // max number of grouped features in a group
	
	public RandomFeatureTreeModel(int numberOfFeatures, int solitaireOdds, int groupOdds, int minChildFeature, int minGroupCard, int maxGroupCard) {
		super();
		
		// feature tree parameters
		this.numberOfFeatures = numberOfFeatures;
		this.childFeaturesOdds = solitaireOdds + groupOdds;
		this.minChildFeature = minChildFeature;
		this.solitaireOdds = solitaireOdds;
		this.groupOdds = groupOdds;
		this.minGroupCard = minGroupCard;
		this.maxGroupCard = maxGroupCard;
	}

	protected FeatureTreeNode createNodes() throws FeatureModelException {				
		
		int countFeatures = 1;
		Vector<FeatureTreeNode> fmNodes = new Vector<FeatureTreeNode>();
		
		String featureName = "R";
		countFeatures++;
		
		RootNode root = new RootNode( featureName, featureName);
		fmNodes.add(root);				
		FeatureTreeNode parentNode = null;
		
		while ( countFeatures <= numberOfFeatures ) {
			
			parentNode = fmNodes.firstElement();
			fmNodes.removeElement(parentNode);
									
			int numberOfChildNodesToCreate = Math.min(numberOfFeatures-countFeatures+1, (Math.abs(new Random().nextInt())%(childFeaturesOdds-minChildFeature+1))+minChildFeature);
			
			// prevents an early end of the recursion when all nodes happen to have no children 
			if ( numberOfChildNodesToCreate == 0 ) {
				if ( fmNodes.size() == 0 ) {
					numberOfChildNodesToCreate = 1;
				}
			}
			
			if ( numberOfChildNodesToCreate > 0 ) {			
				for( int i = 0 ; i < numberOfChildNodesToCreate && countFeatures <= numberOfFeatures ; i++ ) {
					String childFeatureName = parentNode.getID().substring(1) + (i+1);
					FeatureTreeNode randomNode = createRandomNode(childFeatureName,solitaireOdds, groupOdds, minGroupCard, maxGroupCard);
					parentNode.add(randomNode);
					if ( randomNode instanceof FeatureGroup ) {
						FeatureGroup groupRandomNode = (FeatureGroup)randomNode;
						int countGroupedNodes = groupRandomNode.getChildCount();
						for( int j = 0 ; j < countGroupedNodes ; j++ ) {
							fmNodes.add((FeatureTreeNode)groupRandomNode.getChildAt(j));
						}
						countFeatures += (countGroupedNodes);
					}
					else {
						fmNodes.add(randomNode);
						countFeatures++;
					}							
				}
			}
		}
		
		return root;
	}

	private FeatureTreeNode createRandomNode(String childFeatureName, int solitaireOdds, int groupOdds, int minGroupCard, int maxGroupCard) {

		int solitaireOrGroupRandom = (new Random().nextInt()%childFeaturesOdds)+1;
		
		FeatureTreeNode node = null;
		String featureName = "";
		
		// should create a solitaire feature (optional/mandatory)?
		if ( solitaireOrGroupRandom <= solitaireOdds ) {
			featureName = "S" + childFeatureName;
			// 50% odds for mandatory and optional features
//			int solitaireOptorMandRandom = (new Random().nextInt()%2);
			// should create an optional feature?
//			if ( solitaireOptorMandRandom == 0 ) {
		        node = new SolitaireFeature( true, featureName, featureName);
//			}
//			 should create a mandatory feature?
//			else  {
//		        node = new SolitaireFeature( true, featureName, featureName, TreeNodeRendererFactory.createMandatoryRenderer());
//			}						
		}
		// should create a feature group?
		else {
			// number of grouped features
			int groupSize = (Math.abs(new Random().nextInt())%(maxGroupCard-minGroupCard+1))+minGroupCard;
			// 50% odds for [1..*] and [1] group cardinality
			int groupTypeOdds = (new Random().nextInt()%2);
			// [1..*] or [1] cardinality
			int lower = 1;
			int upper = 1;
			if ( groupTypeOdds == 0 ) {  // [1..*] = [1,-1]
				upper = -1;
			}
			
			String groupName = "_G" + childFeatureName;;
			// create group parent node
			node = new FeatureGroup( groupName, groupName, lower,upper);

			// create grouped feature nodes
			for( int i = 0 ; i < groupSize ; i++ ) {
				featureName = "g" + childFeatureName + (i+1);
		        node.add( new GroupedFeature( featureName, featureName));		        
			}
		}
		
		return node;
	}

	public static Vector<PropositionalFormula> createExtraConstraints(FeatureModel fm, int numVars, int numConstraints, int minVarsLeft, int maxVarsLeft, int minVarsRight, int maxVarsRight) {
		Vector<PropositionalFormula> pfs = new Vector<PropositionalFormula>();
		
		if ( fm != null ) {
			if ( numConstraints > 0 && numVars > 1) {
				ArrayList<BooleanVariableInterface> vars = chooseVariablesForConstraints(fm, numVars);
				
				int varIndex = 0;
				for( int i = 0 ; i < numConstraints ; i++ ) {
					
					String constraint = "";
					int leftVarsToPick = (new Random().nextInt()%(maxVarsLeft-minVarsLeft+1))+minVarsLeft;
					int RightVarsToPick = (new Random().nextInt()%(maxVarsRight-minVarsLeft+1))+minVarsRight;
					
					int leftOp = new Random().nextInt()%2;    // 0 = OR, 1 = AND
					int RightOp = new Random().nextInt()%2;   
					
					String v;
					int form;
					
					constraint += "(";
					for( int j = 0 ; j < leftVarsToPick-1 ; j++ ) {
						v = vars.get(varIndex).getID();
						form = new Random().nextInt()%2;    // 0 = positive, 1 = negative
						if ( form == 0 ) {
							constraint += "" + v; 
						}
						else {
							constraint += "~" + v; 
						}
						if (leftOp == 0) {
							constraint += " OR "; 
						}
						else {
//							constraint += " AND ";
							constraint += " OR ";  // enforce CNF
						}
						varIndex = (varIndex < (numVars-1) ? varIndex+1 : 0);
						if ( varIndex == 0 ) scrumbleVars(vars);
					}
					v = vars.get(varIndex).getID();
					form = new Random().nextInt()%2;    // 0 = positive, 1 = negative
					if ( form == 0 ) {
						constraint += "" + v; 
					}
					else {
						constraint += "~" + v; 
					}
//					constraint += ") IMP (";					
					constraint += ") OR ("; // enforce CNF
					
					varIndex = (varIndex < (numVars-1) ? varIndex+1 : 0);
					if ( varIndex == 0 ) scrumbleVars(vars);
					for( int j = 0 ; j < RightVarsToPick-1 ; j++ ) {
						v = vars.get(varIndex).getID();
						form = new Random().nextInt()%2;    // 0 = positive, 1 = negative
						if ( form == 0 ) {
							constraint += "" + v; 
						}
						else {
							constraint += "~" + v; 
						}
						if (RightOp == 0) {
							constraint += " OR "; 
						}
						else {
//							constraint += " AND ";   // enforce CNF
							constraint += " OR ";
						}
						varIndex = (varIndex < (numVars-1) ? varIndex+1 : 0);
						if ( varIndex == 0 ) scrumbleVars(vars);
					}
					v = vars.get(varIndex).getID();
					form = new Random().nextInt()%2;    // 0 = positive, 1 = negative
					if ( form == 0 ) {
						constraint += "" + v; 
					}
					else {
						constraint += "~" + v; 
					}
					constraint += ")";
					varIndex = (varIndex < (numVars-1) ? varIndex+1 : 0);
					if ( varIndex == 0 ) scrumbleVars(vars);
					try {
						pfs.add(new PropositionalFormula("C"+(i+1),constraint));
					}
					catch(Exception e) {
						e.printStackTrace();
					}
				}
			}
						
		}
		
		return pfs;		
	}
	
	private static void scrumbleVars(ArrayList<BooleanVariableInterface> vars) {
		int numVars = vars.size();
		for( int i = 0 ; i < numVars ; i++ ) {
			int index = Math.abs(new Random().nextInt()%numVars);
			BooleanVariableInterface temp = vars.get(index);
			vars.set(index, vars.get(i));
			vars.set(i, temp);
		}
//		System.out.println("scrambled ==>");
//		for( int i = 0 ; i < numVars ; i++ ) {
//			System.out.print(vars.get(i).getName()+",");
//		}
//		System.out.println("");
	}
	
	private static ArrayList<BooleanVariableInterface> chooseVariablesForConstraints(FeatureModel fm, int numVars) {
		ArrayList<BooleanVariableInterface> vars = new ArrayList<BooleanVariableInterface>(numVars);
		Collection<FeatureTreeNode> nodes = fm.getNodes();
		int numNodes = nodes.size();
		int varsGen = 0;
		
		while( varsGen < numVars ) {
			int random = Math.abs(new Random().nextInt()%numNodes);
			Iterator<FeatureTreeNode> it = nodes.iterator();
			for( int i = 0 ; i < (random-1) ; i++ ) {
				it.next();
			}
			FeatureTreeNode node = it.next();
			if ( !(node instanceof FeatureGroup) && !(node instanceof RootNode) ) {
				if ( !vars.contains(node) ) {
					vars.add(node);
					varsGen++;
				}
			}
		}
//		System.out.println("choosen ==>");
//		for( int i = 0 ; i < vars.size() ; i++ ) {
//			System.out.print(vars.get(i).getName()+",");
//		}
//		System.out.println("");
		return vars;
	}
	
	public void saveNodes() {}
}

