package edu.unl.consystlab.sudokuSolver;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import java.util.List;
import java.util.Stack;
import java.util.Hashtable;
import java.io.*;
import java.util.Collection;

import com.sun.org.apache.bcel.internal.generic.CPInstruction;



public class constraintProblem
{
	private String suType;
	private String sourceValue;
	private int lowerDiffValue;
	private int upperDiffValue;
	private int levelDiffValue;
	private String origNameValue;
	private String descValue;

        private String problemName;
	private Hashtable problemRelations;
	private Hashtable problemVariables;
	private Hashtable problemBinaryIntensiveConstraints;
	private Hashtable problemNonBinaryIntensiveConstraints;
	//private List problemValues;
	private Hashtable problemDomains;
//	private List currentDomainReductions;
	//A domian reduction listener is just a list that contains domain reductions.
	// They can be registered and unregistered at any time
	private Collection domainReductionListeners;
	int linesPerUnit;
	int columnsPerUnit;
	int totalLines;
	int totalColumns;

	//default constructor
	public constraintProblem() {

		domainReductionListeners =new LinkedList();
		
		//generate a random name
		problemName = generateName();

		//add it to the all-problems list
		Globals.allProblems.add(this);

		problemDomains = new Hashtable();
		problemVariables = new Hashtable();
		problemRelations = new Hashtable();
		problemBinaryIntensiveConstraints = new Hashtable();
		problemNonBinaryIntensiveConstraints = new Hashtable();
		
		linesPerUnit = 0;
		columnsPerUnit = 0;
		totalLines = 0;
		totalColumns = 0;

	} //end default constructor

	//accepts a name
	public constraintProblem(String tempUserName) {

		domainReductionListeners =new LinkedList();
		
		//set the name
		problemName = tempUserName;

		//add it to the all-problems list
		Globals.allProblems.add(this);

		problemDomains = new Hashtable();
		problemVariables = new Hashtable();
		//problemRelations = new Hashtable();
		problemBinaryIntensiveConstraints = new Hashtable();

		linesPerUnit = 0;
		columnsPerUnit = 0;
		totalLines = 0;
		totalColumns = 0;
		
	} //end constructor

