package org.zstack.compute.allocator;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.header.allocator.AbstractHostAllocatorFlow;
import org.zstack.header.host.HostVO;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;

import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.*;

/**
 * This flow returns a list of host candidates sorted by the number of their VMs.
 */
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class LeastVmPreferredAllocatorFlow extends AbstractHostAllocatorFlow {
    private static final CLogger logger = Utils.getLogger(LeastVmPreferredAllocatorFlow.class);
    @Autowired
    private DatabaseFacade dbf;

    @Transactional(readOnly = true)
    private List<Tuple> findLeastVmHost(List<String> huuids) {
        String sql = "select count(vm) as cnt, host.uuid" +
                " from HostVO host" +
                " Left Join VmInstanceVO vm on host.uuid = vm.hostUuid" +
                " and host.uuid in (:huuids)" +
                " group by host.uuid order by cnt";
        TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
        q.setParameter("huuids", huuids);
        return q.getResultList();
    }

    @Override
    public void allocate() {
        throwExceptionIfIAmTheFirstFlow();

        if (spec.isListAllHosts()) {
            next(candidates);
            return;
        }

        List<String> huuids = getHostUuidsFromCandidates();
        List<Tuple> tuples = findLeastVmHost(huuids);

        // no VM running on any candidate host(s)
        if (tuples.isEmpty()) {
            next(candidates);
            return;
        }

        List<HostVO> sorted = new ArrayList<>(candidates.size());

        Map<String, HostVO> dict = new HashMap<>();
        for (HostVO hvo: candidates) {
            dict.put(hvo.getUuid(), hvo);
        }

        if (huuids.size() > tuples.size()) {
            HashSet<String> hostsWithVMs = new HashSet<>();
            for (Tuple t : tuples) {
                String hostUuid = t.get(1, String.class);
                hostsWithVMs.add(hostUuid);
            }

            for (String huuid : huuids) {
                if (!hostsWithVMs.contains(huuid)) {
                    sorted.add(dict.get(huuid));
                }
            }
        }

        // Note: the query result is ordered.
        for (Tuple t : tuples) {
            String hostUuid = t.get(1, String.class);
            sorted.add(dict.get(hostUuid));
        }

        next(sorted);
    }
}

@@ -1262,19 +1262,21 @@
       %22 
-and
+where
  host.uu

fix bug LeastVmPreferredAllocatorFlow#findLeastVmHost