/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates.
 *  
 * 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.
 */

package org.uberfire.ext.security.management.client.widgets.management.editor.user;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;

import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.EditTextCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.user.cellview.client.ColumnSortEvent;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.ProvidesKey;
import org.gwtbootstrap3.client.ui.constants.ButtonSize;
import org.gwtbootstrap3.client.ui.constants.ButtonType;
import org.gwtbootstrap3.client.ui.constants.IconType;
import org.gwtbootstrap3.client.ui.gwt.ButtonCell;
import org.jboss.errai.security.shared.api.identity.User;
import org.uberfire.client.mvp.UberView;
import org.uberfire.ext.security.management.api.Capability;
import org.uberfire.ext.security.management.api.UserManager;
import org.uberfire.ext.security.management.api.exception.SecurityManagementException;
import org.uberfire.ext.security.management.client.ClientUserSystemManager;
import org.uberfire.ext.security.management.client.resources.i18n.UsersManagementWidgetsConstants;
import org.uberfire.ext.security.management.client.widgets.management.events.CreateUserAttributeEvent;
import org.uberfire.ext.security.management.client.widgets.management.events.DeleteUserAttributeEvent;
import org.uberfire.ext.security.management.client.widgets.management.events.OnErrorEvent;
import org.uberfire.ext.security.management.client.widgets.management.events.UpdateUserAttributeEvent;
import org.uberfire.ext.security.management.client.widgets.popup.ConfirmBox;

/**
 * <p>Presenter class for user editor attributes widget.</p>
 */
