package edu.unl.consystlab.sudokuSolver;

import java.awt.Component;
import java.util.Collections;
import java.awt.PopupMenu;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.FocusListener;
import java.awt.event.FocusEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.*;
import java.text.Collator;
import java.util.Collection;
import java.util.LinkedList;
import java.awt.Button;

public class sudokuCell extends Container implements MouseListener, KeyListener
{
	private sudokuBoard parentBoard;
	private problemVariable parentVariable;
	private int prblmPvalsNumLines;
	private int prblmPvalsNumCols;
	
	private int cellWidth;
	private int cellHeight;
	
	private int numErrors;
	
	public Color myBackgroundColor;
        public Color originalColor;
	
	private assignValueButtonHandler myAssignHandler;
	private removeValueButtonHandler myRemoveHandler;
	
	private boolean isHint;
	
	//public sudokuCell(problemVariable newParentVariable, int newCellWidth, int newCellHeight, sudokuBoard newBoard)
        public sudokuCell(problemVariable newParentVariable, int newCellWidth, int newCellHeight, sudokuBoard newBoard,Color color)
	{
		myAssignHandler = new assignValueButtonHandler();
		myRemoveHandler = new removeValueButtonHandler();
		
		numErrors = 0;
		parentBoard = newBoard;
		parentVariable = newParentVariable;
		cellWidth = newCellWidth;
		cellHeight = newCellHeight;
		prblmPvalsNumLines = 3; //need some way to set these dynamically.
		prblmPvalsNumCols = 3;
		this.addMouseListener(this);
                this.addKeyListener(this);
                this.setFocusable(true);
                this.requestFocusInWindow();

		//myBackgroundColor = Color.WHITE;
                myBackgroundColor = color;
                originalColor = color;
	}
	
