package org.zstack.network.l2.vxlan.vxlanNetworkPool;

import org.zstack.core.db.SimpleQuery;
import org.zstack.network.l2.vxlan.vxlanNetwork.VxlanNetworkVO;
import org.zstack.network.l2.vxlan.vxlanNetwork.VxlanNetworkVO_;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;

import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 * Created by weiwang on 10/03/2017.
 */
public class RandomVniAllocatorStrategy extends AbstractVniAllocatorStrategy {
    private static final CLogger logger = Utils.getLogger(RandomVniAllocatorStrategy.class);
    public static final VniAllocatorType type = new VniAllocatorType(VxlanNetworkPoolConstant.RANDOM_VNI_ALLOCATOR_STRATEGY);

    @Override
    public VniAllocatorType getType() {
        return type;
    }

    @Override
    public Integer allocateVni(VniAllocateMessage msg) {
        if (msg.getRequiredVni() != null) {
            return allocateRequiredVni(msg);
        }

        SimpleQuery<VniRangeVO> query = dbf.createQuery(VniRangeVO.class);
        query.add(VniRangeVO_.l2NetworkUuid, SimpleQuery.Op.EQ, msg.getL2NetworkUuid());
        List<VniRangeVO> ranges = query.list();

        Collections.shuffle(ranges);

        do {
            Integer vni = null;
            VniRangeVO tr = null;

            for (VniRangeVO r : ranges) {
                vni = allocateVni(r);
                tr = r;
                if (vni != null) {
                    break;
                }
            }

            if (vni == null) {
                    /* No available vniin ranges */
                return null;
            }

            if (vni != null) {
                return vni;
            }
        } while (true);
    }

    private Integer allocateVni(VniRangeVO vo) {
        int total = vo.size();
        Random random = new Random();
        Integer s = random.nextInt(total) + vo.getStartVni();
        Integer e = vo.getEndVni();
        Integer ret = steppingAllocate(s, e, total, vo.getUuid(), vo.getL2NetworkUuid());
        if (ret != null) {
            return ret;
        }

        e = s;
        s = vo.getStartVni();
        return steppingAllocate(s, e, total, vo.getUuid(), vo.getL2NetworkUuid());
    }

    private Integer steppingAllocate(Integer s, Integer e, int total, String rangeUuid, String poolUuid) {
        int step = 254;
        int failureCount = 0;
        int failureCheckPoint = 5;

        while (s < e) {
            // if failing failureCheckPoint times, the range is probably full,
            // we check the range.
            // why don't we check before steppingAllocate()? because in that case we
            // have to count the used IP every time allocating a Vni, and count operation
            // is a full scan in DB, which is very costly
            if (failureCheckPoint == failureCount++) {
                SimpleQuery<VxlanNetworkVO> q = dbf.createQuery(VxlanNetworkVO.class);
                q.add(VxlanNetworkVO_.poolUuid, SimpleQuery.Op.EQ, poolUuid);
                q.add(VxlanNetworkVO_.vni, SimpleQuery.Op.GTE, s);
                q.add(VxlanNetworkVO_.vni, SimpleQuery.Op.LTE, e);
                long count = q.count();
                if (count == total) {
                    logger.debug(String.format("vni range[uuid:%s] has no vni available, try next one", rangeUuid));
                    return null;
                } else {
                    failureCount = 0;
                }
            }

            int te = s + step;
            te = te > e ? e : te;
            SimpleQuery<VxlanNetworkVO> q = dbf.createQuery(VxlanNetworkVO.class);
            q.select(VxlanNetworkVO_.vni);
            q.add(VxlanNetworkVO_.vni, SimpleQuery.Op.GTE, s);
            q.add(VxlanNetworkVO_.vni, SimpleQuery.Op.LTE, te);
            q.add(VxlanNetworkVO_.poolUuid, SimpleQuery.Op.EQ, poolUuid);
            List<Integer> used = q.listValue();
            if (te - s + 1 == used.size()) {
                s += step;
                continue;
            }

            Collections.sort(used);

            return randomAllocateVni(s, te, used);
        }

        return null;
    }

    private static Integer randomAllocateVni(Integer startVni, Integer endVni, List<Integer> allocatedVnis) {
        int total = (endVni - startVni + 1);
        if (total == allocatedVnis.size()) {
            return null;
        }

        BitSet full = new BitSet(total);
        for (Integer alloc : allocatedVnis) {
            full.set(alloc - startVni);
        }

        Random random = new Random();
        int next = random.nextInt(total);
        int a = full.nextClearBit(next);

        if (a >= total) {
            a = full.nextClearBit(0);
        }

        return a + startVni;
    }

}

@@ -2451,16 +2451,97 @@
 t = 5;%0A%0A
+        /*zhanyong.miao s equal e that there is only one vni in the pool/range*/%0A
         
@@ -2550,16 +2550,17 @@
 ile (s %3C
+=
  e) %7B%0A  

resolve ZSTAC-15300: create L2 network fail in case that there is only one vni in the vxlan pool