package org.ovirt.engine.ui.webadmin.widget.editor;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.ovirt.engine.ui.common.widget.HasEnabledForContainter;
import org.ovirt.engine.ui.common.widget.editor.EditorWidget;
import org.ovirt.engine.ui.common.widget.editor.TakesValueWithChangeHandlersEditor;
import org.ovirt.engine.ui.common.widget.table.column.EnumColumn;
import org.ovirt.engine.ui.common.widget.table.column.RadioboxCell;

import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.RowStyles;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.google.gwt.view.client.SingleSelectionModel;

/**
 * An Editor that shows an enum as a Radio Group, with one Radio button for each enum value.<BR>
 * Each enum radio button may be disabled separately.
 *
 * @param <E>
 *            The enum type
 */
public class EnumRadioEditor<E extends Enum<E>> implements EditorWidget<E, LeafValueEditor<E>>, TakesValue<E>, HasValueChangeHandlers<E>, HasEnabledForContainter<E> {

    public interface EnumRadioCellTableResources extends CellTable.Resources {
        interface TableStyle extends CellTable.Style {
            /**
             * Applied to disabled rows
             */
            String cellTableDisabledRow();
        }

        @Override
        @Source({ CellTable.Style.DEFAULT_CSS, "org/ovirt/engine/ui/webadmin/css/EnumRadioCellTable.css" })
        TableStyle cellTableStyle();
    }

    /**
     * A {@link CellTable} that shows a radio button for each enum value.<BR>
     * Each enum radio button may be disabled separately.
     *
     * @param <E>
     *            The enum type
     */
    public static class EnumRadioCellTable<E extends Enum<E>> extends CellTable<E> implements HasEnabled {

        /**
         * A {@link RadioboxCell} that can be disabled
         */
        private static class ExRadioboxCell<E extends Enum<E>> extends RadioboxCell {
            /**
             * An html string representation of a checked disabled input box.
             */
            private static final SafeHtml INPUT_CHECKED_DISABLED =
                    SafeHtmlUtils.fromSafeConstant("<input type=\"radio\" tabindex=\"-1\" checked disabled/>"); //$NON-NLS-1$

            /**
             * An html string representation of an unchecked disabled input box.
             */
            private static final SafeHtml INPUT_UNCHECKED_DISABLED =
                    SafeHtmlUtils.fromSafeConstant("<input type=\"radio\" tabindex=\"-1\" disabled/>"); //$NON-NLS-1$

            private final Set<E> disabledSet;

            public ExRadioboxCell(boolean dependsOnSelection, boolean handlesSelection, Set<E> disabledSet) {
                super(dependsOnSelection, handlesSelection);
                this.disabledSet = disabledSet;
            }

            @SuppressWarnings("unchecked")
            @Override
            public void render(com.google.gwt.cell.client.Cell.Context context, Boolean value, SafeHtmlBuilder sb) {
                // Get the view data.
                Object key = context.getKey();
                E keyValue = (E) key;
                if (disabledSet.contains(keyValue)) {
                    // disabled
                    Boolean viewData = getViewData(key);
                    if (viewData != null && viewData.equals(value)) {
                        clearViewData(key);
                        viewData = null;
                    }

                    if (value != null && ((viewData != null) ? viewData : value)) {
                        sb.append(INPUT_CHECKED_DISABLED);
                    } else {
                        sb.append(INPUT_UNCHECKED_DISABLED);
                    }
                } else {
                    // enabled
                    super.render(context, value, sb);
                }
            }
        }

        /**
         * holds the enum values that are disabled
         */
        private final Set<E> disabledSet;
        private final EnumRadioCellTableResources resources;

        public EnumRadioCellTable(Class<E> enumClass, EnumRadioCellTableResources resources) {
            super(15, resources, new ListDataProvider<E>(Arrays.asList(enumClass.getEnumConstants())));
            this.resources = resources;
            disabledSet = new HashSet<E>();
            RowStyles<E> rowStyles = new RowStyles<E>() {

                @Override
                public String getStyleNames(E row, int rowIndex) {
                    return disabledSet.contains(row) ? EnumRadioCellTable.this.resources.cellTableStyle()
                            .cellTableDisabledRow() : null;
                }
            };

            setRowStyles(rowStyles);

            // Radio Column
            Column<E, Boolean> radioColumn = new Column<E, Boolean>(
                    new ExRadioboxCell<E>(true, false, disabledSet)) {
                @Override
                public Boolean getValue(E object) {
                    return getSelectionModel().isSelected(object);
                }
            };

            // Text Column
            EnumColumn<E, E> nameColumn = new EnumColumn<E, E>() {
                @Override
                protected E getRawValue(E object) {
                    return object;
                }
            };

            addColumn(radioColumn);
            addColumn(nameColumn);
            setRowData(Arrays.asList(enumClass.getEnumConstants()));
        }

