/*
 * 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.operation;

import org.jboss.as.console.client.administration.role.model.ModelHelper;
import org.jboss.as.console.client.administration.role.model.Role;
import org.jboss.as.console.client.administration.role.model.RoleAssignment;
import org.jboss.as.console.client.shared.flow.FunctionCallback;
import org.jboss.as.console.client.shared.flow.FunctionContext;
import org.jboss.dmr.client.ModelDescriptionConstants;
import org.jboss.dmr.client.ModelNode;
import org.jboss.dmr.client.Property;
import org.jboss.dmr.client.dispatch.DispatchAsync;
import org.jboss.dmr.client.dispatch.impl.DMRAction;
import org.jboss.gwt.flow.client.Control;
import org.jboss.gwt.flow.client.Function;

import java.util.LinkedList;
import java.util.List;

import static org.jboss.dmr.client.ModelDescriptionConstants.*;

/**
 * Functions to check, and add (if not present) a role assignments; supposed to work inside a call to {@link
 * org.jboss.gwt.flow.client.Async#waterfall(Object, org.jboss.gwt.flow.client.Outcome,
 * org.jboss.gwt.flow.client.Function[])}
 *
 * @author Harald Pehl
 */
public final class RoleAssignmentFunctions {

    private RoleAssignmentFunctions() {}

    public static class Check implements Function<FunctionContext> {

        private final DispatchAsync dispatcher;
        private final Role role;

        Check(final DispatchAsync dispatcher, final Role role) {
            this.dispatcher = dispatcher;
            this.role = role;
        }

        @Override
        public void execute(final Control<FunctionContext> control) {
            ModelNode node = ModelHelper.roleMapping(role);
            node.get(OP).set(READ_RESOURCE_OPERATION);
            dispatcher.execute(new DMRAction(node), new FunctionCallback(control) {
                @Override
                protected void proceed() {
                    // role exists - next function will skip its DMR operation
                    control.getContext().push(true);
                    control.proceed();
                }

                @Override
                protected void abort() {
                    // no role - create it in the next function
                    control.getContext().push(false);
                    control.proceed();
                }
            });
        }
    }

    public static class Add implements Function<FunctionContext> {

        private final DispatchAsync dispatcher;
        private final Role role;

        Add(final DispatchAsync dispatcher, final Role role) {
            this.dispatcher = dispatcher;
            this.role = role;
        }

        @Override
        public void execute(final Control<FunctionContext> control) {
            boolean roleExists = Boolean.valueOf(control.getContext().pop().toString());
            if (roleExists) {
                control.proceed();
            } else {
                ModelNode node = ModelHelper.roleMapping(role);
                node.get(OP).set(ADD);
                dispatcher.execute(new DMRAction(node), new FunctionCallback(control));
            }
        }
    }

    public static class IncludeAll implements Function<FunctionContext> {

        private final DispatchAsync dispatcher;
        private final Role role;

        public IncludeAll(final DispatchAsync dispatcher, final Role role) {
            this.dispatcher = dispatcher;
            this.role = role;
        }

        @Override
        public void execute(final Control<FunctionContext> control) {
            ModelNode node = ModelHelper.roleMapping(role);
            node.get(NAME).set("include-all");
            node.get(VALUE).set(role.isIncludeAll());
            node.get(OP).set(WRITE_ATTRIBUTE_OPERATION);
            dispatcher.execute(new DMRAction(node), new FunctionCallback(control));
        }
    }

    public static class Remove implements Function<FunctionContext> {

        private final DispatchAsync dispatcher;
        private final RoleAssignment assignment;

        public Remove(final DispatchAsync dispatcher, final RoleAssignment assignment) {
            this.dispatcher = dispatcher;
            this.assignment = assignment;
        }

