/**
 * Copyright (c) André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.compiler.assembler;

import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * 
 */
public final class VariablesSnapshot implements Iterable<Variable<?>> {
    private final int startSlot;
    private final BitSet variables;
    private final BitSet active;
    private final Type[] types;

    VariablesSnapshot(int startSlot, BitSet variables, BitSet active, Type[] types) {
        this.startSlot = startSlot;
        this.variables = variables;
        this.active = active;
        this.types = types;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj == null || obj.getClass() != VariablesSnapshot.class) {
            return false;
        }
        VariablesSnapshot other = (VariablesSnapshot) obj;
        return startSlot == other.startSlot && variables.equals(other.variables) && active.equals(other.active)
                && Arrays.equals(types, other.types);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + startSlot;
        result = prime * result + variables.hashCode();
        result = prime * result + active.hashCode();
        result = prime * result + Arrays.hashCode(types);
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append("\n\tstartSlot=").append(startSlot);
        sb.append("\n\tvariables=").append(variables);
        sb.append("\n\tactive=").append(active);
        sb.append("\n\ttypes=").append(Arrays.toString(types));
        return sb.toString();
    }

    int getStartSlot() {
        return startSlot;
    }

    BitSet getVariables() {
        return variables;
    }

    BitSet getActive() {
        return active;
    }

    Type[] getTypes() {
        return types;
    }

    private Variable<?> getVariable(int slot) {
        assert 0 <= slot && slot <= types.length : String.format("slot=%d not in [%d, %d]", slot, 0, types.length);
        assert variables.get(slot) && types[slot] != null && types[slot] != Type.RESERVED : String
                .format("slot=%d, used=%b, type=%s", slot, variables.get(slot), types[slot]);
        return new Variable<>(null, types[slot], slot);
    }

    private int getNextInitializedSlot(int slot) {
        assert 0 <= slot && slot <= types.length : String.format("slot=%d not in [%d, %d]", slot, 0, types.length);
        assert variables.get(slot) && active.get(slot) && types[slot] != null && types[slot] != Type.RESERVED : String
                .format("slot=%d, used=%b, active=%b, type=%s", slot, variables.get(slot), active.get(slot),
                        types[slot]);
        return active.nextSetBit(slot + types[slot].getSize());
    }

    /**
     * Returns the number of initialized variables.
     * 
     * @return the total variable count
     */
    public int getSize() {
        return active.cardinality();
    }

    @Override
    public Iterator<Variable<?>> iterator() {
        return new SnapshotIterator();
    }

    private final class SnapshotIterator implements Iterator<Variable<?>> {
        private int slot = active.nextSetBit(startSlot);

        @Override
        public boolean hasNext() {
            return slot != -1;
        }

        @Override
        public Variable<?> next() {
            if (slot == -1) {
                throw new NoSuchElementException();
            }
            Variable<?> v = getVariable(slot);
            slot = getNextInitializedSlot(slot);
            return v;
        }
    }
}