	//this method creates a copy of the problem.  
	//It must create a copy of the relations, variables, constraints, 
	//domains, currentdomain reductions, etc
	//It is somewhat fragile.
	public constraintProblem copy()
	{
		constraintProblem myCopy = new constraintProblem(this.problemName, this.linesPerUnit,
				this.columnsPerUnit, this.totalLines, this.totalColumns);
		
		//add the problem domain
		Enumeration tempEnumeration = this.problemDomains.elements();
		
		//since there is always only one domain we get its name here
		problemDomain myNewDomain = null;
		while(tempEnumeration.hasMoreElements())
		{
			myNewDomain = ((problemDomain)tempEnumeration.nextElement()).copy(myCopy);
		}
		
		//add new variables
		//go through the current variables
		Iterator i = this.getAllVariables().iterator();
		while(i.hasNext())
		{
			problemVariable tempVariable = (problemVariable)i.next();
			problemVariable tempNewVariable = new problemVariable(tempVariable.getIndex(), myCopy);
			//set the domains
			tempNewVariable.setDomain(myNewDomain.getName());
			//reassign it if it was assigned
			if(tempVariable.isAssigned())
			{
				tempNewVariable.setAssigned(tempVariable.getAssigned());
			}

			//fix its current domain
			List tempOldDomain = tempVariable.getEntireDomain();
			Iterator j = tempOldDomain.iterator();
			List newCurrentDomain = new LinkedList();
			while (j.hasNext()) {
				String element = (String) j.next();
				String newElement = new String(element);
				newCurrentDomain.add(newElement);
			}
			tempNewVariable.setCurrentDomain(newCurrentDomain);
		}

		//recreate the binary constraints
		for(int cellLineIndex = 1; cellLineIndex <= myCopy.totalLines; cellLineIndex++)
 		{
 	 		for(int cellColumnIndex = 1; cellColumnIndex <= myCopy.totalColumns; cellColumnIndex++)
 	 		{
 	 			//add the line constraints
 	 			for(int colpointer = cellColumnIndex + 1; colpointer <= myCopy.totalColumns; colpointer++ )
 	 			{
 	 				problemConstraint newConstraint = new binaryIntensiveConstraint(
 	 						cellLineIndex + "," + cellColumnIndex + " " + cellLineIndex + "," + colpointer,
 	 						myCopy);
 	 			}
 	 			
 	 			//add the column constraints
 	 			for(int rowPointer = cellLineIndex + 1; rowPointer <= myCopy.totalLines; rowPointer++ )
 	 			{
 	 				problemConstraint newConstraint = new binaryIntensiveConstraint(
 	 						cellLineIndex + "," + cellColumnIndex + " " + rowPointer + "," + cellColumnIndex, 
 	 						myCopy);
 	 			} 			
 	 		}
 		}

 		//add the remaining unit constraints
 		//number of units accross is = to describedProblem.linesPerUnit
 		for(int unitLine = 0; unitLine < myCopy.columnsPerUnit; unitLine++)
 		{
 			for(int unitColumn = 0; unitColumn < myCopy.linesPerUnit; unitColumn++)
 			{
 				//inside of every unit add constraints at every row and every column but the last one
 				// to avoid repeating constraints the rule is you can look up but not back.
 				for(int unitLineIndex = 1; unitLineIndex <= myCopy.linesPerUnit; unitLineIndex++)
 				{
 					//this only goes to less than becase of the rule where you don't look back the last row
 					//will add no constraints
 					for(int unitColumnIndex = 1; unitColumnIndex < myCopy.columnsPerUnit; unitColumnIndex++)
 					{
						//since we don't mind looking up we set the line pointer @ 0
 						for(int linePointerIndex = 1; linePointerIndex <= myCopy.linesPerUnit; linePointerIndex++)
 						{
 							//since we don't look back we set the column pointer @ +1
 							for(int columnPointerIndex = unitColumnIndex + 1; columnPointerIndex <= myCopy.columnsPerUnit; columnPointerIndex++)
 							{
 								//make sure we are not repeating the same line
 								if(linePointerIndex != unitLineIndex)
 								{
	 								//now we add the constraints
	 			 	 				problemConstraint newConstraint = new binaryIntensiveConstraint(
	 			 	 						((unitLine * myCopy.linesPerUnit) + unitLineIndex) + "," + 
	 			 	 						((unitColumn * myCopy.columnsPerUnit) + unitColumnIndex) + " " + 
	 			 	 						((unitLine * myCopy.linesPerUnit) + linePointerIndex) + "," + 
	 			 	 						((unitColumn * myCopy.columnsPerUnit) + columnPointerIndex),
	 			 	 						myCopy);
 								}
 							}
 						}
 					}
 				}
 			}
 		}
 			
		//add the All diff constraints.
		//start with the lines
 		for(int cellLineIndex = 1; cellLineIndex <= myCopy.totalLines; cellLineIndex++)
 		{
 			String variablesAffected = "";
 			
 	 		for(int cellColumnIndex = 1; cellColumnIndex <= myCopy.totalColumns; cellColumnIndex++)
 	 		{

 	 				variablesAffected = variablesAffected + cellLineIndex + "," + cellColumnIndex + " ";
 	 		}	 	 			
 	 			variablesAffected.trim();
 	 			problemConstraint newConstraint = new nonBinaryIntensiveConstraint( variablesAffected,
 	 					myCopy, "L" + cellLineIndex); 	 	 			
 		}
 		
		//do the columns
 		for(int cellColIndex = 1; cellColIndex <= myCopy.totalLines; cellColIndex++)
 		{
 			String variablesAffected = "";
 			
 	 		for(int cellLineIndex = 1; cellLineIndex <= myCopy.totalColumns; cellLineIndex++)
 	 		{

 	 				variablesAffected = variablesAffected + cellLineIndex + "," + cellColIndex + " ";
 	 		}	 	 			
 	 			variablesAffected.trim();
 	 			problemConstraint newConstraint = new nonBinaryIntensiveConstraint( variablesAffected,
 	 					myCopy, "C" + cellColIndex); 	 	 			
 		}
 		
 		//add the unit constraints
 		//number of units accross is = to describedProblem.linesPerUnit
 		for(int unitLine = 0; unitLine < myCopy.columnsPerUnit; unitLine++)
 		{
 			for(int unitColumn = 0; unitColumn < myCopy.linesPerUnit; unitColumn++)
 			{
	 			String variablesAffected = "";

 				for(int unitLineIndex = 1; unitLineIndex <= myCopy.linesPerUnit; unitLineIndex++)
 				{
 					for(int unitColumnIndex = 1; unitColumnIndex <= myCopy.columnsPerUnit; unitColumnIndex++)
 					{
	 	 				variablesAffected = variablesAffected + (unitLine*myCopy.linesPerUnit + unitLineIndex)
	 	 					+ "," + (unitColumn*myCopy.columnsPerUnit + unitColumnIndex) + " ";	
 					}
 				}
	 	 		variablesAffected.trim();
		 	 	problemConstraint newConstraint = new nonBinaryIntensiveConstraint( variablesAffected,
		 	 			myCopy, "U" + (unitLine+1) + "," + (unitColumn+1));
 			}
 		}
		
 		//recreate all of the domainReductionListeners and all of their reductions
 		Collection oldDomainReductionListeners = this.domainReductionListeners;
 		Collection copyDomainReductionListeners = new LinkedList();
 		Iterator k = oldDomainReductionListeners.iterator();
 		while(k.hasNext())
 		{
 			List currentListener = (LinkedList)k.next();
 			List newListener = new LinkedList();
 			Iterator m = currentListener.iterator();
 			while(m.hasNext())
 			{
 				variableIndexAndValueGrouping currentReduction = (variableIndexAndValueGrouping)m.next();
 				String currentValue = currentReduction.getValue();
 				String copyValue = new String(currentValue);
 				problemVariable copyVariable = myCopy.getVariable(currentReduction.getVariableIndex());
 				variableIndexAndValueGrouping copyReduction = new variableIndexAndValueGrouping(copyVariable.getIndex(), copyValue);
 			}
 		}
 		
// 		List currentDomainReductions = new LinkedList(this.getDomainReductions());
// 		Iterator k = currentDomainReductions.iterator();
// 		while(k.hasNext())
// 		{
// 			List variableValuePair = new LinkedList((List)k.next());
// 			problemVariable tempVar = myCopy.getVariable(((problemVariable)variableValuePair.get(0)).getIndex());
// 			myCopy.addDomainReduction(tempVar, new String((String)variableValuePair.get(1)));
// 		}
 		
 		
		return myCopy;
	}
	//a special constructor used by copy to set many things at once
	private constraintProblem(String tempName, int tempLinesPerUnit, int tempColumnsPerUnit,
			int tempTotalLines, int tempTotalColumns)
	{
		domainReductionListeners =new LinkedList();
		
		//set the name
		problemName = tempName;

		//add it to the all-problems list
		Globals.allProblems.add(this);

		problemDomains = new Hashtable();
		problemVariables = new Hashtable();
		problemRelations = new Hashtable();
		problemBinaryIntensiveConstraints = new Hashtable();
		problemNonBinaryIntensiveConstraints = new Hashtable();
		

		linesPerUnit = tempLinesPerUnit;
		columnsPerUnit = tempColumnsPerUnit;
		totalLines = tempTotalLines ;
		totalColumns = tempTotalColumns;
	}
	
	
	//make printing pretty
	public String toString()
	{
		return (problemName);
	}