	public void paint(Graphics g)
	{
		//Image offScreen = createImage(this.getWidth(), this.getHeight());
		Point midPoint = new Point(this.getWidth()/2,this.getHeight()/2);

		String[] printString = new String[prblmPvalsNumLines*prblmPvalsNumCols];
		for(int i=0; i <prblmPvalsNumLines*prblmPvalsNumCols; i++)
		{
				printString[i] = new String("");
		}
		
		Graphics2D g2= (Graphics2D)g;
		//myBackgroundColor = Color.BLACK;
		//g2.setBackground(myBackgroundColor);
		Color tempColor = g2.getColor();
		Rectangle2D backRectangle = new Rectangle2D.Float(0,0,cellWidth,cellHeight);
		g2.setColor(myBackgroundColor);
		g2.fill(backRectangle);
		g2.setColor(tempColor);
		
		//draw the background
		
		
		//Graphics2D g2 = (Graphics2D)offScreen.getGraphics();
		//antialiasing makes it look much better, but does slow it down.
		//g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		//g2.setFont(new Font("courier", Font.BOLD, 12));
		
		Font originalFont = g2.getFont();
		Stroke originalStroke = g2.getStroke();
		Color originalColor = g2.getColor();
		
		g2.setFont(new Font("Monospaced", Font.BOLD, 12));
		//make the line thiner
		//it is assumed to be one pixal.
		g2.setStroke(new BasicStroke(0.01f));
		//draw the top line
		g2.drawLine(0,0,cellWidth,0);
		//draw the bottom line
		g2.drawLine(0,cellHeight-1,cellWidth,cellHeight-1);
		//draw the left side
		g2.drawLine(0,0,0,cellHeight);
		//draw the right side
		g2.drawLine(cellWidth-1,0,cellWidth-1,cellHeight);
		
		//do the error checking first so it is in the background
		if(isError())
		{
                     parentBoard.incrementErrors();
                    
			//hardcode the dimensions for now
			Rectangle2D innerRectangle = new Rectangle2D.Float(7,7,cellWidth-14,cellHeight-14);
			Rectangle2D redRectangle = new Rectangle2D.Float(5,5,cellWidth-10,cellHeight-10);
			
			Color defalt = g2.getColor();
			g2.setColor(Color.RED);
			g2.fill(redRectangle);
			g2.setColor(myBackgroundColor);
			g2.fill(innerRectangle);
			g2.setColor(defalt);
			
		}
		if(isHint())
		{
			//hardcode the dimensions for now
			Rectangle2D innerRectangle = new Rectangle2D.Float(6,6,cellWidth-13,cellHeight-13);
			Rectangle2D redRectangle = new Rectangle2D.Float(4,4,cellWidth-9,cellHeight-9);
			
			Color defalt = g2.getColor();
			g2.setColor(Color.BLUE);
			g2.fill(redRectangle);
			g2.setColor(myBackgroundColor);
			g2.fill(innerRectangle);
			g2.setColor(defalt);
			
		}
		

		FontRenderContext frc = g2.getFontRenderContext();
		//We want preassignes to look different than ones assigned by
		// the user.
		if(parentVariable.isPreassigned())
		{
			g2.setFont(g2.getFont().deriveFont(25f));
			printString[0] = parentVariable.getAssigned();

			//center the text
			Rectangle2D bounds =g2.getFont().getStringBounds(printString[0], frc);
			float width = (float) bounds.getWidth();
			//we modify the height to be 5/8th the normal height because it appears that
			//is what numbers are in reality.  Letters are similar though not neccesarily
			//the same.
			float height = ((((float)bounds.getHeight())*5)/8);
			g2.drawString(printString[0], midPoint.x-(width/2)+1, midPoint.y+(height/2));
			
		}
		else if(parentVariable.isAssigned())
		{
			//System.out.println("test");
			g2.setFont(g2.getFont().deriveFont(25f));
			g2.setColor(Color.BLUE);
			printString[0] = parentVariable.getAssigned();

			//center the text
			Rectangle2D bounds =g2.getFont().getStringBounds(printString[0], frc);
			float width = (float) bounds.getWidth();
			//we modify the height to be 5/8th the normal height because it appears that
			//is what numbers are in reality.  Letters are similar though not neccesarily
			//the same.
			float height = ((((float)bounds.getHeight())*5)/8);
			g2.drawString(printString[0], midPoint.x-(width/2)+1, midPoint.y+(height/2));
			//System.out.println("width = " + width + " | height = " + height);
			
			//g2.fillRect(0, (int)(midPoint.y-((height*5)/16)), 3, (int)((height*5)/8)) ;
		}
		else
		{
			if(parentBoard.showDomains())
			{
	
				LinkedList currentDomain = new LinkedList(parentVariable.getEntireDomain());
				g2.setFont(g2.getFont().deriveFont(11f));
				
				//the print strings are assumed to already be ""
	
				//get all the values we need to print into print strings and ready to print.
				LinkedList initialDomain = new LinkedList(parentVariable.getInitialDomain());
				for(int printLine = 1; printLine <= prblmPvalsNumLines; printLine++)
				{
					for(int printCol = 1; printCol <= prblmPvalsNumCols; printCol++)
					{
						//there are cases where there are 8 values in all so there may be unbalanced
						//lines like 3 3 2 in which case we shouldn't look for a 9th value or will get
						//an error.
						if( (printCol-1 + (prblmPvalsNumCols) * (printLine - 1)) < initialDomain.size() &&
								currentDomain.contains(initialDomain.get(printCol-1 + (prblmPvalsNumCols) * (printLine - 1))) )
						{
							printString[((printLine-1)*prblmPvalsNumLines)+(printCol-1)] = 
								(String)initialDomain.get( printCol-1 + (prblmPvalsNumCols) * (printLine - 1) );
								
						}
						else
						{
							printString[((printLine-1)*prblmPvalsNumLines)+(printCol-1)] = " ";
						}
						
					}
				}
				
				//Point midPoint = new Point(this.getWidth()/2,this.getHeight()/prblmPvalsNumLines);
				//Point midPoint = new Point(this.getWidth()/2,this.getHeight()-2);
	
				//do we need to control the font?
				//to get an even look across platforms
				//((printLine-1)*prblmPvalsNumLines)+printCol
				
				//figure out where to print the digits
				Rectangle2D bounds =g2.getFont().getStringBounds("1", frc);
				//we modify the height to be 5/8th the normal height because it appears that
				//is what numbers are in reality.  Letters are similar though not neccesarily
				//the same.
				bounds.setRect(0,0,bounds.getWidth(),((bounds.getHeight()*5)/8));
				double distanceBetweenLines = (( cellHeight - (prblmPvalsNumLines * bounds.getHeight()) )/ (prblmPvalsNumLines + 1));
				double distanceBetweenCols = (( cellWidth - (prblmPvalsNumCols * bounds.getWidth()) )/ (prblmPvalsNumCols + 1));
				
				//System.out.println( "DBC=" + distanceBetweenCols + "  DBL=" + distanceBetweenLines);
				
	//			int[] xDistance = new int[prblmPvalsNumCols*prblmPvalsNumLines];
	//			int[] yDistance = new int[prblmPvalsNumCols*prblmPvalsNumLines];
	//
	//			for(int i=0; i< prblmPvalsNumLines; i++)
	//			{
	//				for(int j=0; j< prblmPvalsNumCols; j++)
	//				{
	//					if(i<prblmPvalsNumCols)
	//					{
	//						yDistance[i]=(int)(distanceBetweenLines+bounds.getY());
	//					}					
	//				}
	//			}
				
				for(int i=0; i <prblmPvalsNumLines*prblmPvalsNumCols; i++)
				{
					
					g2.setColor(parentBoard.getDomainValueColor(
							parentVariable.getLineIndex(),
							parentVariable.getColumnIndex(), printString[i]) );
							
					
	//				//this is where I experimented with drawing arrows.
	//				//change this to a constant, but check to see if it is a two value.
	//				if(g2.getColor() == Color.PINK)
	//				{
	//					int[] xVals = new int[3];
	//					int[] yVals = new int[3];
	//					
	//					xVals[0] =(int)(  (i%prblmPvalsNumCols * bounds.getWidth()) + (i%prblmPvalsNumCols +1) * distanceBetweenCols - (distanceBetweenCols/3) );
	//					yVals[0] =(int)( (Math.floor(i/prblmPvalsNumCols)+1) * (distanceBetweenLines + bounds.getHeight()) - (bounds.getHeight()/2) );
	//
	//					xVals[1] =(int)(  (i%prblmPvalsNumCols * bounds.getWidth()) + (i%prblmPvalsNumCols +1) * distanceBetweenCols + (bounds.getWidth()/6) );
	//					yVals[1] =(int)( (Math.floor(i/prblmPvalsNumCols)+1) * (distanceBetweenLines + bounds.getHeight()) - ((bounds.getHeight()*3)/4) );	
	//					
	//					xVals[2] =(int)(  (i%prblmPvalsNumCols * bounds.getWidth()) + (i%prblmPvalsNumCols +1) * distanceBetweenCols + (bounds.getWidth()/6) );
	//					yVals[2] =(int)( (Math.floor(i/prblmPvalsNumCols)+1) * (distanceBetweenLines + bounds.getHeight()) - (bounds.getHeight()/4) );	
	//					
	//					g2.setColor(Color.GRAY);
	//					g2.fillPolygon(xVals, yVals, 3);
	//					g2.setColor(Color.MAGENTA);
	//				}
					
					g2.drawString(printString[i], 
							(int)(  (i%prblmPvalsNumCols * bounds.getWidth()) + (i%prblmPvalsNumCols +1) * distanceBetweenCols  ),
							(int)( (Math.floor(i/prblmPvalsNumCols)+1) * (distanceBetweenLines + bounds.getHeight()) ) -1); //the -1 is just to correct for rounding?
				}
			}
			
			//this is if there are no values should probably be in a giant if else
			if( parentVariable.getCurrentDomainSize() == 0)
			{
//				g2.setColor(Color.RED);
//				g2.fillRect(0,0, this.cellWidth, this.cellHeight);
				//hardcode the dimensions for now
				Rectangle2D innerRectangle = new Rectangle2D.Float(7,7,cellWidth-14,cellHeight-14);
				Rectangle2D redRectangle = new Rectangle2D.Float(5,5,cellWidth-10,cellHeight-10);
				
				Color defalt = g2.getColor();
				g2.setColor(Color.RED);
				g2.fill(redRectangle);
				g2.setColor(g2.getBackground());
				g2.fill(innerRectangle);
				g2.setColor(defalt);
			}
	
		}
		//g.drawImage(offScreen, 0, 0, this);
		g2.setFont(originalFont);
		g2.setStroke(originalStroke);
		g2.setColor(originalColor);

	}
	
//	public void displayAssignMenu() {
//	
//		PopupMenu pumModel = new PopupMenu();
//		MenuItem binaryItem = new MenuItem("binary");
//		binaryItem.setActionCommand("binary");
//		pumModel.add(binaryItem);
//		//pumModel.insert("hi",2);
//		pumModel.setEnabled(true);
//		this.add(pumModel);
//		pumModel.addActionListener(this);
//		pumModel.setActionCommand("assignMenu");
//		pumModel.show(this,0,0);
//		
//	}

