package leigh.elements.jfoenix.type1;

import com.jfoenix.controls.JFXTextField;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import leigh.elements.ElementBase;
import leigh.elements.javafx.ThemeJFX;
import leigh.elements.javafx.type1.ElementJFX;
import leigh.elements.sdk.AppScreenInstance;
import leigh.elements.sdk.actions.ScreenAction;
import leigh.elements.sdk.type1.TextFieldElement;
import leigh.eo.EO;
import leigh.eo.EOListener;
import leigh.eo.EOLoop;
import leigh.eo.EditEventGenerator;
import leigh.mecha.lang.FutureResult;
import leigh.mecha.lang.FutureResultListener;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;
import leigh.mecha.util.Snavig;

import java.io.Serializable;

/**
 * Implementation of the Field element for JavaFX. Note that this implementation only provides the input field
 * itself, not a label. It is meant to be used with another element which will retrieve its label and other
 * information and decorate the field appropriately, for example, by including a label.
 * <p>
 * This class will update the <code>FieldElement.KEY_VALUE</code> attribute in the configuration whenever
 * the end-user changes the value in the field.
 *
 * @author Alex Leigh
 */
public class FieldElementJFoenix extends ElementBase implements ElementJFX, EOListener {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(FieldElementJFoenix.class);
    private static final String ICON_SEQUENCE = "sequence";
    private HBox hbox = new HBox();
    private JFXTextField input = new JFXTextField();
    private boolean eventsArmed = true;
    private boolean localFocus = false;

    // Whether we should ignore the next change event. We do this to avoid a loop.

