package lu.tudor.santec.gecamed.core.gui.widgets;

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;

import lu.tudor.santec.gecamed.core.utils.SSNChecker;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class SSNDocument extends LimittedDocument 
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	private static final long	serialVersionUID		= 1L;
	
	// the indexes of the Luxembourgish SSN
	private static final int	FIRST_SPACE_INDEX		= 4;
	private static final int	SECOND_SPACE_INDEX		= 7;
	private static final int	THIRD_SPACE_INDEX		= 10;
	private static final int	FOURTH_SPACE_INDEX		= 14;
//	private static final int	SSN_END_INDEX			= 14;
	private static final int	MAX_VALID_DIGITS		= 13;
	
	private static final char	ZERO					= '0';
	private static final String	SPACE					= " ";
	
	
	/* ======================================== */
	// 		MEMBERS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(SSNDocument.class.getName());
	
	
	private Caret		ssnCaret;
	
	private boolean 	editValid		= false;
	
	private boolean 	firstSpaceSet	= false;
	
	private boolean 	secondSpaceSet	= false;
	
	private boolean 	thirdSpaceSet	= false;
	
	private boolean		fourthSpaceSet	= false;
	
	private StringBuilder	docText		= new StringBuilder();
	
	
	
	/* ======================================== */
	// 		CONSTRUCTORS
	/* ======================================== */
	
	public SSNDocument (Caret ssnCaret)
	{
		super(SSNField.DIGIT_LIMIT);
		this.ssnCaret	= ssnCaret;
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	@Override
	public void replace(int offset, int length, String text, AttributeSet attrs)
	{
		Mark mark	= new Mark(offset, offset + length, offset + text.length());
		
		try 
		{
			// copy the document text to the StringBuilder, to work on this instead
			docText.replace(0, docText.length(), getText());
			
			// replace or append a letter instead of inserting one
			if (mark.start < getLength() && mark.length() == 0)
				mark.end++;
			
			if (mark.start == 0 && mark.length() >= getLength())
			{
				// the whole text is being replaced => reset
				firstSpaceSet	= false;
				secondSpaceSet	= false;
				thirdSpaceSet	= false;
				fourthSpaceSet	= false;
				docText.replace(mark.start, mark.end, text);
				validate(mark.dot);

				return;
			}
			
			// key is typed into a valid text
			if (isEditValid() && mark.length() <= 1)
			{
				// replace the next digit instead of the auto space
				// and add an auto space, if it doesn't exist, yet
				if (mark.start == FIRST_SPACE_INDEX)
				{
					if (!firstSpaceSet)
					{
						docText.insert(mark.start, SPACE);
						firstSpaceSet = true;
					}
					mark.increaseAllGreaterThan(FIRST_SPACE_INDEX - 1);
				}
				else if (mark.start == SECOND_SPACE_INDEX)
				{
					if (!secondSpaceSet)
					{
						docText.insert(mark.start, SPACE);
						secondSpaceSet = true;
					}
					mark.increaseAllGreaterThan(SECOND_SPACE_INDEX - 1);
				}
				else if (mark.start == THIRD_SPACE_INDEX)
				{
					if (!thirdSpaceSet)
					{
						docText.insert(mark.start, SPACE);
						thirdSpaceSet = true;
					}
					mark.increaseAllGreaterThan(THIRD_SPACE_INDEX - 1);
				}
				else if (mark.start == FOURTH_SPACE_INDEX)
				{
					if (!fourthSpaceSet)
					{
						docText.insert(mark.start, SPACE);
						fourthSpaceSet = true;
					}
					mark.increaseAllGreaterThan(FOURTH_SPACE_INDEX - 1);
				}
				
				// key typed and the cursor would be placed before an auto space 
				// place it behind the space instead
				if (		(offset == FIRST_SPACE_INDEX  - 1 && firstSpaceSet)
						||	(offset == SECOND_SPACE_INDEX - 1 && secondSpaceSet)
						||	(offset == THIRD_SPACE_INDEX  - 1 && thirdSpaceSet)
						||	(offset == FOURTH_SPACE_INDEX - 1 && fourthSpaceSet))
					mark.dot++;
			}
			
			removeAutoSpaces(mark);
			docText.replace(mark.start, mark.end, text);
			validate(mark.dot);
		}
		catch (Exception e) 
		{
			logger.log(Level.ERROR, e.getMessage(), e);
		}
	}
	
	
	@Override
	public void remove(int offs, int len)
	{
		boolean	backSpaceKey	= ssnCaret.getDot() == offs + 1;
		boolean deleteKey		= ssnCaret.getDot() == offs;
		Mark	mark			= new Mark(offs, offs + len, ssnCaret.getDot());
		
		
		try
		{
			docText.replace(0, docText.length(), getText());
			
			// check and manipulate the dot and offset
			if (isEditValid() &&	mark.length()  == 1 &&	backSpaceKey
					&&	(mark.start == FIRST_SPACE_INDEX	&& firstSpaceSet)
					||	(mark.start == SECOND_SPACE_INDEX	&& secondSpaceSet)
					||	(mark.start == THIRD_SPACE_INDEX	&& thirdSpaceSet)
					||	(mark.start == FOURTH_SPACE_INDEX	&& fourthSpaceSet))
			{
				mark.start--;
				mark.dot--;
			}
			
			if (backSpaceKey)
				mark.dot--;
			if (deleteKey)
				mark.dot++;
			
			
			removeAutoSpaces(mark);
			if (mark.end == docText.length() && !deleteKey)
				// start deleting from back or everything
				docText.replace(mark.start, mark.end, "");
			else
			{
				docText.replace(mark.start, mark.end, getZeros(mark.length()));
				if (!isEditValid())
					docText.replace(mark.start, mark.end, "");
			}
			
//			super.remove(mark.start, mark.length());
			validate(mark.dot);
		}
		catch (Exception e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
		}
	}
	
	
	public boolean isValidMatricule ()
	{
		boolean	valid	= false;
		String	matricule;
		
		if (editValid)
		{
			matricule	= getText().replace(SPACE, "");
			valid		= SSNChecker.isValidSSN(matricule);
		}
		
		return valid;
	}
	
	
	public String getValue ()
	{
		return getText().replace(SPACE, "");
	}
	
	
	public void setValue (Object value)
	{
		docText.replace(0, docText.length(), value != null ? value.toString() : "");
		validate(0);
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	private void validate (int dot)
	{
		String			orgText			= getText();
		
		
		editValid	= isEditValid();
		
		if (editValid)
		{
			if (docText.length() > FIRST_SPACE_INDEX)
			{
				firstSpaceSet	= true;
				if (docText.charAt(FIRST_SPACE_INDEX) != ' ')
				{
					docText.insert(FIRST_SPACE_INDEX, SPACE);
					
					if (dot >= FIRST_SPACE_INDEX)
						dot++;
				}
			}
			if (docText.length() > SECOND_SPACE_INDEX)
			{
				secondSpaceSet	= true;
				if (docText.charAt(SECOND_SPACE_INDEX) != ' ')
				{
					docText.insert(SECOND_SPACE_INDEX, SPACE);
					
					if (dot >= SECOND_SPACE_INDEX)
						dot++;
				}
			}
			if (docText.length() > THIRD_SPACE_INDEX)
			{
				thirdSpaceSet	= true;
				if (docText.charAt(THIRD_SPACE_INDEX) != ' ')
				{
					docText.insert(THIRD_SPACE_INDEX, SPACE);
					
					if (dot >= THIRD_SPACE_INDEX)
						dot++;
				}
			}
			if (docText.length() > FOURTH_SPACE_INDEX)
			{
				fourthSpaceSet	= true;
				if (docText.charAt(FOURTH_SPACE_INDEX) != ' ')
				{
					docText.insert(FOURTH_SPACE_INDEX, SPACE);
					
					if (dot >= FOURTH_SPACE_INDEX)
						dot++;
				}
			}
		}
		
		try
		{
			if (!docText.toString().equals(orgText))
			{
				super.remove(0, getLength());
				super.insertString(0, docText.toString(), null);
			}
			ssnCaret.setDot(dot);
		}
		catch (BadLocationException e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
		}
	}
	
	
	/**
	 * Checks, whether the current text of the document is valid.
	 * Means, if there are only digits (except the automatic spaces
	 * and if the number of digits is equal or lower than 11.
	 * 
	 * @return <code>true</code> if the current text is valid
	 */
	private boolean isEditValid ()
	{
		boolean	valid	= false;
		String	modText	= docText.toString().replace(SPACE, "");
		
		// check if the number of digits is equal or lower than 11
		if (modText.length() <= MAX_VALID_DIGITS)
		{
			try 
			{
				// check if there are only digits
				Long.parseLong(modText);
				valid	= true;
			}
			catch (NumberFormatException e) {}
		}
		
		return valid;
	}
	
	
	private String getText ()
	{
		try
		{
			return super.getText(0, getLength());
		}
		catch (BadLocationException e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
			return null;
		}
	}
	
	
	private String getZeros (int length)
	{
		StringBuilder zeros = new StringBuilder(length);
		
		for (int i = 0; i < length; i++)
			zeros.append(ZERO);
		
		return zeros.toString();
	}
	
	
	private void removeAutoSpaces (Mark mark)
	{
		try
		{
			if (fourthSpaceSet && getLength() >= FOURTH_SPACE_INDEX)
			{
//				remove(FOURTH_SPACE_INDEX, 1);
				docText.replace(FOURTH_SPACE_INDEX, FOURTH_SPACE_INDEX + 1, "");
				fourthSpaceSet	= false;
				mark.decreaseAllGreaterThan(FOURTH_SPACE_INDEX);
			}
			if (thirdSpaceSet && getLength() >= THIRD_SPACE_INDEX)
			{
//				remove(THIRD_SPACE_INDEX, 1);
				docText.replace(THIRD_SPACE_INDEX, THIRD_SPACE_INDEX + 1, "");
				thirdSpaceSet	= false;
				mark.decreaseAllGreaterThan(THIRD_SPACE_INDEX);
			}
			if (secondSpaceSet && getLength() >= SECOND_SPACE_INDEX)
			{
//				remove(SECOND_SPACE_INDEX, 1);
				docText.replace(SECOND_SPACE_INDEX, SECOND_SPACE_INDEX + 1, "");
				secondSpaceSet	= false;
				mark.decreaseAllGreaterThan(SECOND_SPACE_INDEX);
			}
			if (firstSpaceSet && getLength() >= FIRST_SPACE_INDEX)
			{
//				remove(FIRST_SPACE_INDEX, 1);
				docText.replace(FIRST_SPACE_INDEX, FIRST_SPACE_INDEX + 1, "");
				firstSpaceSet	= false;
				mark.decreaseAllGreaterThan(FIRST_SPACE_INDEX);
			}
		}
		catch (Exception e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
		}
	}
	
	
	
	/* ======================================== */
	// 		CLASS: MARK
	/* ======================================== */
	
	private class Mark
	{
		int start;
		int end;
		int dot;
		
		public Mark (int start, int end, int dot)
		{
			this.start	= start;
			this.end	= end;
			this.dot	= dot;
		}
		
		
		public int length ()
		{
			return end - start;
		}
		
		
		public void increaseAllGreaterThan (int value)
		{
			if (start	> value)
				start++;
			if (end		> value)
				end++;
			if (dot		> value)
				dot++;
		}
		
		
		public void decreaseAllGreaterThan (int value)
		{
			if (start	> value)
				start--;
			if (end		> value)
				end--;
			if (dot		> value)
				dot--;
		}
		
	}
}