	public void depress()
	{
		myBackgroundColor = Color.GRAY;
		this.repaint();
	}
	
	public void undepress()
	{
                myBackgroundColor = originalColor;
		//myBackgroundColor = Color.WHITE;
		this.repaint();
	}
	
	public void setBackgroundColor(Color newColor)
	{
		myBackgroundColor = newColor;
	}
	
	public void addError()
	{
		numErrors++;
		return;
	}
	public void reduceError()
	{
		numErrors--;
		return;
	}


        public void keyReleased(KeyEvent e){
            
        }
        
        public void keyTyped(KeyEvent e){
            
        }

        public void keyPressed(KeyEvent e){
            if(parentVariable.isPreassigned() || parentVariable.isAssigned()){
                //forward check on the variable if double clicked
                if(e.getKeyCode() == KeyEvent.VK_SPACE){
                    parentBoard.preformBinaryForwardCheckOnVariable(parentVariable);
                }
            }
            else{
                if(e.getKeyCode() == KeyEvent.VK_NUMPAD1){
                    parentBoard.preformReduction(parentVariable, "1");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD2){
                    parentBoard.preformReduction(parentVariable, "2");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD3){
                    parentBoard.preformReduction(parentVariable, "3");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD4){
                    parentBoard.preformReduction(parentVariable, "4");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD5){
                    parentBoard.preformReduction(parentVariable, "5");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD6){
                    parentBoard.preformReduction(parentVariable, "6");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD7){
                    parentBoard.preformReduction(parentVariable, "7");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD8){
                    parentBoard.preformReduction(parentVariable, "8");
                }
                else if(e.getKeyCode() == KeyEvent.VK_NUMPAD9){
                    parentBoard.preformReduction(parentVariable, "9");
                }
                else if(e.getKeyCode() == KeyEvent.VK_1){
                    parentBoard.makeAssignment(parentVariable, "1");
                }
                else if(e.getKeyCode() == KeyEvent.VK_2){
                    parentBoard.makeAssignment(parentVariable, "2");
                }
                else if(e.getKeyCode() == KeyEvent.VK_3){
                    parentBoard.makeAssignment(parentVariable, "3");
                }
                else if(e.getKeyCode() == KeyEvent.VK_4){
                    parentBoard.makeAssignment(parentVariable, "4");
                }
                else if(e.getKeyCode() == KeyEvent.VK_5){
                    parentBoard.makeAssignment(parentVariable, "5");
                }
                else if(e.getKeyCode() == KeyEvent.VK_6){
                    parentBoard.makeAssignment(parentVariable, "6");
                }
                else if(e.getKeyCode() == KeyEvent.VK_7){
                    parentBoard.makeAssignment(parentVariable, "7");
                }
                else if(e.getKeyCode() == KeyEvent.VK_8){
                    parentBoard.makeAssignment(parentVariable, "8");
                }
                else if(e.getKeyCode() == KeyEvent.VK_9){
                    parentBoard.makeAssignment(parentVariable, "9");
                }

                if(e.getKeyCode() == KeyEvent.VK_0){
                    parentBoard.preform_undo();
                }

                parentBoard.doAutoPropogation(parentVariable);
                parentBoard.repaint();
            }
        }