	public void setName(String name)
	{
		problemName = name;
		return;
	}

    public String getName()
    {
        return problemName;
    }
	
	public void setDimensions(String newLines, String newColumns) 
	{
		linesPerUnit = Integer.parseInt(newLines);
		columnsPerUnit = Integer.parseInt(newColumns);
		totalLines = linesPerUnit*columnsPerUnit;
		totalColumns = totalLines;
	}
	
	//function by Angelo
	public void setDimensionsForXML3(String newLines, String newColumns) 
	{
		totalLines = Integer.parseInt(newLines);
		totalColumns = Integer.parseInt(newColumns);
		linesPerUnit = totalLines/3;
		columnsPerUnit = totalColumns/3;		
	}	

        public void setSuType(String type)
	{
		suType = type;
	}

	public void setSourceValue(String source)
	{
		sourceValue = source;
	}

	public void setLowerDiffValue(int lower)
	{
		lowerDiffValue = lower;
	}

	public void setUpperDiffValue(int upper)
	{
		upperDiffValue = upper;
	}

	public void setLevelDiffValue(int level)
	{
		levelDiffValue = level;
	}

	public void setOrigNameValue(String orig)
	{
		origNameValue = orig;
	}

	public void setDescValue(String desc)
	{
		descValue = desc;
	}

