/*
 * AddressBookTextField.java
 *
 * Copyright 2013 John W Dawson
 *
 * This code is distributed under the terms of the GNU General Public License, version 3
 *
 * This class represents a single line text field on the data entry tabs
 */
 
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import java.util.regex.*;
import java.util.*;
import java.util.logging.*;
import javax.naming.directory.*;
public class AddressBookTextField extends JTextField implements AddressBookField, DocumentListener
{
  static final int DEFAULT_FIELD_WIDTH = 36;
  private static Pattern fieldPattern = Pattern.compile ("%(\\w+)%");
  private static Pattern escapePattern = Pattern.compile ("\\\\+([,\\=\\+<>;\\\\\"])");
  private ConfigurationRecord fieldConfiguration;
  private String id;
  private String ldapAttribute;
  private Boolean required;
  private String defaultValue;
  private String fieldLabel;
  private Map<String, AddressBookField> fieldList;
  private Pattern validationPattern = null;
  private String validationPrompt;
  private Container panel;
  private JTabbedPane tabs;
  private Boolean dnField = false;
  private Boolean terminal = false;
  private Pattern dnFieldPattern;
  private ArrayList<DocumentListener> listeners = new ArrayList<DocumentListener> ();
  private CallButton callButton;
  private ExtensionSelector extensionSelector;
  static Logger log = Logger.getLogger ("LdapAddressBook");

  public AddressBookTextField (ConfigurationRecord fieldConfiguration, ExtensionSelector extensionSelector, MessageLine messageLine, Map<String, AddressBookField> fieldList)
  {
    super (DEFAULT_FIELD_WIDTH);
    this.fieldConfiguration = fieldConfiguration;
    this.extensionSelector = extensionSelector;
    this.id = fieldConfiguration.getElementValue ("id");
    this.ldapAttribute = fieldConfiguration.getElementValue ("ldapattribute");
    this.required = fieldConfiguration.getBooleanValue ("required");
    this.defaultValue = fieldConfiguration.getElementValue ("defaultvalue");
    this.fieldLabel = fieldConfiguration.getElementValue ("label");
    String validation = fieldConfiguration.getElementValue ("validation");
    if (validation != null)
    {
      validationPattern = Pattern.compile (validation);
      validationPrompt = fieldConfiguration.getElementValue ("validationprompt");
    }
    this.fieldList = fieldList;
    
    // Create a call button if this is a phone field
    this.callButton = fieldConfiguration.getBooleanValue ("phonenumberfield") 
      ? new CallButton (this, extensionSelector, messageLine)
      : null;
  }
  
  public void initialise ()
  {
    // Field is initially disabled
    setEnabled (false);
    setDisabledTextColor (Color.BLACK);
    
    this.panel = getParent();
    this.tabs = (JTabbedPane) panel.getParent();
    
    // Check if this field has a default value which is dependent on other fields
    if (defaultValue != null)
    {
      // Find all fields that the default value depends on
      Matcher matcher = fieldPattern.matcher (defaultValue);
      while (matcher.find ())
      {
        AddressBookField referencedField = fieldList.get (matcher.group (1));
        
        // Check the referenced field has been defined in configuration
        if (referencedField == null)
        {
          JOptionPane.showMessageDialog 
            (null, "The field \"" + matcher.group (1) + "\" is not defined in the configuration file",
             "Invalid Configuration Data", JOptionPane.ERROR_MESSAGE);
          System.exit (0);
        }
        
        // Attach this field to the referenced field to handle changes to the referenced field
        referencedField.addDocumentListener (this);
          
      }
    }
  }
  
  public void displayValue (SearchResult entry)
  {
  
    // Disable all document listeners so this change is ignored
    disableListeners ();
    
    // Get value from entry and set field text
    setText (getValue (entry));
    
    // Re-enable the listners
    enableListeners ();
  }  
  
  public void clear ()
  {
    setText ("");
  }  
  
  private void displayDefault ()
  {
    StringBuffer buff = new StringBuffer ();
    
    // Loop for all fields referenced in the default value pattern
    Matcher matcher = fieldPattern.matcher (defaultValue);
    while (matcher.find ())
    {
      // Replace the field reference with the actual value of the field (if any) in this entry
      matcher.appendReplacement (buff, fieldList.get (matcher.group (1)).getText());
    }
    
    // Add any remaining text
    matcher.appendTail (buff);
    
    // Display the text
    setText (buff.toString());
  }
  
