/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.jboss.as.console.client.administration.role.form;

import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import org.jboss.as.console.client.administration.role.model.Principal;
import org.jboss.as.console.client.administration.role.model.Principals;
import org.jboss.ballroom.client.widgets.forms.FormItem;
import org.jboss.ballroom.client.widgets.forms.InputElementWrapper;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Harald Pehl
 */
public class PrincipalFormItem extends FormItem<Principal> {

    private final Principal.Type type;
    private final Map<String, Principal> cache;
    private Principal value;
    private TextBox textBox;
    private SuggestBox suggestBox;
    private PrincipalSuggestOracle oracle;
    private InputElementWrapper wrapper;

    public PrincipalFormItem(final Principal.Type type, final String name, final String title) {
        super(name, title);

        this.type = type;
        this.cache = new HashMap<String, Principal>();

        setup();
    }

    private void setup() {
        textBox = new TextBox();
        textBox.setName(name);
        textBox.setTitle(title);
        textBox.setTabIndex(0);

        oracle = new PrincipalSuggestOracle(type);
        suggestBox = new SuggestBox(oracle, textBox);
        suggestBox.addValueChangeHandler(new ValueChangeHandler<String>() {
            @Override
            public void onValueChange(ValueChangeEvent<String> event) {
                setModified(true);
                String newValue = event.getValue();
                setUndefined(newValue.equals(""));
                parseValue(newValue);
            }
        });
        suggestBox.addSelectionHandler(new SelectionHandler<SuggestOracle.Suggestion>() {
            @Override
            public void onSelection(
                    SelectionEvent<SuggestOracle.Suggestion> suggestionSelectionEvent) {
                setModified(true);
                String newValue = suggestBox.getValue();
                setUndefined(newValue.equals(""));
                parseValue(newValue);
            }
        });
        wrapper = new InputElementWrapper(suggestBox, this);
    }

    private void parseValue(final String name) {
        Principal principal = null;
        if (name != null && name.trim().length() != 0) {
            principal = cache.get(name);
            if (principal == null) {
                // create a new principal
                String id = type.name().toLowerCase() + "-" + name;
                principal = new Principal(id, name, type);
                cache.put(principal.getId(), principal);
            }
        }
        setValue(principal);
    }

    @Override
    public void setFiltered(boolean filtered) {
        super.setFiltered(filtered);
        super.toggleAccessConstraint(textBox, filtered);
        textBox.setEnabled(!filtered);
    }

    @Override
    public Widget asWidget() {
        return wrapper;
    }

    @Override
    public Principal getValue() {
        return value;
    }

    @Override
    public void setValue(Principal value) {
        this.value = value;
        toggleExpressionInput(textBox, false);
        textBox.setValue(asString());
    }

    @Override
    public void resetMetaData() {
        super.resetMetaData();
        textBox.setValue(null);
    }

    @Override
    public void setExpressionValue(String expr) {
        this.expressionValue = expr;
        if (expressionValue != null) {
            toggleExpressionInput(textBox, true);
            textBox.setValue(expressionValue);
        }
    }

    @Override
    public void setEnabled(boolean b) {
        textBox.setEnabled(b);
    }

    @Override
    public void setErroneous(boolean b) {
        super.setErroneous(b);
        wrapper.setErroneous(b);
    }

    @Override
    public String getErrMessage() {
        return super.getErrMessage() + ": no whitespace, no special chars";
    }

    @Override
    public boolean validate(Principal value) {
        return !(isRequired() && value == null);
    }

    @Override
    public void clearValue() {
        setValue(null);
    }

    @Override
    protected void toggleExpressionInput(Widget target, boolean flag) {
        wrapper.setExpression(flag);
    }

    public String asString() {
        return value != null ? value.getName() : "";
    }

    public void update(Principals principals) {
        oracle.update(principals);
    }

    // ------------------------------------------------------ suggest stuff

    static class PrincipalSuggestion extends MultiWordSuggestOracle.MultiWordSuggestion {

        private final Principal principal;

        public PrincipalSuggestion(final Principal principal) {
            super(principal.getName(), principal.getName());
            this.principal = principal;
        }

        public Principal getPrincipal() {
            return principal;
        }
    }

    static class PrincipalSuggestOracle extends SuggestOracle {

        private final Principal.Type type;
        private final List<PrincipalSuggestion> suggestions;

        PrincipalSuggestOracle(Principal.Type type) {
            this.type = type;
            this.suggestions = new ArrayList<PrincipalSuggestion>();
        }

        @Override
        public void requestSuggestions(final Request request, final Callback callback) {
            Response response = new Response(matchingQuery(request.getQuery(), request.getLimit()));
            callback.onSuggestionsReady(request, response);
        }

        public Collection<PrincipalSuggestion> matchingQuery(final String query, final int limit) {
            List<PrincipalSuggestion> matchingResults = new ArrayList<PrincipalSuggestion>(limit);

            if (query.length() > 1) {
                String prefix = query.toLowerCase();

                int i = 0;
                int s = suggestions.size();
                while (i < s && !suggestions.get(i).getDisplayString().toLowerCase().contains(prefix)) {
                    i++;
                }

                int count = 0;
                while (i < s && suggestions.get(i).getDisplayString().toLowerCase().contains(prefix) && count < limit) {
                    matchingResults.add(suggestions.get(i));
                    i++;
                    count++;
                }
            }
            return matchingResults;
        }

        public void update(Principals principals) {
            Collection<Principal> byType = principals.get(type);
            if (byType != null) {
                suggestions.clear();
                for (Principal principal : byType) {
                    suggestions.add(new PrincipalSuggestion(principal));
                }
            }
        }
    }
}
