/*
 * #%L
 * GwtMaterial
 * %%
 * Copyright (C) 2015 - 2017 GwtMaterialDesign
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package gwt.material.design.client.base;

import com.google.gwt.dom.client.Element;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.editor.client.HasEditorErrors;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.UIObject;
import gwt.material.design.client.base.error.ErrorHandler;
import gwt.material.design.client.base.error.ErrorHandlerType;
import gwt.material.design.client.base.error.HasErrorHandler;
import gwt.material.design.client.base.mixin.*;
import gwt.material.design.client.base.validator.BlankValidator;
import gwt.material.design.client.base.validator.HasValidators;
import gwt.material.design.client.base.validator.ValidationChangedEvent;
import gwt.material.design.client.base.validator.Validator;
import gwt.material.design.client.constants.Position;
import gwt.material.design.client.constants.StatusDisplayType;
import gwt.material.design.client.ui.MaterialLabel;

import java.util.List;

//TODO: HasRawValue
public abstract class AbstractValueWidget<V> extends MaterialWidget implements HasValue<V>, LeafValueEditor<V>,
        HasEditorErrors<V>, HasErrorHandler, HasStatusText, HasValidators<V>, HasRequiredField, HasClearOnKeyUp {

    private boolean allowBlank = true;
    private boolean autoValidate;
    private BlankValidator<V> blankValidator;
    private ValidatorMixin<AbstractValueWidget<V>, V> validatorMixin;
    private StatusTextMixin<AbstractValueWidget, ?> statusTextMixin;
    private ErrorHandlerMixin<V> errorHandlerMixin;
    private RequiredFieldMixin<AbstractValueWidget, UIObject> requiredFieldMixin;
    private ClearOnKeyUpMixin<AbstractValueWidget, MaterialLabel> clearOnKeyUpMixin;
    private HandlerRegistration attachHandler, blurHandler;

    public AbstractValueWidget(Element element) {
        super(element);
    }

    public AbstractValueWidget(Element element, String... initialClass) {
        super(element, initialClass);
    }

    @Override
    public void setValue(V value) {
        setValue(value, false);
    }

    @Override
    public void setValue(V value, boolean fireEvents) {
        if (fireEvents) {
            ValueChangeEvent.fire(this, value);
        }
    }

    public void setValue(V value, boolean fireEvents, boolean reload) {
        setValue(value, fireEvents);

        if (this instanceof HasReload) {
            if (reload) {
                ((HasReload)this).reload();
            }
        }
    }

    //TODO:
    //setSanitizer();

    @Override
    public void setErrorText(String errorText) {
        getStatusTextMixin().setErrorText(errorText);
    }

    @Override
    public void setSuccessText(String successText) {
        getStatusTextMixin().setSuccessText(successText);
    }

    @Override
    public void setHelperText(String helperText) {
        getStatusTextMixin().setHelperText(helperText);
    }

    @Override
    public void clearErrorText() {
        getStatusTextMixin().clearErrorText();
    }

    @Override
    public void clearHelperText() {
        getStatusTextMixin().clearHelperText();
    }

    @Override
    public void clearSuccessText() {
        getStatusTextMixin().clearSuccessText();
    }

    @Override
    public void clearStatusText() {
        getStatusTextMixin().clearStatusText();
    }

    @Override
    public boolean isErrorTextVisible() {
        return getStatusTextMixin().isErrorTextVisible();
    }

    @Override
    public boolean isHelperTextVisible() {
        return getStatusTextMixin().isHelperTextVisible();
    }

    @Override
    public boolean isSuccessTextVisible() {
        return getStatusTextMixin().isSuccessTextVisible();
    }

    @Override
    public void setStatusDisplayType(StatusDisplayType displayType) {
        getStatusTextMixin().setStatusDisplayType(displayType);
    }

    @Override
    public StatusDisplayType getStatusDisplayType() {
        return getStatusTextMixin().getStatusDisplayType();
    }

    @Override
    public void updateStatusDisplay(StatusDisplayMixin.StatusType statusType) {
        getStatusTextMixin().updateStatusDisplay(statusType);
    }

    @Override
    public void showErrors(List<EditorError> errors) {
        getErrorHandlerMixin().showErrors(errors);
    }

    @Override
    public ErrorHandler getErrorHandler() {
        return getErrorHandlerMixin().getErrorHandler();
    }

    @Override
    public void setErrorHandler(ErrorHandler errorHandler) {
        getErrorHandlerMixin().setErrorHandler(errorHandler);
    }

    @Override
    public ErrorHandlerType getErrorHandlerType() {
        return getErrorHandlerMixin().getErrorHandlerType();
    }

    @Override
    public void setErrorHandlerType(ErrorHandlerType errorHandlerType) {
        getErrorHandlerMixin().setErrorHandlerType(errorHandlerType);
    }

    @Override
    public void addValidator(Validator<V> validator) {
        getValidatorMixin().addValidator(validator);
    }

    @Override
    public boolean isValidateOnBlur() {
        return getValidatorMixin().isValidateOnBlur();
    }

    @Override
    public boolean removeValidator(Validator<V> validator) {
        return getValidatorMixin().removeValidator(validator);
    }

    @Override
    public void reset() {
        getValidatorMixin().reset();
    }

    @Override
    public void setValidateOnBlur(boolean validateOnBlur) {
        getValidatorMixin().setValidateOnBlur(validateOnBlur);
        setupBlurValidation();
    }

    @Override
    public void setValidators(@SuppressWarnings("unchecked") Validator<V>... validators) {
        getValidatorMixin().setValidators(validators);
    }

    @Override
    public boolean validate() {
        return getValidatorMixin().validate();
    }

    @Override
    public boolean validate(boolean show) {
        return getValidatorMixin().validate(show);
    }

    @Override
    public void setRequired(boolean required) {
        getRequiredFieldMixin().setRequired(required);
    }

    @Override
    public boolean isRequired() {
        return getRequiredFieldMixin().isRequired();
    }

    /**
     * Enable or disable the default blank validator.
     */
    public void setAllowBlank(boolean allowBlank) {
        this.allowBlank = allowBlank;

        // Setup the allow blank validation
        if (!allowBlank) {
            if (blankValidator == null) {
                blankValidator = createBlankValidator();
            }
            setupBlurValidation();
            addValidator(blankValidator);
        } else {
            removeValidator(blankValidator);
        }

    }

    /**
     * Will this widget allow blank values upon validation.
     */
    public boolean isAllowBlank() {
        return allowBlank;
    }

    /**
     * Hook for custom blank validators.
     *
     * @return the blank validatorMixin
     */
    protected BlankValidator<V> createBlankValidator() {
        return new BlankValidator<>();
    }

    protected void setupBlurValidation() {
        final AbstractValueWidget inputWidget = getValidatorMixin().getInputWidget();
        if (!inputWidget.isAttached()) {
            if(attachHandler == null) {
                attachHandler = inputWidget.addAttachHandler(event -> {
                    if (blurHandler == null) {
                        blurHandler = inputWidget.addBlurHandler(blurEvent -> {
                            validate(isValidateOnBlur());
                        });
                    }
                });
            }
        } else {
            if(blurHandler == null) {
                blurHandler = inputWidget.addBlurHandler(event -> validate(isValidateOnBlur()));
            }
        }
    }

    @Deprecated
    public boolean isAutoValidate() {
        return autoValidate;
    }

    /**
     * This method was deprecated and replaced by {@link #setValidateOnBlur(boolean)}
     */
    @Deprecated
    public void setAutoValidate(boolean autoValidate) {
        this.autoValidate = autoValidate;
        if (autoValidate) {
            if (isAttached()) {
                autoValidate();
            } else {
                registerHandler(addAttachHandler(event -> autoValidate()));
            }
        }
    }

    @Deprecated
    protected void autoValidate() {
        registerHandler(addBlurHandler(event -> validate()));
    }

    @Override
    public void setClearOnKeyUp(boolean value) {
        getClearOnKeyUpMixin().setClearOnKeyUp(value);
    }

    @Override
    public boolean isClearOnKeyUp() {
        return getClearOnKeyUpMixin().isClearOnKeyUp();
    }

    @Override
    public void setClearKeyCode(int keyCode) {
        getClearOnKeyUpMixin().setClearKeyCode(keyCode);
    }

    @Override
    public int getClearKeyCode() {
        return getClearOnKeyUpMixin().getClearKeyCode();
    }

    @Override
    public void setStatusDisplayPosition(Position position) {
        getStatusTextMixin().setStatusDisplayPosition(position);
    }

    @Override
    public HandlerRegistration addValidationChangedHandler(ValidationChangedEvent.ValidationChangedHandler handler) {
        return getValidatorMixin().addValidationChangedHandler(handler);
    }

    @Override
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<V> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }

    protected ValidatorMixin<AbstractValueWidget<V>, V> getValidatorMixin() {
        if (validatorMixin == null) {
            validatorMixin = new ValidatorMixin<>(this, getErrorHandler());
        }
        return validatorMixin;
    }

    protected StatusTextMixin<AbstractValueWidget, ?> getStatusTextMixin() {
        if (statusTextMixin == null) {
            statusTextMixin = new StatusTextMixin<>(this);
        }
        return statusTextMixin;
    }

    protected ErrorHandlerMixin<V> getErrorHandlerMixin() {
        if (errorHandlerMixin == null) {
            errorHandlerMixin = new ErrorHandlerMixin<>(this);
        }
        return errorHandlerMixin;
    }

    protected RequiredFieldMixin<AbstractValueWidget, UIObject> getRequiredFieldMixin() {
        if (requiredFieldMixin == null) {
            requiredFieldMixin = new RequiredFieldMixin<>(this, getStatusTextMixin().getPlaceholder());
        }
        return requiredFieldMixin;
    }

    public ClearOnKeyUpMixin<AbstractValueWidget, MaterialLabel> getClearOnKeyUpMixin() {
        if (clearOnKeyUpMixin == null) {
            clearOnKeyUpMixin = new ClearOnKeyUpMixin(this,  getStatusTextMixin().getPlaceholder());
        }
        return clearOnKeyUpMixin;
    }
}