/*
 * Created on 02.04.2005
 */
package net.sourceforge.ganttproject.gui.options;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ComboBoxModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SpringLayout;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import net.sourceforge.ganttproject.gui.IPropertySheetBuilder;
import net.sourceforge.ganttproject.gui.options.model.BooleanOption;
import net.sourceforge.ganttproject.gui.options.model.ChangeValueDispatcher;
import net.sourceforge.ganttproject.gui.options.model.ChangeValueEvent;
import net.sourceforge.ganttproject.gui.options.model.ChangeValueListener;
import net.sourceforge.ganttproject.gui.options.model.ColorOption;
import net.sourceforge.ganttproject.gui.options.model.DateOption;
import net.sourceforge.ganttproject.gui.options.model.EnumerationOption;
import net.sourceforge.ganttproject.gui.options.model.GPOption;
import net.sourceforge.ganttproject.gui.options.model.GPOptionGroup;
import net.sourceforge.ganttproject.gui.options.model.IntegerOption;
import net.sourceforge.ganttproject.gui.options.model.StringOption;
import net.sourceforge.ganttproject.gui.options.model.ValidationException;
import net.sourceforge.ganttproject.gui.options.model.WritableStateHint;
import net.sourceforge.ganttproject.gui.taskproperties.IPropertySheetComponent;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.time.gregorian.FramerImpl;

import org.jdesktop.swingx.JXDatePicker;
import org.jdesktop.swingx.calendar.DatePickerFormatter;

/**
 * @author bard
 */
public class OptionsPageBuilder implements IPropertySheetBuilder {
    protected static final Color INVALID_FIELD_COLOR = Color.RED.brighter();
    I18N myi18n = new I18N();
    private Component myParentComponent;
    
    public OptionsPageBuilder() {
        this(null);
    }
    
    public OptionsPageBuilder(Component parentComponent) {
        myParentComponent = parentComponent;
    }
    
    public void setI18N(I18N i18n) {
        myi18n = i18n;
    }
    
    public I18N getI18N() {
        return myi18n;
    }
    
    public void setOptionKeyPrefix(String optionKeyPrefix) {
        myi18n.setOptionKeyPrefix(optionKeyPrefix);
    }
    public JComponent buildPage(GPOptionGroup[] optionGroups, String pageID) {
        JPanel result = new JPanel(new BorderLayout());
        result.setBorder(new EmptyBorder(0, 5, 0, 5));
        TopPanel topPanel = new TopPanel(myi18n.getPageTitle(pageID), myi18n
                .getPageDescription(pageID));
        topPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        result.add(topPanel, BorderLayout.NORTH);
        JComponent planePage = buildPlanePage(optionGroups);
        result.add(planePage, BorderLayout.CENTER);
        return result;
    }

    public JComponent buildPlanePage(GPOptionGroup optionGroup) {
        return buildPlanePage(new GPOptionGroup[] {optionGroup});
    }
    