	public String getSuType()
	{
		return suType;
	}

	public String getSourceValue()
	{
		return sourceValue;
	}

	public int getLowerDiffValue()
	{
		return lowerDiffValue;
	}

	public int getUpperDiffValue()
	{
		return upperDiffValue;
	}

	public int getLevelDiffValue()
	{
		return levelDiffValue;
	}

	public String getOrigNameValue()
	{
		return origNameValue;
	}

	public String getDescValue()
	{
		return descValue;
	}

	public int getLinesPerUnit()
	{
		return(linesPerUnit);
	}

	public int getColumnsPerUnit()
	{
		return(columnsPerUnit);
	}
	
	public void addVariable(problemVariable newVariable)
	{
		problemVariables.put(newVariable.getIndex(), newVariable);
		return;
	}


	//variables are named by there index which is in the form "row,col"
	public problemVariable getVariable(String variableName)
	{
		return ((problemVariable)problemVariables.get( variableName ));
	}


	public void addDomain(problemDomain newDomain, String domainName)
	{
//System.out.println( " " + domainName + " added\n");
		problemDomains.put(domainName, newDomain);
	}

	public problemDomain getDomain(String domainName)
	{
		return(	(problemDomain) problemDomains.get( domainName ) );
	}
	
	//assuming there is only one domain (which should always be true for basic sudokus)
	// returns the problemDomain object that corresponds to the domain.
	public problemDomain getDomain()
	{
		if(problemDomains.size() == 1)
		{
			return(	(problemDomain) (new LinkedList(problemDomains.values()).element()) );
		}
		else
		{
			return(null);
		}
	}

//	public void addRelation(constraintRelation newRelation, String relationName)
//	{
//		problemRelations.put(relationName, newRelation);
//		return;
//	}
//
//	public constraintRelation getRelation(String relationName)
//	{
// 		return(	(constraintRelation) problemRelations.get( relationName ) );
//	}


	public void addBinaryIntensiveConstraint(problemConstraint newConstraint, List constraintScopeVariables)
	{
		problemBinaryIntensiveConstraints.put(constraintScopeVariables, newConstraint);
		return;
	}
	public void addNonBinaryIntensiveConstraint(problemConstraint newConstraint, String key)
	{
		((nonBinaryIntensiveConstraint)newConstraint).setKey(key);
		problemNonBinaryIntensiveConstraints.put(key, newConstraint);
		return;
	}

	//the key is charnumber read char number.  Char is either L for line,
	// U for unit, or C for collumn, and number is the line/row starting at one from
	// the top left.  For units the number will be 1,1 or such with the form of row,col
	// starting at the top left.
	public problemConstraint getNonBinaryIntensiveConstraint(String constraintKey)
	{
		return( (problemConstraint) problemNonBinaryIntensiveConstraints.get(constraintKey));
	}
	
	public problemConstraint getConstraint(List constraintKey)
	{
 		return(	(problemConstraint) problemBinaryIntensiveConstraints.get( constraintKey ) );
	}
	
	public Collection getAllConstraints()
	{
 		return(	(problemBinaryIntensiveConstraints.values()) );
	}	
	
	public Collection getAllNonBinaryIntensiveConstraints()
	{
 		return(	(problemNonBinaryIntensiveConstraints.values()) );
	}	
	
	public Collection getAllVariables()
	{
		return( (problemVariables.values()) );
	}
	