    protected FieldElementJFoenix(EO cfg, AppScreenInstance screen) {
        super(cfg);

        input.getStyleClass().add("vds-field-text");

        input.setLabelFloat(true);
        input.setPromptText(cfg.getValueString(TextFieldElement.KEY_LABEL));

        boolean isReadOnly = cfg.getValueBoolean(TextFieldElement.KEY_IS_READONLY);
        if (isReadOnly) {
            logger.debug("Disabling edit status.");
            input.setEditable(false);
        } else {
            logger.debug("Field is read/write");
        }

        String tooltip = cfg.getValueString(TextFieldElement.KEY_TOOLTIP);
        if (tooltip != null) {
            Tooltip tt = new Tooltip();
            tt.setText(tooltip);
            input.setTooltip(tt);
        }

        ((EditEventGenerator) cfg.getEditTracker()).addListener(cfg, this);

        // By definition we do not have focus, so we self-update to set the bare value.
        valueUpdated(getCfg(), TextFieldElement.KEY_VALUE, null, cfg.getValue(TextFieldElement.KEY_VALUE));
        setupCss();

        input.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                // For this to work, the model and cfg object must already have been updated.
                String dataValue = cfg.getValueString(TextFieldElement.KEY_VALUE);
                String displayValue = cfg.getValueString(TextFieldElement.KEY_DISPLAY_VALUE);

                if (newValue) {
                    logger.debug("Gained focus. Setting data value");
                    input.setText(dataValue);
                } else {
                    logger.debug("Lost focus. Setting display value. [display: {}] [value: {}] [cfg: {}]",
                            displayValue, dataValue, getCfg());
                    if (displayValue != null) {
                        input.setText(displayValue);
                    }
                }

                setupCss();
            }
        });

        input.textProperty().addListener((observable, oldValue, newValue) -> {
            logger.debug("textProperty() change event fire. [oldValue: {}] [newValue: {}]", oldValue, newValue);
            if (input.isFocused()) {
                cfg.setValue(TextFieldElement.KEY_VALUE, newValue);
            }
        });

        hbox.getChildren().add(input);

        String sequence = cfg.getValueString(TextFieldElement.KEY_SEQUENCE);
        if (sequence != null) {
            Button seqBtn = new Button();
            seqBtn.setTooltip(new Tooltip("Generate Sequence Value"));
            seqBtn.setGraphic(new ImageView(((ThemeJFX) screen.getTheme()).getIcons().getImage(ICON_SEQUENCE)));
            seqBtn.getStyleClass().add("vds-field-button");
            seqBtn.setOnAction(new EventHandler<ActionEvent>() {
                @Override
                public void handle(ActionEvent event) {
                    Integer seq = screen.sequenceNextVal(sequence);
                    if (seq != null) {
                        logger.debug("Setting input to new value: {}", seq.toString());
                        input.setText(seq.toString());
                        // We have to do this because setText() will not, since the field does not have focus
                        cfg.setValue(TextFieldElement.KEY_VALUE, seq.toString());
                    } else {
                        logger.warn("Sequence not found. [key: {}] [screen: {}]", sequence, screen);
                    }
                }
            });
            hbox.getChildren().add(seqBtn);
        }

        EOLoop actions = cfg.getValueLoop(TextFieldElement.KEY_ACTIONS);
        for (EO action : actions) {
            String cmdFunc = action.getValueString(TextFieldElement.KEY_ACTION_FUNC);
            String cmdTarget = action.getValueString(TextFieldElement.KEY_ACTION_FUNC_TARGET);
            String icon = action.getValueString(TextFieldElement.KEY_ACTION_ICON);
            Button funcBtn = new Button();
            funcBtn.getStyleClass().add("vds-field-button");

            if (icon != null) {
                funcBtn.setGraphic(new ImageView(((ThemeJFX) screen.getTheme()).getIcons().getImage(icon)));
            } else {
                logger.warn("Icon configuration missing for text field action.");
                funcBtn.setText("(f)");
            }

            funcBtn.setOnAction(event -> {
                FutureResult<ScreenAction> future = screen.call(cmdTarget, cmdFunc);
                future.addListener(new FutureResultListener() {
                    @Override
                    public void receiveResult(Object result) {
                        screen.processAction((ScreenAction) result);
                    }
                });
            });
            hbox.getChildren().add(funcBtn);
        }
    }

    public void setupCss() {
        String color = Snavig.convertToString(getCfg().getValueString(TextFieldElement.KEY_COLOR));
        String fontWeight = Snavig.convertToString(getCfg().getValueString(TextFieldElement.KEY_FONT_WEIGHT));

        StringBuffer sb = new StringBuffer();

        if (color != null) {
            String css = "-fx-text-inner-color: " + color + ";" + "-jfx-focus-color: "
                    + color + ";" + "-jfx-unfocused-color: " + color + ";";
            sb.append(css);
        } else {
            // TODO Unset the style?
        }

        if (fontWeight != null) {
            String css = "-fx-font-weight: " + fontWeight + ";";
            sb.append(css);
        } else {
            // TODO Unset the weight?
        }

        if (sb.length() > 0) {
            input.setStyle(sb.toString());
        }
    }

    @Override
    public Node getJavaFXNode() {
        return hbox;
    }

    @Override
    /**
     * Called by the EO change controller to indicate that a monitored object has a value which is changing.
     */
    public void valueUpdated(EO eo, String key, Serializable oldValue, Serializable newValue) {
        logger.debug("Inbound value event. [key: {}] [newValue: {}] [obj: {}]", key, newValue, eo);

        if (eventsArmed) {
            Platform.runLater(() -> {
                eventsArmed = false;
                if (input.isFocused()) {
                    // When focused, we always use the bare value.
                    if (key.equals(TextFieldElement.KEY_VALUE)) {
                        if (!newValue.equals(input.getText())) {
                            logger.debug("update: focus value");
                            // If we are focused, the user is editing, so we never change the value.
                            //     input.setText(Snavig.convertToString(newValue));
                        }
                    }
                } else {
                    // FIXME: We are running this for ANY key value
                    String dv = eo.getValueString(TextFieldElement.KEY_DISPLAY_VALUE);
                    if (dv != null) {
                        logger.debug("!focused with dv: {}", dv);
                        input.setText(dv);
                    } else {
                        logger.debug("!focused without dv");
                        input.setText(Snavig.convertToString(newValue));
                    }
                }
                eventsArmed = true;
            });
        }
    }
}
