// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.apache.cloudstack.acl;

import org.apache.commons.lang3.StringUtils;

import com.cloud.user.Account;
import com.google.common.base.Enums;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

// Enum for default roles in CloudStack
public enum RoleType {
    Admin(1L, Account.Type.ADMIN, 1),
    ResourceAdmin(2L, Account.Type.RESOURCE_DOMAIN_ADMIN, 2),
    DomainAdmin(3L, Account.Type.DOMAIN_ADMIN, 4),
    User(4L, Account.Type.NORMAL, 8),
    Unknown(-1L,  Account.Type.UNKNOWN, 0);

    private long id;
    private Account.Type accountType;
    private int mask;

    private static Logger LOGGER = LogManager.getLogger(RoleType.class.getName());
    private static Map<Account.Type, RoleType> ACCOUNT_TYPE_MAP = new HashMap<>();

    static {
        for (RoleType t: RoleType.values()) {
            ACCOUNT_TYPE_MAP.put(t.getAccountType(),t);
        }
    }

    RoleType(final long id, final Account.Type accountType, final int mask) {
        this.id = id;
        this.accountType = accountType;
        this.mask = mask;
    }

    public long getId() {
        return id;
    }

    public Account.Type getAccountType() {
        return accountType;
    }

    public int getMask() {
        return mask;
    }

    public static RoleType fromString(final String name) {
        if (StringUtils.isNotEmpty(name)
                && Enums.getIfPresent(RoleType.class, name).isPresent()) {
            return RoleType.valueOf(name);
        }
        throw new IllegalStateException("Illegal RoleType name provided");
    }

    public static RoleType fromMask(int mask) {
        for (RoleType roleType : RoleType.values()) {
            if (roleType.getMask() == mask) {
                return roleType;
            }
        }
        return Unknown;
    }

    public static RoleType getByAccountType(final Account.Type accountType) {
        RoleType t = ACCOUNT_TYPE_MAP.get(accountType);
        if (t == null) {
            return RoleType.Unknown;
        }
        return t;
    }

    public static Long getRoleByAccountType(final Long roleId, final Account.Type accountType) {
        if (roleId == null && accountType != null) {
            RoleType defaultRoleType = RoleType.getByAccountType(accountType);
            if (defaultRoleType != null && defaultRoleType != RoleType.Unknown) {
                return defaultRoleType.getId();
            }
        }
        return roleId;
    }

    public static int toCombinedMask(Collection<RoleType> roles) {
        int combinedMask = 0;
        if (roles != null) {
            for (RoleType role : roles) {
                combinedMask |= role.getMask();
            }
        }
        return combinedMask;
    }

    public static Set<RoleType> fromCombinedMask(int combinedMask) {
        Set<RoleType> roles = EnumSet.noneOf(RoleType.class);
        for (RoleType roleType : RoleType.values()) {
            if ((combinedMask & roleType.getMask()) != 0) {
                roles.add(roleType);
            }
        }
        if (roles.isEmpty()) {
            roles.add(Unknown);
        }
        return roles;
    }


    /**
     * This method returns the role account type if the role isn't null, else it returns the default account type.
     * */
    public static Account.Type getAccountTypeByRole(final Role role, final Account.Type defautAccountType) {
        if (role != null) {
            LOGGER.debug(String.format("Role [%s] is not null; therefore, we use its account type [%s].", role, defautAccountType));
            return role.getRoleType().getAccountType();
        }
        LOGGER.debug(String.format("Role is null; therefore, we use the default account type [%s] value.", defautAccountType));
        return defautAccountType;
    }
}
