package util.ui;

import java.awt.Point;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFormattedTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;

/**
 * A class for correction of the caret position of a JFormattedTextField.
 * 
 * @author René Mach
 * 
 */
public class CaretPositionCorrector {
    
  private Point mClickLocation;
  private int mCaretPosition, mStartPosition;
  private CaretListener mCaretListener;
  private char[] mJumpCharacters;
  private char mStartIndexChar;
  private boolean mMouseDown;
  
  private CaretPositionCorrector(JFormattedTextField field, char[] jumpCharacters, char startIndexChar, int startPosition) {
    mJumpCharacters = jumpCharacters;
    mStartIndexChar = startIndexChar;
    mStartPosition = startPosition;
    mClickLocation = null;
    mCaretPosition = -1;
    
    if(jumpCharacters != null && jumpCharacters.length > 0) {
      createCaretListener(field);
    
      addKeyListenerToField(field);
    }
    
    addMouseListenerToField(field);
    addFocusListenerToField(field);
  }
  
  /**
   * Creates an instance of the JSpinnerCaretPositionCorrector.
   * 
   * Use <code>null</code> for jumpCharacters if you only want to
   * let the position set to the click position.
   * 
   * @param field The JFormattedTextField to correct the caret position of.
   * @param jumpCharacters The character to jump from if the caret is in front of it.
   * @param startIndexChar The character that is used to find the start position of the caret.
   * 
   */
  public static void createCorrector(JFormattedTextField field, char[] jumpCharacters, char startIndexChar) {
    new CaretPositionCorrector(field, jumpCharacters, startIndexChar, -1);
  }
  
  /**
   * Creates an instance of the JSpinnerCaretPositionCorrector.
   * 
   * Use <code>null</code> for jumpCharacters if you only want to
   * let the position set to the click position.
   * 
   * @param field The JFormattedTextField to correct the caret position of.
   * @param jumpCharacters The character to jump from if the caret is in front of it.
   * @param startPosition The start position for the caret.
   * 
   */
  public static void createCorrector(JFormattedTextField field, char[] jumpCharacters, int startPosition) {
    new CaretPositionCorrector(field, jumpCharacters, '\0', startPosition);
  }
  
  /** Create caret listener for making sure of editing the right value.*/
  private void createCaretListener(final JFormattedTextField field) {
    mCaretListener = new CaretListener() {
      public void caretUpdate(final CaretEvent e) {
        if(mCaretPosition != -1 && field.getSelectedText() == null) {
          mCaretPosition = field.getCaretPosition();
        }
      }
    };
  }
  
  private boolean hasToMoveCaret(String text, int pos) {
    for(char value : mJumpCharacters) {
      if(text.charAt(pos) == value) {
        return true;
      }
    }
    
    return false;
  }
  
  /** Add key listener to the field to handle correct moving throw the text of the field */
  private void addKeyListenerToField(final JFormattedTextField field) {
    field.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        if(KeyEvent.VK_UP == e.getKeyCode() || KeyEvent.VK_DOWN == e.getKeyCode()) {
          mCaretPosition = field.getCaretPosition();
          if(mCaretPosition <= field.getText().length() && mCaretPosition > 0 &&
              field.getSelectedText() == null &&
              (mCaretPosition >= field.getText().length() || hasToMoveCaret(field.getText(), mCaretPosition))) {
            field.setCaretPosition(--mCaretPosition);
          }
        }
      }
    });
  }
  
  /** Add mouse listener to the field to track clicks and get the location of it */
  private void addMouseListenerToField(final JFormattedTextField field) {
    field.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        mMouseDown = true;
        mClickLocation = e.getPoint();
      }
      
      public void mouseReleased(MouseEvent e) {
        mMouseDown = false;
        mCaretPosition = field.getUI().viewToModel(field, e.getPoint());
        
        if(mCaretPosition <= field.getText().length() && mCaretPosition > 0 &&
            field.getSelectedText() == null &&
            (mCaretPosition >= field.getText().length() || hasToMoveCaret(field.getText(), mCaretPosition))) {
          field.setCaretPosition(--mCaretPosition);
        }
      }
    });
  }
  
  /**  Add focus listener to set the location of the caret to the right place */
  private void addFocusListenerToField(final JFormattedTextField field) {
    field.addFocusListener(new FocusListener() {
      public void focusGained(FocusEvent e) {
        if(mCaretPosition == -1) {
          if(mStartIndexChar != '\0') {
            mCaretPosition = field.getText().indexOf(String.valueOf(mStartIndexChar)) + 1;
          } else if(mStartPosition != -1) {
            mCaretPosition = mStartPosition;
          } else {
            mCaretPosition = 0;
          }
        }
        
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            if(mClickLocation != null) {
              mCaretPosition = field.getUI().viewToModel(field, mClickLocation);
            }
            
            mClickLocation = null;
            
            if(mCaretPosition <= field.getText().length() && mCaretPosition > 0 &&
                field.getSelectedText() == null && !mMouseDown &&
                (mCaretPosition >= field.getText().length() || hasToMoveCaret(field.getText(), mCaretPosition))) {
              mCaretPosition--;
            }
            
            field.setCaretPosition(mCaretPosition);
            field.addCaretListener(mCaretListener);
          }
        });
      }

      public void focusLost(FocusEvent e) {
        mCaretPosition = field.getCaretPosition();
        field.removeCaretListener(mCaretListener);
      }
    });
  }
}