        private void displayInfo(KeyEvent e, String keyStatus){

            //You should only rely on the key char if the event
            //is a key typed event.
            int id = e.getID();
            String keyString;
            if (id == KeyEvent.KEY_TYPED) {
                char c = e.getKeyChar();
                keyString = "key character = '" + c + "'";
            } else {
                int keyCode = e.getKeyCode();
                keyString = "key code = " + keyCode
                    + " ("
                    + KeyEvent.getKeyText(keyCode)
                    + ")";
            }

            int modifiersEx = e.getModifiersEx();
            String modString = "extended modifiers = " + modifiersEx;
            String tmpString = KeyEvent.getModifiersExText(modifiersEx);
            if (tmpString.length() > 0) {
                modString += " (" + tmpString + ")";
            } else {
                modString += " (no extended modifiers)";
            }

            String actionString = "action key? ";
            if (e.isActionKey()) {
                actionString += "YES";
            } else {
                actionString += "NO";
            }

            String locationString = "key location: ";
            int location = e.getKeyLocation();
            if (location == KeyEvent.KEY_LOCATION_STANDARD) {
                locationString += "standard";
            } else if (location == KeyEvent.KEY_LOCATION_LEFT) {
                locationString += "left";
            } else if (location == KeyEvent.KEY_LOCATION_RIGHT) {
                locationString += "right";
            } else if (location == KeyEvent.KEY_LOCATION_NUMPAD) {
                locationString += "numpad";
            } else { // (location == KeyEvent.KEY_LOCATION_UNKNOWN)
                locationString += "unknown";
            }

        }

