// 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 com.cloud.api.query.dao;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import javax.inject.Inject;

import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.springframework.stereotype.Component;

import org.apache.cloudstack.api.ApiConstants.DomainDetails;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ResourceLimitAndCountResponse;
import org.apache.cloudstack.api.response.UserResponse;

import com.cloud.api.ApiDBUtils;
import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.ViewResponseHelper;
import com.cloud.api.query.vo.AccountJoinVO;
import com.cloud.api.query.vo.UserAccountJoinVO;
import com.cloud.configuration.Resource;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;

@Component
public class AccountJoinDaoImpl extends GenericDaoBase<AccountJoinVO, Long> implements AccountJoinDao {

    @Inject
    private ConfigurationDao configDao;
    private final SearchBuilder<AccountJoinVO> acctIdSearch;
    private final SearchBuilder<AccountJoinVO> domainSearch;
    @Inject
    AccountManager _acctMgr;

    protected AccountJoinDaoImpl() {

        domainSearch = createSearchBuilder();
        domainSearch.and("idIN", domainSearch.entity().getId(), SearchCriteria.Op.IN);
        domainSearch.done();

        acctIdSearch = createSearchBuilder();
        acctIdSearch.and("id", acctIdSearch.entity().getId(), SearchCriteria.Op.EQ);
        acctIdSearch.done();

        _count = "select count(distinct id) from account_view WHERE ";
    }

    @Override
    public AccountResponse newAccountResponse(ResponseView view, EnumSet<DomainDetails> details, AccountJoinVO account) {
        AccountResponse accountResponse = new AccountResponse();
        accountResponse.setId(account.getUuid());
        accountResponse.setName(account.getAccountName());
        accountResponse.setAccountType(account.getType().ordinal());
        accountResponse.setDomainId(account.getDomainUuid());
        accountResponse.setDomainName(account.getDomainName());
        accountResponse.setDomainPath(ApiResponseHelper.getPrettyDomainPath(account.getDomainPath()));
        accountResponse.setState(account.getState().toString());
        accountResponse.setCreated(account.getCreated());
        accountResponse.setNetworkDomain(account.getNetworkDomain());
        accountResponse.setDefaultZone(account.getDataCenterUuid());
        accountResponse.setIsDefault(account.isDefault());
        if (view == ResponseView.Full) {
            accountResponse.setApiKeyAccess(account.getApiKeyAccess());
        }

        // get network stat
        accountResponse.setBytesReceived(account.getBytesReceived());
        accountResponse.setBytesSent(account.getBytesSent());

        if (details.contains(DomainDetails.all) || details.contains(DomainDetails.resource)) {
            boolean fullView = (view == ResponseView.Full && _acctMgr.isRootAdmin(account.getId()));
            setResourceLimits(account, fullView, accountResponse);

            //get resource limits for projects
            long projectLimit = ApiDBUtils.findCorrectResourceLimit(account.getProjectLimit(), account.getId(), ResourceType.project);
            String projectLimitDisplay = (fullView || projectLimit == -1) ? Resource.UNLIMITED : String.valueOf(projectLimit);
            long projectTotal = (account.getProjectTotal() == null) ? 0 : account.getProjectTotal();
            String projectAvail = (fullView || projectLimit == -1) ? Resource.UNLIMITED : String.valueOf(projectLimit - projectTotal);
            accountResponse.setProjectLimit(projectLimitDisplay);
            accountResponse.setProjectTotal(projectTotal);
            accountResponse.setProjectAvailable(projectAvail);
        }

        // set async job
        if (account.getJobId() != null) {
            accountResponse.setJobId(account.getJobUuid());
            accountResponse.setJobStatus(account.getJobStatus());
        }

        // adding all the users for an account as part of the response obj
        List<UserAccountJoinVO> usersForAccount = ApiDBUtils.findUserViewByAccountId(account.getId());
        List<UserResponse> userResponses = ViewResponseHelper.createUserResponse(usersForAccount.toArray(new UserAccountJoinVO[usersForAccount.size()]));
        accountResponse.setUsers(userResponses);

        // set details
        accountResponse.setDetails(ApiDBUtils.getAccountDetails(account.getId()));
        accountResponse.setObjectName("account");

        // add all the acl groups for an account
        accountResponse.setGroups(_acctMgr.listAclGroupsByAccount(account.getId()));

        return accountResponse;
    }