    public JComponent buildPlanePage(GPOptionGroup[] optionGroups) {
        final JComponent optionsPanel = new JPanel(new SpringLayout());
        for (int i = 0; i < optionGroups.length; i++) {
            optionsPanel.add(createGroupComponent(optionGroups[i]));
        }
        SpringUtilities.makeCompactGrid(optionsPanel, optionGroups.length, 1,
                0, 0, 5, 5);
        JPanel resultPanel = new JPanel(new BorderLayout());
        resultPanel.add(optionsPanel, BorderLayout.NORTH);
        resultPanel.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                optionsPanel.getComponent(0).requestFocus();
            }
            
        });
        return resultPanel;
    }

    public JComponent createScrollableList(BooleanOption[] options, int visibleCount) {
        JCheckBoxList list = new JCheckBoxList(options);
        list.setVisibleRowCount(visibleCount);
        
        JScrollPane result = new JScrollPane(list);
        return result;
    }
    
    public JComponent createGroupComponent(GPOptionGroup group) {
        JPanel optionsPanel = new JPanel(new SpringLayout());
        if (group.isTitled()) {
            Border lineBorder = BorderFactory.createMatteBorder(1,0,0,0,Color.BLACK);
            optionsPanel.setBorder(BorderFactory.createTitledBorder(lineBorder,myi18n
                    .getOptionGroupLabel(group)));
        }
        GPOption[] options = group.getOptions();
        for (int i = 0; i < options.length; i++) {
            final GPOption nextOption = options[i];
            final Component nextComponent = createOptionComponent(nextOption);
            if (needsLabel(nextOption)) {
                /*
                Component nextLabel =createOptionLabel(options[i]);
                optionsPanel.add(nextLabel);
                */
                JPanel labelPanel = new JPanel(new BorderLayout());
                labelPanel.add(createOptionLabel(options[i]), BorderLayout.WEST);
                
                final WritableStateHint writableStateHint = (WritableStateHint) nextOption.getUIHint(WritableStateHint.class);
                if (writableStateHint!=null) {
                    final JLabel iconLabel = new JLabel();
                    iconLabel.addMouseListener(new MouseAdapter() {
                        boolean isWritable = writableStateHint.isWritable();
                        final Icon myLockedIcon = new ImageIcon(getClass().getResource("/icons/status/locked.png"));
                        final Icon myUnlockedIcon = new ImageIcon(getClass().getResource("/icons/status/unlocked.png")); 
                        {
                            iconLabel.setIcon(getIcon());
                            nextOption.addPropertyChangeListener(new PropertyChangeListener() {
                                public void propertyChange(PropertyChangeEvent evt) {
                                    if (WritableStateHint.PROPERTY_IS_WRITABLE.equals(evt.getPropertyName())) {
                                        isWritable = writableStateHint.isWritable();
                                        iconLabel.setIcon(getIcon());
                                    }
                                }
                            });
                        }
                        private Icon getIcon() {
                            return isWritable ? myUnlockedIcon : myLockedIcon; 
                        }
                        public void mouseClicked(MouseEvent e) {
                           if (writableStateHint.trySetWritable(!isWritable).isOK()) {
                               writableStateHint.setWritable(!isWritable);
                           }
                        }                        
                    });
                    labelPanel.add(iconLabel, BorderLayout.EAST);
                }
                optionsPanel.add(labelPanel);
                
                optionsPanel.add(nextComponent);                
            }
            else {
                optionsPanel.add(nextComponent);
                optionsPanel.add(new JPanel());
            }
            if (i==0) {
                optionsPanel.addFocusListener(new FocusAdapter() {
                    public void focusGained(FocusEvent e) {
                        super.focusGained(e);
                        nextComponent.requestFocus();
                    }
                    
                });
            }
        }
        if (options.length > 0) {
            SpringUtilities.makeCompactGrid(optionsPanel, options.length, 2, 0,
                    0, 3, 3);
        }
        
        JPanel result = new JPanel(new BorderLayout());
        result.add(optionsPanel, BorderLayout.NORTH);
        return result;
    }

    private boolean needsLabel(GPOption nextOption) {
        //return false==nextOption instanceof BooleanOption;
        return true;
    }

    public Component createStandaloneOptionPanel(GPOption option) {
        JPanel optionPanel = new JPanel(new BorderLayout());
        Component  optionComponent = createOptionComponent(option); 
        if (needsLabel(option)) {
            optionPanel.add(createOptionLabel(option), BorderLayout.WEST);
            optionPanel.add(optionComponent, BorderLayout.CENTER);
        }
        else {
            optionPanel.add(optionComponent, BorderLayout.WEST);
        }
        JPanel result = new JPanel(new BorderLayout());
        result.add(optionPanel, BorderLayout.NORTH);
        return result;
    }
    
    private Component createOptionLabel(GPOption option) {
        JLabel nextLabel = new JLabel(myi18n.getOptionLabel(option));
        nextLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 3));
        return nextLabel;
    }
    private Component createOptionComponent(GPOption option) {
        final Component result;
        if (option instanceof EnumerationOption) {
            result = createEnumerationComponent((EnumerationOption) option);
        } else if (option instanceof BooleanOption) {
            result = createBooleanComponent((BooleanOption) option);
        }
        else if (option instanceof ColorOption) {
            result = createColorComponent((ColorOption)option);
        }
        else if (option instanceof DateOption) {
            result = createDateComponent((DateOption)option);
        }
        else if (option instanceof GPOptionGroup) {
            result = createButtonComponent((GPOptionGroup)option);
        }
        else if (option instanceof StringOption) {
            result = createStringComponent((StringOption)option);
        }
        else if (option instanceof IntegerOption) {
            result = createIntegerComponent((IntegerOption)option);
        }
        else {
            result = new JLabel("Unknown option class=" + option.getClass());
        }
        if (!option.isWritable()) {
            result.setEnabled(false);
        }
        option.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (GPOption.PROPERTY_IS_WRITABLE.equals(evt.getPropertyName())) {
                    result.setEnabled((Boolean)evt.getNewValue());
                }
            }
        });
        return result;
    }

    private Color getValidFieldColor() {
        return UIManager.getColor("TextField.background");
    }
    private Component createStringComponent(final StringOption option) {
        final JTextField result = new JTextField(option.getValue());
        final DocumentListener listener = new DocumentListener() {
            private void saveValue() {
                try {
                    option.setValue(result.getText());
                    result.setBackground(getValidFieldColor());
                }
                catch(ValidationException ex) {
                    result.setBackground(INVALID_FIELD_COLOR);
                }        		
            }
            public void insertUpdate(DocumentEvent e) {
                saveValue();
            }

            public void removeUpdate(DocumentEvent e) {
                saveValue();
            }

            public void changedUpdate(DocumentEvent e) {
                saveValue();
            }
        };
        result.getDocument().addDocumentListener(listener);
        option.addChangeValueListener(new ChangeValueListener() {
            public void changeValue(ChangeValueEvent event) {
                result.getDocument().removeDocumentListener(listener);
                if (!result.getText().equals(event.getNewValue())) {
                    result.setText(option.getValue());
                }
                result.getDocument().addDocumentListener(listener);
            }
        });
        return result;
    }

    /**
     * Create JTextField component in options that allows user to input only integer values.
     * @param option
     * @return
     */
    private Component createIntegerComponent(final IntegerOption option) {
        final JTextField result = new JTextField(String.valueOf(option.getValue()));
        final DocumentListener listener = new DocumentListener() {
            private void saveValue() {
                try {
                    int value = Integer.parseInt(result.getText());
                    option.editValue(value);
                    result.setBackground(getValidFieldColor());
                }
                /* If value in text filed is not integer change field color */
                catch (NumberFormatException ex) {
                    result.setBackground(INVALID_FIELD_COLOR);
                }
                catch(ValidationException ex) {
                    result.setBackground(INVALID_FIELD_COLOR);
                } 
            }
            public void insertUpdate(DocumentEvent e) {
                saveValue();
            }

            public void removeUpdate(DocumentEvent e) {
                saveValue();
            }

            public void changedUpdate(DocumentEvent e) {
                saveValue();
            }
        };
        result.getDocument().addDocumentListener(listener);
        option.addChangeValueListener(new ChangeValueListener() {
            public void changeValue(ChangeValueEvent event) {
                result.getDocument().removeDocumentListener(listener);
                if (!result.getText().equals(event.getNewValue())) {
                    result.setText(String.valueOf(option.getValue()));
                }
                result.getDocument().addDocumentListener(listener);
            }
        });
        return result;
    }

    private Component createButtonComponent(GPOptionGroup optionGroup) {
        Action action = new AbstractAction(myi18n.getAdvancedActionTitle()) {
            public void actionPerformed(ActionEvent e) {
                System.err
                        .println("[OptionsPageBuilder] createButtonComponent: ");
            }
            
        };
        JButton result = new JButton(action);
        return result;
    }
    private Component createBooleanComponent(final BooleanOption option) {
        final JCheckBox result = new JCheckBox(new BooleanOptionAction(option));
        result.setText("");
        result.setHorizontalAlignment(JCheckBox.LEFT);
        result.setHorizontalTextPosition(SwingConstants.TRAILING);
        result.setSelected(option.isChecked());
        ComponentOrientation componentOrientation = GanttLanguage.getInstance().getComponentOrientation();
        result.setComponentOrientation(componentOrientation);
        option.addChangeValueListener(new ChangeValueListener() {
            public void changeValue(ChangeValueEvent event) {
                result.setSelected(option.isChecked());
            }
        });
        return result;
    }

    private JComboBox createEnumerationComponent(final EnumerationOption option) {
        final EnumerationOptionComboBoxModel model = new EnumerationOptionComboBoxModel(option);
        final JComboBox result = new JComboBox(model);
        option.addChangeValueListener(new ChangeValueListener() {
            public void changeValue(ChangeValueEvent event) {
                model.onValueChange();
                result.setSelectedItem(model.getSelectedItem());
            }
        });
        result.setEditable(false);
        return result;
    }

    private Component createColorComponent(final ColorOption option) {
        final JButton colorButton = new JButton();
        Action action = new AbstractAction(myi18n.getColorButtonText(option)) {
            public void actionPerformed(ActionEvent e) {
                ActionListener onOkPressing = new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        Color color = ourColorChooser.getColor();
                        colorButton.setBackground(color);
                        option.setValue(color);
                    }
                };
                ActionListener onCancelPressing = new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        // nothing to do for "Cancel"
                    }
                };
                JDialog dialog = JColorChooser.createDialog(myParentComponent,
                        myi18n.getColorChooserTitle(option), true,
                        ourColorChooser, onOkPressing, onCancelPressing);
                ourColorChooser.setColor(colorButton.getBackground());
                dialog.setVisible(true);
            };
        };
        colorButton.setAction(action);
        colorButton.setBackground(option.getValue());
        return colorButton;
    }

    public Component createDateComponent(final DateOption option) {
        return createDateComponent(option, true);
    }
    
    public Component createDateComponent(final DateOption option, final boolean addPicker) {
        class Editor implements PropertyChangeListener, ChangeValueListener {
            private JFormattedTextField myTimeField;
            private JXDatePicker myDatePicker;
            private FramerImpl myDayFramer = new FramerImpl(Calendar.DAY_OF_MONTH);
            private boolean myEventsOff;
            private Box myBox;
            Editor(DateOption option) {
                myBox = Box.createHorizontalBox();
                DateFormat dateFormat = option.getDateFormat();
                myDatePicker = new JXDatePicker();
                myDatePicker.setDate(option.getValue());
                if (dateFormat!=null) {
                    myDatePicker.setFormats(new DateFormat[] {dateFormat});
                    myDatePicker.getEditor().addPropertyChangeListener("value", this);
                    myBox.add(addPicker ? myDatePicker : myDatePicker.getEditor());
                }
                
                if (option.getTimeFormat()!=null) {
                    myTimeField = new JFormattedTextField(option.getTimeFormat());
                    myTimeField.setValue(option.getValue());
                    myTimeField.addPropertyChangeListener("value", this);
                    option.addChangeValueListener(this);
                    myBox.add(Box.createHorizontalStrut(3));
                    myBox.add(myTimeField);
                }
            }

            public void propertyChange(PropertyChangeEvent arg0) {
                try {
                    if (myEventsOff) {
                        return;
                    }
                    Calendar c = (Calendar) Calendar.getInstance().clone();
                    c.setTime(myDatePicker.getDate());
                    if (myTimeField!=null) {
                        Date time = (Date) myTimeField.getFormatter().stringToValue(myTimeField.getText());
                        c.add(Calendar.HOUR, time.getHours());
                        c.add(Calendar.MINUTE, time.getMinutes());
                    }                    
                    option.setValue(c.getTime());
                    myDatePicker.getEditor().setBackground(getValidFieldColor());
                    if (myTimeField!=null) {
                        myTimeField.setBackground(getValidFieldColor());
                    }
                }
                catch(ValidationException ex) {
                    myDatePicker.getEditor().setBackground(INVALID_FIELD_COLOR);
                    if (myTimeField!=null) {
                        myTimeField.setBackground(INVALID_FIELD_COLOR);
                    }
                } catch (ParseException e) {
                    myDatePicker.getEditor().setBackground(INVALID_FIELD_COLOR);
                    if (myTimeField!=null) {
                        myTimeField.setBackground(INVALID_FIELD_COLOR);
                    }
                }
            }

            public void changeValue(final ChangeValueEvent event) {
                assert event.getNewValue() instanceof Date;
                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        Date value = (Date)event.getNewValue();
                        Date dateValue = myDayFramer.adjustLeft(value);

                        myEventsOff = true;
                        try {
                            if (!myDatePicker.getDate().equals(dateValue)) {
                                myDatePicker.setDate(dateValue);
                            }
                            if (myTimeField!=null && !myTimeField.getFormatter().valueToString(value).equals(myTimeField.getText())) {
                                myTimeField.setValue(value);
                            }
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                        finally {
                            myEventsOff = false;
                        }
                    }
                });
            }

            public Component getComponent() {
                return myBox;
            }
        }
        Editor editor = new Editor(option);
        return editor.getComponent();
    }
    
    
    private static JColorChooser ourColorChooser = new JColorChooser();
    
    static {
        ImageIcon calendarImage = new ImageIcon(OptionsPageBuilder.class.getResource(
        "/icons/calendar_16.gif"));        
        Icon nextMonth = new ImageIcon(OptionsPageBuilder.class
                .getResource("/icons/nextmonth.gif"));
        Icon prevMonth = new ImageIcon(OptionsPageBuilder.class
                .getResource("/icons/prevmonth.gif"));
        UIManager.put("JXDatePicker.arrowDown.image", calendarImage);
        UIManager.put("JXMonthView.monthUp.image", prevMonth);
        UIManager.put("JXMonthView.monthDown.image", nextMonth);
        UIManager.put("JXMonthView.monthCurrent.image", calendarImage);        
    }

    public JComponent buildPropertySheets(IPropertySheetComponent[] components) {
        throw new UnsupportedOperationException();
    }
}