	public class assignValueButtonHandler implements ActionListener//, KeyListener
	{
		public void actionPerformed(ActionEvent e) 
		{

			LinkedList currentDomain = (LinkedList)parentVariable.getEntireDomain();

			//this is where variables are assigned after they are clicked on in the menu
			for(int i=0; i < currentDomain.size(); i++)
			{
                                System.out.println(e.getActionCommand());
				if(e.getActionCommand().equals(currentDomain.get(i).toString()))
				{
					
					//parentVariable.setAssigned(currentDomain.get(i).toString());
					parentBoard.makeAssignment(parentVariable, currentDomain.get(i).toString());
					//this.repaint();
					
	//				//this is where we run the arcConsistency to eliminate values from the domains.
	//				forwardCheckingSearch mySearch = new forwardCheckingSearch(parentBoard.parentProblem);
	//				mySearch.arcConsistency();
	//				//mySearch.solve();
					parentBoard.doAutoPropogation(parentVariable);
					
					//this is where we recolor the variables.
					//parentBoard.runDomainColoring();
					//and repaint the board.
					parentBoard.repaint();
				}
				else
				{
				}
			}
		}
	}
	
	public class removeValueButtonHandler implements ActionListener
	{
		public void actionPerformed(ActionEvent e) 
		{
			parentBoard.preformReduction(parentVariable, e.getActionCommand());
		}
	}

        public void mouseEntered(MouseEvent e){
            addKeyListener(this);
            setFocusable(true);
            requestFocusInWindow();
        }

	public void mouseClicked(MouseEvent e) 
	{
		
		if(parentVariable.isPreassigned() || parentVariable.isAssigned())
		{
			//forward check on the variable if double clicked
			if(e.getClickCount() == 2)
			{
				parentBoard.preformBinaryForwardCheckOnVariable(parentVariable);
			}
		}
		else
		{
			if(e.getButton() == MouseEvent.BUTTON3)
			{
				PopupMenu pumModel = new PopupMenu();
				pumModel.addActionListener(myRemoveHandler);
				LinkedList currentDomain = new LinkedList(parentVariable.getEntireDomain());
				MenuItem[] assignmentChoices = new MenuItem[currentDomain.size()];
				Collections.sort(currentDomain);
				
				for(int i =0; i < currentDomain.size(); i++)
				{
					//the action command is set to the label by default.
					assignmentChoices[i] = new MenuItem(currentDomain.get(i).toString());
					pumModel.add(assignmentChoices[i]);
				}
				pumModel.setEnabled(true);
				this.add(pumModel);
				pumModel.show(this,0,0);
			}
			else if(e.getButton() == MouseEvent.BUTTON1)
			{
				PopupMenu pumModel = new PopupMenu();
				pumModel.addActionListener(myAssignHandler);
				LinkedList currentDomain = new LinkedList(parentVariable.getEntireDomain());
				MenuItem[] assignmentChoices = new MenuItem[currentDomain.size()];
				Collections.sort(currentDomain);
				
				for(int i =0; i < currentDomain.size(); i++)
				{
					//the action command is set to the label by default.
					assignmentChoices[i] = new MenuItem(currentDomain.get(i).toString());
					pumModel.add(assignmentChoices[i]);
				}
				pumModel.setEnabled(true);
				this.add(pumModel);
				pumModel.show(this,0,0);
			}
                        
		}
	}
	public void mousePressed(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	
	private boolean isError()
	{
		if(numErrors > 0)
		{
			return true;
		}
		return false;
	}

	public void setHint(boolean isHint) {
		this.isHint = isHint;
	}

	private boolean isHint() {
		return isHint;
	}
}