        @Override
        public boolean isEnabled() {
            for (E item : getVisibleItems()) {
                if (isEnabled(item)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Returns whether a specific enum value is enabled
         */
        public boolean isEnabled(E value) {
            return true;
        }

        @Override
        public void setEnabled(boolean enabled) {
            for (E item : getVisibleItems()) {
                setEnabled(item, enabled);
            }
        }

        /**
         * Enables a specific enum value
         */
        public void setEnabled(E value, boolean enabled) {
            if (enabled) {
                disabledSet.remove(value);
            } else {
                disabledSet.add(value);
            }
            redraw();
        }

        protected Set<E> getDisabledSet() {
            return disabledSet;
        }

    }

    private final EventBus bus;
    private TakesValueWithChangeHandlersEditor<E> editor;
    private final EnumRadioCellTable<E> peer;

    private final SingleSelectionModel<E> selectionModel;

    /**
     * Create a new EnumRadioEditor, for the provided enum class
     *
     * @param enumClass
     *            the enum class
     */
    public EnumRadioEditor(Class<E> enumClass, EventBus bus) {
        this.bus = bus;
        peer = new EnumRadioCellTable<E>(enumClass,
                GWT.<EnumRadioCellTableResources> create(EnumRadioCellTableResources.class));

        // Selection Model
        selectionModel = new SingleSelectionModel<E>() {
            @Override
            public void setSelected(E object, boolean selected) {
                // enforce disabled set
                if (peer.getDisabledSet().contains(object)) {
                    return;
                } else {
                    super.setSelected(object, selected);
                }
            };
        };
        peer.setSelectionModel(selectionModel);
        selectionModel.addSelectionChangeHandler(new Handler() {
            @Override
            public void onSelectionChange(SelectionChangeEvent event) {
                E selectedObject = selectionModel.getSelectedObject();
                setValue(selectedObject);
                ValueChangeEvent.<E> fire(EnumRadioEditor.this, selectedObject);
            }
        });
    }

    @Override
    public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
        return peer.addHandler(handler, KeyDownEvent.getType());
    }

    @Override
    public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
        return peer.addHandler(handler, KeyPressEvent.getType());
    }

    @Override
    public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
        return peer.addHandler(handler, KeyUpEvent.getType());
    }

    @Override
    public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<E> handler) {
        // don't add to peer, since its changed value is the entire item list
        return bus.addHandler(ValueChangeEvent.getType(), handler);
    }

    @Override
    public TakesValueWithChangeHandlersEditor<E> asEditor() {
        if (editor == null) {
            editor = TakesValueWithChangeHandlersEditor.of(this, this);
        }
        return editor;
    }

    @Override
    public EnumRadioCellTable<E> asWidget() {
        return peer;
    }

    @Override
    public void fireEvent(GwtEvent<?> event) {
        bus.fireEvent(event);
    }

    @Override
    public int getTabIndex() {
        return peer.getTabIndex();
    }

    @Override
    public E getValue() {
        return selectionModel.getSelectedObject();
    }

    @Override
    public boolean isEnabled() {
        return peer.isEnabled();
    }

    @Override
    public void setAccessKey(char key) {
        peer.setAccessKey(key);
    }

    @Override
    public void setEnabled(boolean enabled) {
        peer.setEnabled(enabled);
    }

    public void setEnabled(E value, boolean enabled) {
        peer.setEnabled(value, enabled);
    }

    @Override
    public void setFocus(boolean focused) {
        peer.setFocus(focused);
    }

    @Override
    public void setTabIndex(int index) {
        peer.setTabIndex(index);
    }

    @Override
    public void setValue(E value) {
        selectionModel.setSelected(value, true);
    }

}