	public void addDomainReduction(variableIndexAndValueGrouping newReduction)
	{
		Iterator i = domainReductionListeners.iterator();
		//adds the reduction to all registered listeners.
		while(i.hasNext())
		{
			((List)i.next()).add(newReduction);
		}
	}
	
	public void registerDomainReductionListener(List newDomainReductionListener)
	{
		domainReductionListeners.add(newDomainReductionListener);
	}
	
	public void unregisterDomainReductionList(List oldDomainReductionListener)
	{
		
		domainReductionListeners.remove(oldDomainReductionListener);
	}
	
	//this method only prints nine x nine sudokus without problems
	//TODO: expand to more sizes.
	public void print()
	{
		for(int lineIndex = 1; lineIndex <= this.totalLines; lineIndex++)
		{
			for(int columnIndex = 1; columnIndex <= this.totalColumns; columnIndex++)
			{

				//if the variable is assigned print it's value out otherwise a question mark
				if(this.getVariable(lineIndex+","+columnIndex).isAssigned())
				{
					System.out.print(this.getVariable(lineIndex+","+columnIndex).getAssigned());
				}
				else
				{
					System.out.print("?");
				}

				if((columnIndex % this.columnsPerUnit) ==0 && columnIndex!=this.totalColumns)
				{
					System.out.print("|");
				}
				else if(columnIndex==this.totalColumns)
				{
					System.out.print('\n');
				}
			}
			if((lineIndex % this.linesPerUnit) == 0 && lineIndex!=this.totalLines)
			{
				for(int i=0; i< (this.totalColumns +this.linesPerUnit-1); i++)
					System.out.print("-");
				System.out.print('\n');
			}
		}
	}
	
	
	//args:
	//returns: a random string of characters between 10 and 20 characters long
	private String generateName()
	{
		Random generator = new Random( );
		String tempRandomName = new String();

		//make the name between 10 and 20 characters
		int nameLength = generator.nextInt(11) + 10;

		for(int i=0; i<=nameLength; i++)
		{
			int nextChar = generator.nextInt(26);
			switch(nextChar)
			{
				case 0: tempRandomName  = tempRandomName .concat("a");
					break;
				case 1: tempRandomName  = tempRandomName .concat("b");
					break;
				case 2: tempRandomName  = tempRandomName .concat("c");
					break;
				case 3: tempRandomName  = tempRandomName .concat("d");
					break;
				case 4: tempRandomName  = tempRandomName .concat("e");
					break;
				case 5: tempRandomName  = tempRandomName .concat("f");
					break;
				case 6: tempRandomName  = tempRandomName .concat("g");
					break;
				case 7: tempRandomName  = tempRandomName .concat("h");
					break;
				case 8: tempRandomName  = tempRandomName .concat("i");
					break;
				case 9: tempRandomName  = tempRandomName .concat("j");
					break;
				case 10: tempRandomName  = tempRandomName .concat("k");
					break;
				case 11: tempRandomName  = tempRandomName .concat("l");
					break;
				case 12: tempRandomName  = tempRandomName .concat("m");
					break;
				case 13: tempRandomName  = tempRandomName .concat("n");
					break;
				case 14: tempRandomName  = tempRandomName .concat("o");
					break;
				case 15: tempRandomName  = tempRandomName .concat("p");
					break;
				case 16: tempRandomName  = tempRandomName .concat("q");
					break;
				case 17: tempRandomName  = tempRandomName .concat("r");
					break;
				case 18: tempRandomName  = tempRandomName .concat("s");
					break;
				case 19: tempRandomName  = tempRandomName .concat("t");
					break;
				case 20: tempRandomName  = tempRandomName .concat("u");
					break;
				case 21: tempRandomName  = tempRandomName .concat("v");
					break;
				case 22: tempRandomName  = tempRandomName .concat("w");
					break;
				case 23: tempRandomName  = tempRandomName .concat("x");
					break;
				case 24: tempRandomName  = tempRandomName .concat("y");
					break;
				case 25: tempRandomName  = tempRandomName .concat("z");
					break;
				default: tempRandomName  = tempRandomName .concat("1");
					break;
			}

		}

		return (tempRandomName);
	} //end generateName


}