        @Override
        public void execute(final Control<FunctionContext> control) {
            ModelNode node = new ModelNode();
            node.get(ADDRESS).setEmptyList();
            node.get(OP).set(COMPOSITE);
            List<ModelNode> steps = new LinkedList<ModelNode>();

            for (Role role : assignment.getRoles()) {
                ModelNode step = ModelHelper.includeExclude(role, assignment.getPrincipal(), "include");
                step.get(OP).set(REMOVE);
                steps.add(step);
            }
            for (Role exclude : assignment.getExcludes()) {
                ModelNode step = ModelHelper.includeExclude(exclude, assignment.getPrincipal(), "exclude");
                step.get(OP).set(REMOVE);
                steps.add(step);
            }

            node.get(STEPS).set(steps);
            dispatcher.execute(new DMRAction(node), new FunctionCallback(control));
        }
    }

    public static class Find implements Function<FunctionContext> {

        private final DispatchAsync dispatcher;
        private final String name;

        public Find(final DispatchAsync dispatcher) {
            this(dispatcher, null);
        }

        public Find(final DispatchAsync dispatcher, final String name) {
            this.dispatcher = dispatcher;
            this.name = name;
        }

        @Override
        public void execute(final Control<FunctionContext> control) {
            final List<String> matchingRoles = new LinkedList<String>();
            control.getContext().push(matchingRoles);

            ModelNode node = new ModelNode();
            node.get(ADDRESS).add("core-service", "management");
            node.get(ADDRESS).add("access", "authorization");
            node.get(OP).set(READ_CHILDREN_RESOURCES_OPERATION);
            node.get(CHILD_TYPE).set("role-mapping");
            node.get("recursive-depth").set("2");
            dispatcher.execute(new DMRAction(node), new FunctionCallback(control) {

                @Override
                protected void onSuccess(final ModelNode result) {
                    List<ModelNode> roleMappings = result.get(RESULT).asList();
                    for (ModelNode node : roleMappings) {
                        Property property = node.asProperty();
                        String roleName = property.getName();

                        if (name != null) {
                            if (name.equals(roleName)) {
                                matchingRoles.add(roleName);
                                break;
                            }
                        } else {
                            // check if the role is empty and does not have include-all=true
                            boolean match = true;
                            ModelNode assignmentNode = property.getValue();
                            if (assignmentNode.hasDefined("include-all")) {
                                match = !assignmentNode.get("include-all").asBoolean();
                            }
                            if (match) {
                                if (assignmentNode.hasDefined("include")) {
                                    List<Property> inclusions = assignmentNode.get("include").asPropertyList();
                                    match = inclusions.isEmpty();
                                }
                                if (match) {
                                    if (assignmentNode.hasDefined("exclude")) {
                                        List<Property> exclusions = assignmentNode.get("exclude").asPropertyList();
                                        match = exclusions.isEmpty();
                                    }
                                }
                            }
                            if (match) {
                                matchingRoles.add(roleName);
                            }
                        }
                    }
                }
            });
        }
    }

    public static class RemoveMatching implements Function<FunctionContext> {

        private final DispatchAsync dispatcher;

        public RemoveMatching(final DispatchAsync dispatcher) {this.dispatcher = dispatcher;}

        @Override
        @SuppressWarnings("unchecked")
        public void execute(final Control<FunctionContext> control) {
            List<String> roles = control.getContext().pop();
            if (roles.isEmpty()) {
                control.proceed();
            } else {
                ModelNode comp = new ModelNode();
                comp.get(ADDRESS).setEmptyList();
                comp.get(OP).set(COMPOSITE);
                List<ModelNode> steps = new LinkedList<ModelNode>();
                for (String role : roles) {
                    ModelNode node = new ModelNode();
                    node.get(ADDRESS).add("core-service", "management");
                    node.get(ADDRESS).add("access", "authorization");
                    node.get(ADDRESS).add("role-mapping", role);
                    node.get(OP).set(ModelDescriptionConstants.REMOVE);
                    steps.add(node);
                }
                comp.get(STEPS).set(steps);
                dispatcher.execute(new DMRAction(comp), new FunctionCallback(control));
            }
        }
    }
}