  public Boolean validateText ()
  {
    // Check if validation required for this field and field is not empty
    if (validationPattern != null && getText().length() > 0)
    {
      // Check if text conforms to required pattern
      Matcher matcher = validationPattern.matcher (getText());
      if (matcher.matches ())
      {
        // Call button may need to be enabled or disabled
        enableCallButton ();
        
        return true;
      }
      else
      {
        // Return focus to this field
        setFocus ();
        
        // Display message to prompt user to correct entry
        JOptionPane.showMessageDialog 
          (null, "Please enter " + validationPrompt + " in the " + fieldLabel + " field",
           "Invalid Field Entry", JOptionPane.ERROR_MESSAGE);
        
        return false;
      }
    }
    else
    {
      // Call button may need to be enabled or disabled
      enableCallButton ();
      
      // No specific validation so contents must be valid
      return true;
    }
  }
  
  public Boolean requiredEmpty ()
  {
    if (required && getText().length() == 0)
    {
      // Set focus to this field
      setFocus ();
      
      JOptionPane.showMessageDialog 
        (null, "Please enter a value in the " + fieldLabel + " field",
         "Required Field Empty", JOptionPane.ERROR_MESSAGE);
         
      return true;
    }
    else
    {
      return false;
    }
  }

  public void insertUpdate(DocumentEvent event) 
  {
    displayDefault ();
  }
  
  public void removeUpdate(DocumentEvent event) 
  {
    displayDefault ();
  }
  public void changedUpdate(DocumentEvent event) 
  {}
  
  public String getLdapAttribute ()
  {
    return ldapAttribute;
  }
  
  public String getValue (SearchResult entry)
  {
    if (dnField)
    {
      // If this is a component of the distinguished name extract the value from the name
      Matcher matcher = dnFieldPattern.matcher (entry.getName ());
      if (matcher.find ())
      {
        // Need to remove escape characters
        StringBuffer buff = new StringBuffer ();
        matcher = escapePattern.matcher (matcher.group (1));
        while (matcher.find ())
        {
          // Remove any backslashes acting as escape characters. Escaped backslashes can have multiple escape
          // characters preceding, and must be escaped in the replacement string
          matcher.appendReplacement (buff, matcher.group (1).equals("\\")
                                            ? "\\\\"
                                            : matcher.group (1));
          
        }
        matcher.appendTail (buff);
        return buff.toString ();
      }
      else
      {
        return null;
      }
    }
    else
    {
      // Otherwise get attribute value from attribute entries
      try
      {   
        return String.valueOf (entry.getAttributes().get(ldapAttribute).get());
      }
      catch (Exception e)
      {
        return null;
      }
    }
  }
  
  public void setDnField (Boolean terminal)
  {
    dnField = true;
    this.terminal = terminal;
    dnFieldPattern = Pattern.compile (ldapAttribute + "=(.*?($|[^\\\\]?=,))");
  }
  
  public String getLabel ()
  {
    return fieldLabel;
  } 
  
  public void setFocus ()
  { 
    // Return focus to this field (reset tab if necessary)
    tabs.setSelectedComponent (panel);
    requestFocusInWindow ();
  }
  
  public Boolean isDnField ()
  {
    return dnField;
  }
  
  public Boolean isTerminal ()
  {
    return terminal;
  }
  
  public void addDocumentListener (DocumentListener listener)
  {
    // Set a handler for changes to this field
    getDocument().addDocumentListener (listener);
    
    // Add handler to list
    listeners.add (listener);
    
  }
  
  public CallButton getCallButton ()
  {
    return this.callButton;
  }
  
  public void enableCallButton ()
  {
    if (this.callButton != null)
    {
      // Button is only enabled if field is not empty and at least one extension has been defined
      this.callButton.setEnabled (getText().length() > 0 && extensionSelector.getSelectedExtension() != null);
    }
  }
  
  public void disableCallButton ()
  {
    if (this.callButton != null)
    {
      this.callButton.setEnabled (false);
    }
  }
  
  private void disableListeners ()
  {
    // Temporarily disable all handlers listening for changes to this field
    for (DocumentListener listener : listeners)
    {
      getDocument().removeDocumentListener (listener);
    }
  }
  
  private void enableListeners ()
  {
    // Re-enable all handlers listening for changes to this field
    for (DocumentListener listener : listeners)
    {
      getDocument().addDocumentListener (listener);
    }
  }
  
  public byte [] getBinary()
  {
    return null;
  }
  
}
       