package org.ovirt.engine.ui.webadmin.uicommon.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.ovirt.engine.ui.common.uicommon.model.TreeNodeModel;
import org.ovirt.engine.ui.common.utils.ElementIdUtils;
import org.ovirt.engine.ui.common.widget.tree.TreeModelWithElementId;

import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.CompositeCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.HasCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.DOM;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.DefaultSelectionEventManager;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;

/**
 * A TreeView Model for {@link TreeNodeModel<T>} Nodes
 */
public class ModelListTreeViewModel<T, M extends TreeNodeModel<T, M>> implements TreeModelWithElementId {

    private final class NodeSelectionHandler implements SelectionHandler<M> {

        private final HasData<M> display;

        private NodeSelectionHandler(HasData<M> display) {
            this.display = display;
        }

        @Override
        public void onSelection(SelectionEvent<M> event) {
            M selectedItem = event.getSelectedItem();
            display.getSelectionModel().setSelected(selectedItem,
                    selectedItem.getSelected());
        }

    }

    private final class CellLabel extends TextColumn<M> {
        @Override
        public String getValue(M object) {
            return object.getName();
        }
    }

    private final class CheckboxColumn implements HasCell<M, Boolean> {

        private final CheckboxCell cell = new ExCheckboxCell(true, false);

        @Override
        public Cell<Boolean> getCell() {
            return cell;
        }

        @Override
        public FieldUpdater<M, Boolean> getFieldUpdater() {
            return null;
        }

        @Override
        public Boolean getValue(M object) {
            return selectionModel.isSelected(object);
        }

    }

    interface ExCheckboxCellTemplate extends SafeHtmlTemplates {

        /**
         * An html string representation of a checked input box.
         */
        @Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\" checked/>")
        SafeHtml inputChecked(String elementId);

        /**
         * An html string representation of an unchecked input box.
         */
        @Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\"/>")
        SafeHtml inputUnchecked(String elementId);

        /**
         * An html string representation of a disabled checked input box.
         */
        @Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\" checked disabled/>")
        SafeHtml inputCheckedDisabled(String elementId);

        /**
         * An html string representation of a disabled unchecked input box.
         */
        @Template("<input type=\"checkbox\" id=\"{0}\" tabindex=\"-1\" disabled/>")
        SafeHtml inputUncheckedDisabled(String elementId);

    }

    /**
     * A {@link CheckboxCell} that can be disabled
     */
    private final class ExCheckboxCell extends CheckboxCell {

        private final ExCheckboxCellTemplate template = GWT.create(ExCheckboxCellTemplate.class);

        private ExCheckboxCell(boolean dependsOnSelection, boolean handlesSelection) {
            super(dependsOnSelection, handlesSelection);
        }

        @SuppressWarnings("unchecked")
        @Override
        public void render(Context context, Boolean value, SafeHtmlBuilder sb) {
            // TODO semi-checked checkbox (null value)
            // value = ((SimpleSelectionTreeNodeModel) context.getKey()).getIsSelectedNullable();
            Object key = context.getKey();
            Boolean viewData = getViewData(key);

            M nodeModel = (M) key;
            String elementId = ElementIdUtils.createTreeCellElementId(
                    elementIdPrefix, nodeModel, roots);

            if (viewData != null && viewData.equals(value)) {
                clearViewData(key);
                viewData = null;
            }

            if (value != null && ((viewData != null) ? viewData : value)) {
                // Checked state
                sb.append(nodeModel.isEditable() ? template.inputChecked(elementId)
                        : template.inputCheckedDisabled(elementId));
            } else {
                // Unchecked state
                sb.append(nodeModel.isEditable() ? template.inputUnchecked(elementId)
                        : template.inputUncheckedDisabled(elementId));
            }
        }

    }

    private final AsyncDataProvider<M> asyncTreeDataProvider;

    private final List<HasCell<M, ?>> cells = new ArrayList<HasCell<M, ?>>();

    private List<M> roots;

    private final MultiSelectionModel<M> selectionModel = new MultiSelectionModel<M>();

    private NodeSelectionHandler nodeSelectionHandler;

    private String elementIdPrefix = DOM.createUniqueId();

    public ModelListTreeViewModel() {
        cells.add(new CheckboxColumn());
        cells.add(new CellLabel());

        asyncTreeDataProvider = new AsyncDataProvider<M>() {
            @Override
            protected void onRangeChanged(HasData<M> display) {
                // no-op
            }

            @Override
            protected void updateRowData(final HasData<M> display, int start, List<M> values) {
                super.updateRowData(display, start, values);

                for (M model : values) {
                    UpdateSelection(model, display);
                }
            }
        };

        // Drive selection
        selectionModel.addSelectionChangeHandler(new Handler() {
            @Override
            public void onSelectionChange(SelectionChangeEvent event) {
                Set<M> selectedSet = selectionModel.getSelectedSet();
                HashSet<TreeNodeModel<?, ?>> removedSet = new HashSet<TreeNodeModel<?, ?>>();
                for (TreeNodeModel<T, M> child : roots) {
                    if (!selectedSet.contains(child) && child.getSelected()) {
                        selectedSet.remove(child);
                        removedSet.add(child);
                    }
                    for (TreeNodeModel<T, M> child1 : child.getChildren()) {
                        if (!selectedSet.contains(child1) && child1.getSelected()) {
                            selectedSet.remove(child1);
                            removedSet.add(child1);
                        }
                        for (TreeNodeModel<T, M> child2 : child1.getChildren()) {
                            if (!selectedSet.contains(child2) && child2.getSelected()) {
                                selectedSet.remove(child2);
                                removedSet.add(child2);
                            }
                        }
                    }
                }
                for (M m : selectedSet) {
                    m.setSelected(true);
                }
                for (TreeNodeModel<?, ?> treeNodeModel : removedSet) {
                    treeNodeModel.setSelected(false);
                }
            }
        });
    }

    public void UpdateSelection(M model, final HasData<M> display) {
        // Add Selection Listener
        if (nodeSelectionHandler == null) {
            nodeSelectionHandler = new NodeSelectionHandler(display);
        }
        model.addSelectionHandler(nodeSelectionHandler);

        // show value
        display.getSelectionModel().setSelected(model, model.getSelected());

        for (int i = 0; i < model.getChildren().size(); i++) {
            UpdateSelection(model.getChildren().get(i), display);
        }
    }

    public AsyncDataProvider<M> getAsyncTreeDataProvider() {
        return asyncTreeDataProvider;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <N> NodeInfo<?> getNodeInfo(N value) {
        M model = (M) value;
        CompositeCell<M> composite = new CompositeCell<M>(cells);
        if (value == null) {
            // root node
            return new DefaultNodeInfo<M>(asyncTreeDataProvider,
                    composite,
                    selectionModel,
                    DefaultSelectionEventManager.<M> createCheckboxManager(),
                    null);
        } else {
            // child nodes
            return new DefaultNodeInfo<M>(new ListDataProvider<M>(model.getChildren()),
                    composite,
                    selectionModel,
                    DefaultSelectionEventManager.<M> createCheckboxManager(),
                    null);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean isLeaf(Object value) {
        M model = (M) value;
        return model != null && model.getChildren() != null && model.getChildren().size() == 0;
    }

    /**
     * Set the Root list- required to sync the Model Tree correctly
     *
     * @param arrayList
     */
    public void setRoots(List<M> arrayList) {
        this.roots = arrayList;
    }

    @Override
    public void setElementIdPrefix(String elementIdPrefix) {
        this.elementIdPrefix = elementIdPrefix;
    }

}
