package de.knoobie.vaadin.addons.bootstrap.formgroup.client;

import com.google.gwt.aria.client.Id;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ui.VCustomComponent;
import com.vaadin.client.ui.VNativeSelect;
import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.ui.ContentMode;
import de.knoobie.vaadin.addons.bootstrap.formgroup.shared.BootstrapCss;
import de.knoobie.vaadin.addons.bootstrap.formgroup.shared.BootstrapMode;

import static de.knoobie.vaadin.addons.bootstrap.formgroup.shared.FormGroupDefaults.*;

public class ClientSideFormGroup extends VCustomComponent {

    // row is needed to let bootstrap handle the lower media query
    // where the field should drop below the label
    private static final String CLASSNAME = BootstrapCss.FORM_GROUP + " row";

    Element label;

    Element div;
    DivElement feedback;
    Element small;

    BootstrapMode oldBootstrapMode = BOOTSTRAP_MODE;

    boolean hasDescription = false;
    boolean isDescriptionHeightReservedIfEmpty = IS_DESCRIPTION_HEIGHT_RESERVED_IF_EMPTY;
    boolean hasFeedback = false;
    boolean isFeedbackHeightReservedIfEmpty = IS_FEEDBACK_HEIGHT_RESERVED_IF_EMPTY;
    boolean labelReplaced = false;

    Element widgetElement;
    VaadinWidgetType widgetType;

    public ClientSideFormGroup() {
        super();
        setStyleName(CLASSNAME);

        div = Document.get().createDivElement();

        small = Document.get().createElement("small");
        small.addClassName("form-text");
        small.addClassName("text-muted");
        AriaHelper.ensureHasId(small);

        feedback = Document.get().createDivElement();
        feedback.addClassName("form-control-feedback");
        AriaHelper.ensureHasId(feedback);
    }

    public void setDescriptionHeightReservedIfEmpty(boolean reservedIfEmpty) {
        isDescriptionHeightReservedIfEmpty = reservedIfEmpty;
    }

    public void setFeedbackHeightReservedIfEmpty(boolean reservedIfEmpty) {
        isFeedbackHeightReservedIfEmpty = reservedIfEmpty;
    }

    void setLabelRatio(String labelStyle) {
        label.setClassName(labelStyle);
    }

    void setFieldRatio(String fieldStyle) {
        div.setClassName(fieldStyle);
    }

    void setCaption(String caption, boolean isHtml, boolean isRequired) {
        if (isRequired) {
            addStyleName("required");
        } else {
            removeStyleName("required");
        }

        if (isHtml) {
            label.setInnerHTML(caption);
        } else {
            label.setInnerText(caption);
        }
    }

    void setDescription(String description, ContentMode contentMode) {
        Element element = getInput();

        if (description == null || description.isEmpty() || description.trim().isEmpty()) {
            if (hasFeedback) {
                Roles.getTextboxRole().setAriaDescribedbyProperty(element, Id.of(this.feedback));
            } else {
                Roles.getTextboxRole().removeAriaDescribedbyProperty(element);
            }
            small.setInnerHTML(isDescriptionHeightReservedIfEmpty ? " " : "");
            hasDescription = false;
            return;
        }

        if (!hasDescription) {
            if (hasFeedback) {
                Roles.getTextboxRole().setAriaDescribedbyProperty(element, Id.of(small), Id.of(this.feedback));
            } else {
                Roles.getTextboxRole().setAriaDescribedbyProperty(element, Id.of(small));
            }
            hasDescription = true;
        }

        if (ContentMode.HTML == contentMode) {
            small.setInnerHTML(description);
        } else {
            small.setInnerText(description);
        }
    }

    void setFeedback(String feedback, ContentMode contentMode) {
        Element element = getInput();

        if (feedback == null || feedback.isEmpty() || feedback.trim().isEmpty()) {
            if (hasDescription) {
                Roles.getTextboxRole().setAriaDescribedbyProperty(element, Id.of(small));
            } else {
                Roles.getTextboxRole().removeAriaDescribedbyProperty(element);
            }
            this.feedback.setInnerHTML(isFeedbackHeightReservedIfEmpty ? " " : "");
            hasFeedback = false;
            return;
        }

        if (!hasFeedback) {
            if (hasDescription) {
                Roles.getTextboxRole().setAriaDescribedbyProperty(element, Id.of(small), Id.of(this.feedback));
            } else {
                Roles.getTextboxRole().setAriaDescribedbyProperty(element, Id.of(this.feedback));
            }
            hasFeedback = true;
        }

        if (ContentMode.HTML == contentMode) {
            this.feedback.setInnerHTML(feedback);
        } else {
            this.feedback.setInnerText(feedback);
        }
    }

    private Element getInput() {
        if (VaadinWidgetType.CHECKBOX.equals(widgetType)) {
            // we want to apply aria attributes to the input / not the styling div
            return widgetElement.getFirstChildElement();
        } else if (VaadinWidgetType.CHECKBOXGROUP.equals(widgetType)) {
            // just a reminder - CheckBoxGroup can have the describedby here because
            // the outer div has tabindex and is kinda like a fieldset for all inputs.
            return widgetElement;
        }

        // anything else
        return widgetElement;
    }

    private Element getStylingElement() {
        return widgetElement;
    }

    void setMode(BootstrapMode bootstrapMode) {
        if (bootstrapMode == null) bootstrapMode = BootstrapMode.DEFAULT;
        if (oldBootstrapMode == bootstrapMode) return;

        removeOldStyle(oldBootstrapMode);
        addNewStyle(bootstrapMode);

        oldBootstrapMode = bootstrapMode;
    }

    private void removeOldStyle(BootstrapMode mode) {
        if (BootstrapMode.DEFAULT == mode) return;

        removeStyleName(mode.getFormGroupCss());
        getStylingElement().removeClassName(mode.getFormControlCss());
    }

    private void addNewStyle(BootstrapMode mode) {
        if (BootstrapMode.DEFAULT == mode) return;

        addStyleName(mode.getFormGroupCss());
        getStylingElement().addClassName(mode.getFormControlCss());
    }

    void setFor(ComponentConnector forId) {
        Widget widget = forId.getWidget();

        // VNativeSelect need binding of Caption on the ListBox to get :focus
        if (widget instanceof VNativeSelect) {
            AriaHelper.bindCaption(((VNativeSelect) widget).getListBox(), label);
            return;
        }

        AriaHelper.bindCaption(widget, label);
    }

    public void setWidget(Widget w, boolean delegateCaptionHandling) {
        if (w != null) {
            this.widgetType = VaadinWidgetUtils.getWidgetTypeByWidget(w);
            this.widgetElement = VaadinWidgetUtils.getElementByVaadinWidget(w, this.widgetType);
            if (this.widgetType.isAddStyle()) {
                this.widgetElement.addClassName(this.widgetType.getCssStyle());
            } else {
                this.widgetElement.setClassName(this.widgetType.getCssStyle());
            }
            if (delegateCaptionHandling) {
                getElement().appendChild(label = DOM.createLabel());
            } else {
                getElement().appendChild(label = DOM.createDiv());
            }
            getElement().appendChild(div);
        }
        super.setWidget(w);

        if (w != null) {
            getContainerElement().appendChild(small);
            getContainerElement().appendChild(feedback);
        }
    }

    @Override
    public boolean remove(Widget w) {
        getElement().removeChild(label);
        getElement().removeChild(div);
        getContainerElement().removeChild(small);
        getContainerElement().removeChild(feedback);
        return super.remove(w);
    }

    @Override
    protected com.google.gwt.user.client.Element getContainerElement() {
        return DOM.asOld(div);
    }
}