    @Override
    public void setResourceLimits(AccountJoinVO account, boolean fullView, ResourceLimitAndCountResponse response) {
        // Get resource limits and counts
        long vmLimit = ApiDBUtils.findCorrectResourceLimit(account.getVmLimit(), account.getId(), ResourceType.user_vm);
        String vmLimitDisplay = (fullView || vmLimit == -1) ? Resource.UNLIMITED : String.valueOf(vmLimit);
        long vmTotal = (account.getVmTotal() == null) ? 0 : account.getVmTotal();
        String vmAvail = (fullView || vmLimit == -1) ? Resource.UNLIMITED : String.valueOf(vmLimit - vmTotal);
        response.setVmLimit(vmLimitDisplay);
        response.setVmTotal(vmTotal);
        response.setVmAvailable(vmAvail);

        long ipLimit = ApiDBUtils.findCorrectResourceLimit(account.getIpLimit(), account.getId(), ResourceType.public_ip);
        String ipLimitDisplay = (fullView || ipLimit == -1) ? Resource.UNLIMITED : String.valueOf(ipLimit);
        long ipTotal = (account.getIpTotal() == null) ? 0 : account.getIpTotal();

        Long ips = ipLimit - ipTotal;
        // check how many free ips are left, and if it's less than max allowed number of ips from account - use this
        // value
        Long ipsLeft = account.getIpFree();
        boolean unlimited = true;
        if (ips.longValue() > ipsLeft.longValue()) {
            ips = ipsLeft;
            unlimited = false;
        }

        String ipAvail = ((fullView || ipLimit == -1) && unlimited) ? Resource.UNLIMITED : String.valueOf(ips);

        response.setIpLimit(ipLimitDisplay);
        response.setIpTotal(ipTotal);
        response.setIpAvailable(ipAvail);

        long volumeLimit = ApiDBUtils.findCorrectResourceLimit(account.getVolumeLimit(), account.getId(), ResourceType.volume);
        String volumeLimitDisplay = (fullView || volumeLimit == -1) ? Resource.UNLIMITED : String.valueOf(volumeLimit);
        long volumeTotal = (account.getVolumeTotal() == null) ? 0 : account.getVolumeTotal();
        String volumeAvail = (fullView || volumeLimit == -1) ? Resource.UNLIMITED : String.valueOf(volumeLimit - volumeTotal);
        response.setVolumeLimit(volumeLimitDisplay);
        response.setVolumeTotal(volumeTotal);
        response.setVolumeAvailable(volumeAvail);

        long snapshotLimit = ApiDBUtils.findCorrectResourceLimit(account.getSnapshotLimit(), account.getId(), ResourceType.snapshot);
        String snapshotLimitDisplay = (fullView || snapshotLimit == -1) ? Resource.UNLIMITED : String.valueOf(snapshotLimit);
        long snapshotTotal = (account.getSnapshotTotal() == null) ? 0 : account.getSnapshotTotal();
        String snapshotAvail = (fullView || snapshotLimit == -1) ? Resource.UNLIMITED : String.valueOf(snapshotLimit - snapshotTotal);
        response.setSnapshotLimit(snapshotLimitDisplay);
        response.setSnapshotTotal(snapshotTotal);
        response.setSnapshotAvailable(snapshotAvail);

        Long templateLimit = ApiDBUtils.findCorrectResourceLimit(account.getTemplateLimit(), account.getId(), ResourceType.template);
        String templateLimitDisplay = (fullView || templateLimit == -1) ? Resource.UNLIMITED : String.valueOf(templateLimit);
        Long templateTotal = (account.getTemplateTotal() == null) ? 0 : account.getTemplateTotal();
        String templateAvail = (fullView || templateLimit == -1) ? Resource.UNLIMITED : String.valueOf(templateLimit - templateTotal);
        response.setTemplateLimit(templateLimitDisplay);
        response.setTemplateTotal(templateTotal);
        response.setTemplateAvailable(templateAvail);

        // Get stopped and running VMs
        response.setVmStopped(account.getVmStopped()!=null ? account.getVmStopped() : 0);
        response.setVmRunning(account.getVmRunning()!=null ? account.getVmRunning() : 0);

        //get resource limits for networks
        long networkLimit = ApiDBUtils.findCorrectResourceLimit(account.getNetworkLimit(), account.getId(), ResourceType.network);
        String networkLimitDisplay = (fullView || networkLimit == -1) ? Resource.UNLIMITED : String.valueOf(networkLimit);
        long networkTotal = (account.getNetworkTotal() == null) ? 0 : account.getNetworkTotal();
        String networkAvail = (fullView || networkLimit == -1) ? Resource.UNLIMITED : String.valueOf(networkLimit - networkTotal);
        response.setNetworkLimit(networkLimitDisplay);
        response.setNetworkTotal(networkTotal);
        response.setNetworkAvailable(networkAvail);

        //get resource limits for vpcs
        long vpcLimit = ApiDBUtils.findCorrectResourceLimit(account.getVpcLimit(), account.getId(), ResourceType.vpc);
        String vpcLimitDisplay = (fullView || vpcLimit == -1) ? Resource.UNLIMITED : String.valueOf(vpcLimit);
        long vpcTotal = (account.getVpcTotal() == null) ? 0 : account.getVpcTotal();
        String vpcAvail = (fullView || vpcLimit == -1) ? Resource.UNLIMITED : String.valueOf(vpcLimit - vpcTotal);
        response.setVpcLimit(vpcLimitDisplay);
        response.setVpcTotal(vpcTotal);
        response.setVpcAvailable(vpcAvail);

        //get resource limits for cpu cores
        long cpuLimit = ApiDBUtils.findCorrectResourceLimit(account.getCpuLimit(), account.getId(), ResourceType.cpu);
        String cpuLimitDisplay = (fullView || cpuLimit == -1) ? Resource.UNLIMITED : String.valueOf(cpuLimit);
        long cpuTotal = (account.getCpuTotal() == null) ? 0 : account.getCpuTotal();
        String cpuAvail = (fullView || cpuLimit == -1) ? Resource.UNLIMITED : String.valueOf(cpuLimit - cpuTotal);
        response.setCpuLimit(cpuLimitDisplay);
        response.setCpuTotal(cpuTotal);
        response.setCpuAvailable(cpuAvail);

        //get resource limits for memory
        long memoryLimit = ApiDBUtils.findCorrectResourceLimit(account.getMemoryLimit(), account.getId(), ResourceType.memory);
        String memoryLimitDisplay = (fullView || memoryLimit == -1) ? Resource.UNLIMITED : String.valueOf(memoryLimit);
        long memoryTotal = (account.getMemoryTotal() == null) ? 0 : account.getMemoryTotal();
        String memoryAvail = (fullView || memoryLimit == -1) ? Resource.UNLIMITED : String.valueOf(memoryLimit - memoryTotal);
        response.setMemoryLimit(memoryLimitDisplay);
        response.setMemoryTotal(memoryTotal);
        response.setMemoryAvailable(memoryAvail);

        //get resource limits for gpus
        setGpuResourceLimits(account, fullView,response);

        //get resource limits for primary storage space and convert it from Bytes to GiB
        long primaryStorageLimit = ApiDBUtils.findCorrectResourceLimit(account.getPrimaryStorageLimit(), account.getId(), ResourceType.primary_storage);
        String primaryStorageLimitDisplay = (fullView || primaryStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf(primaryStorageLimit / ResourceType.bytesToGiB);
        long primaryStorageTotal = (account.getPrimaryStorageTotal() == null) ? 0 : (account.getPrimaryStorageTotal() / ResourceType.bytesToGiB);
        String primaryStorageAvail = (fullView || primaryStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf((primaryStorageLimit / ResourceType.bytesToGiB) - primaryStorageTotal);

        response.setPrimaryStorageLimit(primaryStorageLimitDisplay);
        response.setPrimaryStorageTotal(primaryStorageTotal);
        response.setPrimaryStorageAvailable(primaryStorageAvail);

        //get resource limits for secondary storage space and convert it from Bytes to GiB
        long secondaryStorageLimit = ApiDBUtils.findCorrectResourceLimit(account.getSecondaryStorageLimit(), account.getId(), ResourceType.secondary_storage);
        String secondaryStorageLimitDisplay = (fullView || secondaryStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf(secondaryStorageLimit / ResourceType.bytesToGiB);
        float secondaryStorageTotal = (account.getSecondaryStorageTotal() == null) ? 0 : (account.getSecondaryStorageTotal() / (ResourceType.bytesToGiB * 1f));
        String secondaryStorageAvail = (fullView || secondaryStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf(( (double)secondaryStorageLimit / ResourceType.bytesToGiB)
                - secondaryStorageTotal);

        response.setSecondaryStorageLimit(secondaryStorageLimitDisplay);
        response.setSecondaryStorageTotal(secondaryStorageTotal);
        response.setSecondaryStorageAvailable(secondaryStorageAvail);

        //get resource limits for backups
        long backupLimit = ApiDBUtils.findCorrectResourceLimit(account.getBackupLimit(), account.getId(), ResourceType.backup);
        String backupLimitDisplay = (fullView || backupLimit == -1) ? Resource.UNLIMITED : String.valueOf(backupLimit);
        long backupTotal = (account.getBackupTotal() == null) ? 0 : account.getBackupTotal();
        String backupAvail = (fullView || backupLimit == -1) ? Resource.UNLIMITED : String.valueOf(backupLimit - backupTotal);
        response.setBackupLimit(backupLimitDisplay);
        response.setBackupTotal(backupTotal);
        response.setBackupAvailable(backupAvail);

        //get resource limits for backup storage space and convert it from Bytes to GiB
        long backupStorageLimit = ApiDBUtils.findCorrectResourceLimit(account.getBackupStorageLimit(), account.getId(), ResourceType.backup_storage);
        String backupStorageLimitDisplay = (fullView || backupStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf(backupStorageLimit / ResourceType.bytesToGiB);
        long backupStorageTotal = (account.getBackupStorageTotal() == null) ? 0 : (account.getBackupStorageTotal() / ResourceType.bytesToGiB);
        String backupStorageAvail = (fullView || backupStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf((backupStorageLimit / ResourceType.bytesToGiB) - backupStorageTotal);
        response.setBackupStorageLimit(backupStorageLimitDisplay);
        response.setBackupStorageTotal(backupStorageTotal);
        response.setBackupStorageAvailable(backupStorageAvail);

        //get resource limits for buckets
        long bucketLimit = ApiDBUtils.findCorrectResourceLimit(account.getBucketLimit(), account.getId(), ResourceType.bucket);
        String bucketLimitDisplay = (fullView || bucketLimit == -1) ? Resource.UNLIMITED : String.valueOf(bucketLimit);
        long bucketTotal = (account.getBucketTotal() == null) ? 0 : account.getBucketTotal();
        String bucketAvail = (fullView || bucketLimit == -1) ? Resource.UNLIMITED : String.valueOf(bucketLimit - bucketTotal);
        response.setBucketLimit(bucketLimitDisplay);
        response.setBucketTotal(bucketTotal);
        response.setBucketAvailable(bucketAvail);

        //get resource limits for object storage space and convert it from Bytes to GiB
        long objectStorageLimit = ApiDBUtils.findCorrectResourceLimit(account.getObjectStorageLimit(), account.getId(), ResourceType.object_storage);
        String objectStorageLimitDisplay = (fullView || objectStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf(objectStorageLimit / ResourceType.bytesToGiB);
        long objectStorageTotal = (account.getObjectStorageTotal() == null) ? 0 : (account.getObjectStorageTotal() / ResourceType.bytesToGiB);
        String objectStorageAvail = (fullView || objectStorageLimit == -1) ? Resource.UNLIMITED : String.valueOf((objectStorageLimit / ResourceType.bytesToGiB) - objectStorageTotal);
        response.setObjectStorageLimit(objectStorageLimitDisplay);
        response.setObjectStorageTotal(objectStorageTotal);
        response.setObjectStorageAvailable(objectStorageAvail);
    }

    private void setGpuResourceLimits(AccountJoinVO account, boolean fullView, ResourceLimitAndCountResponse response) {
        long gpuLimit = ApiDBUtils.findCorrectResourceLimit(account.getGpuLimit(), account.getId(), ResourceType.gpu);
        String gpuLimitDisplay = (fullView || gpuLimit == -1) ? Resource.UNLIMITED : String.valueOf(gpuLimit);
        long gpuTotal = (account.getGpuTotal() == null) ? 0 : account.getGpuTotal();
        String gpuAvail = (fullView || gpuLimit == -1) ? Resource.UNLIMITED : String.valueOf(gpuLimit - gpuTotal);
        response.setGpuLimit(gpuLimitDisplay);
        response.setGpuTotal(gpuTotal);
        response.setGpuAvailable(gpuAvail);
    }

    @Override
    public List<AccountJoinVO> searchByIds(Long... accountIds) {
        // set detail batch query size
        int DETAILS_BATCH_SIZE = 2000;
        String batchCfg = configDao.getValue("detail.batch.query.size");
        if (batchCfg != null) {
            DETAILS_BATCH_SIZE = Integer.parseInt(batchCfg);
        }

        List<AccountJoinVO> uvList = new ArrayList<>();
        // query details by batches
        int curr_index = 0;
        if (accountIds.length > DETAILS_BATCH_SIZE) {
            while ((curr_index + DETAILS_BATCH_SIZE) <= accountIds.length) {
                Long[] ids = new Long[DETAILS_BATCH_SIZE];
                for (int k = 0, j = curr_index; j < curr_index + DETAILS_BATCH_SIZE; j++, k++) {
                    ids[k] = accountIds[j];
                }
                SearchCriteria<AccountJoinVO> sc = domainSearch.create();
                sc.setParameters("idIN", ids);
                List<AccountJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
                if (accounts != null) {
                    uvList.addAll(accounts);
                }
                curr_index += DETAILS_BATCH_SIZE;
            }
        }
        if (curr_index < accountIds.length) {
            int batch_size = (accountIds.length - curr_index);
            // set the ids value
            Long[] ids = new Long[batch_size];
            for (int k = 0, j = curr_index; j < curr_index + batch_size; j++, k++) {
                ids[k] = accountIds[j];
            }
            SearchCriteria<AccountJoinVO> sc = domainSearch.create();
            sc.setParameters("idIN", ids);
            List<AccountJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
            if (accounts != null) {
                uvList.addAll(accounts);
            }
        }
        return uvList;
    }

    @Override
    public AccountJoinVO newAccountView(Account acct) {
        SearchCriteria<AccountJoinVO> sc = acctIdSearch.create();
        sc.setParameters("id", acct.getId());
        List<AccountJoinVO> accounts = searchIncludingRemoved(sc, null, null, false);
        assert accounts != null && accounts.size() == 1 : "No account found for account id " + acct.getId();
        return accounts.get(0);

    }

}