@Dependent
public class UserAttributesEditor implements IsWidget,
                                             org.uberfire.ext.security.management.client.editor.user.UserAttributesEditor {

    /**
     * The provider that holds the list of containers.
     */
    final ListDataProvider<Entry<String, String>> userAttributesProvider = new ListDataProvider<Entry<String, String>>();
    final ProvidesKey<Entry<String, String>> KEY_PROVIDER = new ProvidesKey<Map.Entry<String, String>>() {
        @Override
        public Object getKey(final Map.Entry<String, String> item) {
            return item == null ? null : item.getKey();
        }
    };
    public View view;
    ClientUserSystemManager userSystemManager;
    ConfirmBox confirmBox;
    Event<CreateUserAttributeEvent> createUserAttributeEventEvent;
    Event<UpdateUserAttributeEvent> updateUserAttributeEventEvent;
    Event<DeleteUserAttributeEvent> deleteUserAttributeEventEvent;
    Event<OnErrorEvent> errorEvent;
    NewUserAttributeEditor newUserAttributeEditor;
    Map<UserManager.UserAttribute, String> attributes;
    private final FieldUpdater keyModifiedEventHandler = new FieldUpdater<Entry<String, String>, String>() {
        @Override
        public void update(final int index,
                           final Entry<String, String> object,
                           final String value) {
            final String k = object != null ? object.getKey() : null;
            if (hasValueChanged(k,
                                value)) {
                updateUserAttribute(index,
                                    value,
                                    object != null ? object.getValue() : null);
            }
        }
    };
    private final FieldUpdater valueModifiedEventHandler = new FieldUpdater<Entry<String, String>, String>() {
        @Override
        public void update(final int index,
                           final Entry<String, String> object,
                           final String value) {
            final String k = object != null ? object.getValue() : null;
            if (hasValueChanged(k,
                                value)) {
                updateUserAttribute(index,
                                    object != null ? object.getKey() : null,
                                    value);
            }
        }
    };
    private final FieldUpdater removeButtonHandler = new FieldUpdater<Entry<String, String>, String>() {
        @Override
        public void update(final int index,
                           final Entry<String, String> object,
                           final String value) {
            if (value == null) {
                // Attribute is mandatory and cannot be removed.
                errorEvent
                        .fire(new OnErrorEvent(UserAttributesEditor.this,
                                               new SecurityManagementException(UsersManagementWidgetsConstants.INSTANCE.attributeIsMandatory())));
            } else {
                confirmBox.show(UsersManagementWidgetsConstants.INSTANCE.confirmAction(),
                                UsersManagementWidgetsConstants.INSTANCE.ensureRemoveAttribute(),
                                () -> removeUserAttribute(index,
                                                          object),
                                () -> {
                                });
            }
        }
    };

    /*  ******************************************************************************************************
                                 PUBLIC PRESENTER API 
     ****************************************************************************************************** */
    boolean isEditMode;

    @Inject
    public UserAttributesEditor(final ClientUserSystemManager userSystemManager,
                                final ConfirmBox confirmBox,
                                final Event<CreateUserAttributeEvent> createUserAttributeEventEvent,
                                final Event<UpdateUserAttributeEvent> updateUserAttributeEventEvent,
                                final Event<DeleteUserAttributeEvent> deleteUserAttributeEventEvent,
                                final Event<OnErrorEvent> errorEvent,
                                final NewUserAttributeEditor newUserAttributeEditor,
                                final View view) {
        this.userSystemManager = userSystemManager;
        this.confirmBox = confirmBox;
        this.createUserAttributeEventEvent = createUserAttributeEventEvent;
        this.updateUserAttributeEventEvent = updateUserAttributeEventEvent;
        this.deleteUserAttributeEventEvent = deleteUserAttributeEventEvent;
        this.errorEvent = errorEvent;
        this.newUserAttributeEditor = newUserAttributeEditor;
        this.view = view;
    }

    @PostConstruct
    public void init() {
        view.init(this);
        view.initWidgets(newUserAttributeEditor.view);
        buildViewColumns();
    }

    @Override
    public void show(final User user) {
        this.isEditMode = false;
        open(user);
    }

    @Override
    public void edit(final User user) {
        this.isEditMode = true;
        open(user);
    }

    @Override
    public void flush() {
        assert isEditMode;
        // No additional flush logic to perform here.
    }

    @Override
    public Map<String, String> getValue() {
        final Map<String, String> result = new HashMap<String, String>(attributes.size());
        for (Map.Entry<UserManager.UserAttribute, String> entry : attributes.entrySet()) {
            result.put(entry.getKey().getName(),
                       entry.getValue());
        }
        return result;
    }

    /*  ******************************************************************************************************
                                 VIEW CALLBACKS 
     ****************************************************************************************************** */

    @Override
    public void setViolations(Set<ConstraintViolation<User>> constraintViolations) {
        //  Currently no violations expected.
    }

    public void clear() {
        userAttributesProvider.getList().clear();
        attributes = null;
        isEditMode = false;
    }



    /*  ******************************************************************************************************
                                 PRIVATE METHODS AND VALIDATORS
     ****************************************************************************************************** */

    @Override
    public Widget asWidget() {
        return view.asWidget();
    }

    /**
     * View callback for getting the list.
     */
    void addDataDisplay(HasData<Entry<String, String>> display) {
        userAttributesProvider.addDataDisplay(display);
    }

    protected void loadUserAttributes(final User user) {
        final Map<String, String> properties = user.getProperties();
        if (properties != null) {
            final Map<UserManager.UserAttribute, String> _properties = new HashMap<UserManager.UserAttribute, String>(properties.size());
            for (final Map.Entry<String, String> entry : properties.entrySet()) {
                final String name = entry.getKey();
                UserManager.UserAttribute attribute = getSupportedAttribute(name);
                final String value = entry.getValue();
                if (attribute != null) {
                    _properties.put(attribute,
                                    value);
                }
            }
            this.attributes = _properties;
        }
    }

    protected UserManager.UserAttribute getSupportedAttribute(final String name) {
        UserManager.UserAttribute attribute = userSystemManager.getUserSupportedAttribute(name);
        if (attribute == null) {
            attribute = userSystemManager.createUserAttribute(name,
                                                              false,
                                                              true,
                                                              null);
        }
        return attribute;
    }

    protected void open(final User user) {

        // User attributes editor settings.
        newUserAttributeEditor.clear().showAddButton();
        view.setCanCreate(canManageAttributes());

        // User load.
        if (user != null) {
            loadUserAttributes(user);
            redraw();
        }
    }

    protected void redraw() {
        userAttributesProvider.getList().clear();
        buildViewColumns();
        if (attributes != null) {
            for (final Map.Entry<UserManager.UserAttribute, String> entry : attributes.entrySet()) {
                final UserManager.UserAttribute attribute = entry.getKey();
                final String value = entry.getValue();
                final Map.Entry<String, String> attrEntry = createAttributeEntry(attribute.getName(),
                                                                                 value);
                addAttributeEntry(attrEntry);
            }
            view.redraw();
        } else {
            view.showEmpty();
        }
    }

    protected void addAttributeEntry(final Map.Entry<String, String> entry) {
        List<Map.Entry<String, String>> contacts = userAttributesProvider.getList();
        contacts.remove(entry);
        contacts.add(entry);
    }

    boolean canManageAttributes() {
        if (!isEditMode) {
            return false;
        }
        final boolean canManageAttrs = userSystemManager.isUserCapabilityEnabled(Capability.CAN_MANAGE_ATTRIBUTES);
        return canManageAttrs;
    }

    protected void buildViewColumns() {
        int columnCount = view.getColumnCount();
        while (columnCount > 0) {
            view.removeColumn(0);
            columnCount = view.getColumnCount();
        }

        // Attach a column sort handler to the ListDataProvider to sort the list.
        ColumnSortEvent.ListHandler<Map.Entry<String, String>> sortHandler = new ColumnSortEvent.ListHandler<Map.Entry<String, String>>(userAttributesProvider.getList());
        view.setColumnSortHandler(sortHandler);

        // Attribute name.
        final com.google.gwt.user.cellview.client.Column<Map.Entry<String, String>, String> keyColumn = createAttributeNameColumn(sortHandler);
        if (keyColumn != null) {
            view.addColumn(keyColumn,
                           UsersManagementWidgetsConstants.INSTANCE.name());
        }

        // Attribute value.
        final com.google.gwt.user.cellview.client.Column<Map.Entry<String, String>, String> valueColumn = createAttributeValueColumn(sortHandler);
        if (valueColumn != null) {
            view.addColumn(valueColumn,
                           UsersManagementWidgetsConstants.INSTANCE.value());
        }

        // Create remove button column.
        final com.google.gwt.user.cellview.client.Column<Map.Entry<String, String>, String> removeColumn = createAttributeRemoveColumn();
        if (removeColumn != null) {
            view.addColumn(removeColumn,
                           UsersManagementWidgetsConstants.INSTANCE.remove());
        }
    }

    protected com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> createAttributeNameColumn(ColumnSortEvent.ListHandler<Entry<String, String>> sortHandler) {
        // Attribute name.
        final Cell<String> nameCell = canManageAttributes() ? new EditTextCell() : new TextCell();
        final com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> keyColumn = new com.google.gwt.user.cellview.client.Column<Entry<String, String>, String>(
                nameCell) {
            @Override
            public String getValue(final Entry<String, String> object) {
                return object.getKey() != null ? object.getKey() : "";
            }
        };
        keyColumn.setSortable(true);
        sortHandler.setComparator(keyColumn,
                                  new Comparator<Entry<String, String>>() {
                                      @Override
                                      public int compare(Entry<String, String> o1,
                                                         Entry<String, String> o2) {
                                          return o1.getKey().compareTo(o2.getKey());
                                      }
                                  });
        keyColumn.setFieldUpdater(canManageAttributes() ? keyModifiedEventHandler : null);

        return keyColumn;
    }

    protected com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> createAttributeValueColumn(ColumnSortEvent.ListHandler<Entry<String, String>> sortHandler) {
        // Attribute value.
        final Cell<String> valueCell = canManageAttributes() ? new EditTextCell() : new TextCell();
        final com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> valueColumn = new com.google.gwt.user.cellview.client.Column<Entry<String, String>, String>(
                valueCell) {
            @Override
            public String getValue(final Entry<String, String> object) {
                return object.getValue() != null ? object.getValue() : "";
            }
        };
        valueColumn.setSortable(true);
        sortHandler.setComparator(valueColumn,
                                  new Comparator<Entry<String, String>>() {
                                      @Override
                                      public int compare(Entry<String, String> o1,
                                                         Entry<String, String> o2) {
                                          return o1.getValue().compareTo(o2.getValue());
                                      }
                                  });
        valueColumn.setFieldUpdater(canManageAttributes() ? valueModifiedEventHandler : null);

        return valueColumn;
    }

    protected com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> createAttributeRemoveColumn() {
        // On read mode, remove button not present.
        if (!canManageAttributes()) {
            return null;
        }

        // Create remove button column.
        final ButtonCell removeButtonCell = new ButtonCell(IconType.CLOSE,
                                                           ButtonType.LINK,
                                                           ButtonSize.SMALL);
        final com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> removeColumn =
                new com.google.gwt.user.cellview.client.Column<Entry<String, String>, String>(removeButtonCell) {

                    @Override
                    public String getValue(final Entry<String, String> object) {
                        // if can be removed return empty string, if not, return null
                        if (object != null) {
                            final UserManager.UserAttribute attribute = getAttribute(object.getKey());
                            if (attribute != null && !attribute.isMandatory() && attribute.isEditable()) {
                                removeButtonCell.setEnabled(true);
                                return "";
                            }
                        }
                        removeButtonCell.setEnabled(false);
                        return null;
                    }
                };

        removeColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        removeColumn.setFieldUpdater(canManageAttributes() ? removeButtonHandler : null);

        return removeColumn;
    }

    private UserManager.UserAttribute getAttribute(final String name) {
        if (!isEmpty(name) && attributes != null && !attributes.isEmpty()) {
            for (final Map.Entry<UserManager.UserAttribute, String> entry : attributes.entrySet()) {
                final UserManager.UserAttribute attribute = entry.getKey();
                if (name.equals(attribute.getName())) {
                    return attribute;
                }
            }
        }
        return null;
    }

    private Map.Entry<String, String> createAttributeEntry(final String key,
                                                           final String value) {
        return new Map.Entry<String, String>() {
            @Override
            public String getKey() {
                return key;
            }

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

            @Override
            public String setValue(String value) {
                return null;
            }
        };
    }

    private boolean hasValueChanged(final String s1,
                                    final String s2) {
        if (s1 != null && !s1.equals(s2)) {
            return true;
        }
        if (s2 != null && !s2.equals(s1)) {
            return true;
        }
        return false;
    }

    private boolean isEmpty(final String str) {
        return str == null || str.trim().length() == 0;
    }

    void updateUserAttribute(final int index,
                             final String key,
                             final String value) {
        final Entry<String, String> attr = createAttributeEntry(key,
                                                                value);
        UserManager.UserAttribute attribute = getAttribute(key);
        attributes.put(attribute,
                       value);
        redraw();
        updateUserAttributeEventEvent.fire(new UpdateUserAttributeEvent(UserAttributesEditor.this,
                                                                        attr));
    }

    void removeUserAttribute(final int index,
                             final Entry<String, String> entry) {
        UserManager.UserAttribute attribute = getAttribute(entry.getKey());
        attributes.remove(attribute);
        redraw();
        deleteUserAttributeEventEvent.fire(new DeleteUserAttributeEvent(UserAttributesEditor.this,
                                                                        entry));
    }

    void onAttributeCreated(@Observes final CreateUserAttributeEvent createUserAttributeEvent) {
        final Entry<String, String> entry = createUserAttributeEvent.getAttribute();
        final String name = entry.getKey();
        final String value = entry.getValue();
        UserManager.UserAttribute attribute = getSupportedAttribute(name);
        attributes.put(attribute,
                       value);
        redraw();
    }

    public interface View extends UberView<UserAttributesEditor> {

        View initWidgets(NewUserAttributeEditor.View newUserAttributeEditorView);

        View setCanCreate(boolean isCreateAllowed);

        View setColumnSortHandler(ColumnSortEvent.ListHandler<Map.Entry<String, String>> sortHandler);

        View addColumn(com.google.gwt.user.cellview.client.Column<Entry<String, String>, String> column,
                       String name);

        View removeColumn(int index);

        int getColumnCount();

        View showEmpty();

        View redraw();
    }
}
