/*
 * Copyright 2014 Open Networking Laboratory
 *
 * 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.onlab.onos.store.mastership.impl;

import static org.onlab.onos.net.MastershipRole.MASTER;
import static org.onlab.onos.net.MastershipRole.NONE;
import static org.onlab.onos.net.MastershipRole.STANDBY;

import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.cluster.RoleInfo;
import org.onlab.onos.net.MastershipRole;

import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;

/**
 * A structure that holds node mastership roles associated with a
 * {@link org.onlab.onos.net.DeviceId}. This structure needs to be locked through IMap.
 */
final class RoleValue {

    protected final Map<MastershipRole, List<NodeId>> value = new EnumMap<>(MastershipRole.class);

    public RoleValue() {
        value.put(MastershipRole.MASTER, new LinkedList<NodeId>());
        value.put(MastershipRole.STANDBY, new LinkedList<NodeId>());
        value.put(MastershipRole.NONE, new LinkedList<NodeId>());
    }

    // exposing internals for serialization purpose only
    Map<MastershipRole, List<NodeId>> value() {
        return Collections.unmodifiableMap(value);
    }

    public List<NodeId> nodesOfRole(MastershipRole type) {
        return value.get(type);
    }

    /**
     * Returns the first node to match the MastershipRole, or if there
     * are none, null.
     *
     * @param type the role
     * @return a node ID or null
     */
    public NodeId get(MastershipRole type) {
        return value.get(type).isEmpty() ? null : value.get(type).get(0);
    }

    public boolean contains(MastershipRole type, NodeId nodeId) {
        return value.get(type).contains(nodeId);
    }

    public MastershipRole getRole(NodeId nodeId) {
        if (contains(MASTER, nodeId)) {
            return MASTER;
        }
        if (contains(STANDBY, nodeId)) {
            return STANDBY;
        }
        return NONE;
    }

    /**
     * Associates a node to a certain role.
     *
     * @param type the role
     * @param nodeId the node ID of the node to associate
     * @return true if modified
     */
    public boolean add(MastershipRole type, NodeId nodeId) {
        List<NodeId> nodes = value.get(type);

        if (!nodes.contains(nodeId)) {
            return nodes.add(nodeId);
        }
        return false;
    }

    /**
     * Removes a node from a certain role.
     *
     * @param type the role
     * @param nodeId the ID of the node to remove
     * @return true if modified
     */
    public boolean remove(MastershipRole type, NodeId nodeId) {
        List<NodeId> nodes = value.get(type);
        if (!nodes.isEmpty()) {
            return nodes.remove(nodeId);
        } else {
            return false;
        }
    }

    /**
     * Reassigns a node from one role to another. If the node was not of the
     * old role, it will still be assigned the new role.
     *
     * @param nodeId the Node ID of node changing roles
     * @param from the old role
     * @param to the new role
     * @return true if modified
     */
    public boolean reassign(NodeId nodeId, MastershipRole from, MastershipRole to) {
        boolean modified = remove(from, nodeId);
        modified |= add(to, nodeId);
        return modified;
    }

    /**
     * Replaces a node in one role with another node. Even if there is no node to
     * replace, the new node is associated to the role.
     *
     * @param from the old NodeId to replace
     * @param to the new NodeId
     * @param type the role associated with the old NodeId
     * @return true if modified
     */
    public boolean replace(NodeId from, NodeId to, MastershipRole type) {
        boolean modified = remove(type, from);
        modified |= add(type, to);
        return modified;
    }

    /**
     * Summarizes this RoleValue as a RoleInfo. Note that master and/or backups
     * may be empty, so the values should be checked for safety.
     *
     * @return the RoleInfo.
     */
    public RoleInfo roleInfo() {
        return new RoleInfo(
                get(MastershipRole.MASTER), nodesOfRole(MastershipRole.STANDBY));
    }

    @Override
    public String toString() {
        ToStringHelper helper = MoreObjects.toStringHelper(this.getClass());
        for (Map.Entry<MastershipRole, List<NodeId>> el : value.entrySet()) {
            helper.add(el.getKey().toString(), el.getValue());
        }
        return helper.toString();
    }
}

@@ -1170,16 +1170,56 @@
 gHelper;
+%0Aimport com.google.common.collect.Lists;
 %0A%0A/**%0A *
@@ -1495,24 +1495,75 @@
 le.class);%0A%0A
+    /**%0A     * Constructs empty RoleValue.%0A     */%0A
     public R
@@ -1704,32 +1704,32 @@
 ist%3CNodeId%3E());%0A
-
         value.pu
@@ -1781,24 +1781,428 @@
 ());%0A    %7D%0A%0A
+    /**%0A     * Constructs copy of specified RoleValue.%0A     *%0A     * @param original original to create copy from%0A     */%0A    public RoleValue(final RoleValue original) %7B%0A        value.put(MASTER, Lists.newLinkedList(original.value.get(MASTER)));%0A        value.put(STANDBY, Lists.newLinkedList(original.value.get(STANDBY)));%0A        value.put(NONE, Lists.newLinkedList(original.value.get(NONE)));%0A    %7D%0A%0A
     // expos

Add copy constructor