/*--------------------------------------------------------------*
  Copyright (C) 2006-2015 OpenSim Ltd.

  This file is distributed WITHOUT ANY WARRANTY. See the file
  'License' for details on this and other legal matters.
*--------------------------------------------------------------*/

package org.omnetpp.common.computedstate;

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

import org.omnetpp.common.Debug;


@SuppressWarnings({"unchecked", "rawtypes"})
public class ComputedState<T> {
    private static long currentPulse = 0;

    private long pulse = -1;

    private List<ComputedState> dependsOn = new ArrayList<ComputedState>();

    private IComputation computation;

    private T value;

    public ComputedState() {
        this(null);
    }

    public ComputedState(IComputation computation) {
        this.computation = computation;
    }

    public boolean isValid() {
        if (pulse == -1)
            return false;

        if (computation == null || pulse == currentPulse)
            return true;

        return isValidRecursive(this);
    }

    private boolean isValidRecursive(ComputedState computedState) {
        for (Object o : computedState.dependsOn) {
            ComputedState c = (ComputedState)o;
            if (!c.isValid())
                return false;

            if (c.pulse >= pulse)
                return false;
        }

        return true;
    }

    public T recomputeIfInvalidAs(IComputation computation) {
        this.computation = computation;

        return recomputeIfInvalid();
    }

    public T recomputeIfInvalid() {
        if (!isValid())
           recompute();

        return getValue0();
    }

    public void recompute() {
        if (ComputedContext.hasOwnerOnStack(this))
            throw new RuntimeException("Circularity detected!");

        ComputedContext.pushComputedContextStack(this);
        value = (T)computation.compute();
        pulse = currentPulse;
        dependsOn = ComputedContext.popComputedContextStack().getDependsOn();
    }

    public T getValue() {
        if (computation != null)
            throw new RuntimeException("getValue may be called on constants only");

        return getValue0();
    }

    public void setValue(T value) {
        if (computation != null)
            throw new RuntimeException("setValue may be called on constants only");

        this.value = value;
        this.pulse = ++currentPulse;
        Debug.println("P: " + currentPulse);
    }

    private T getValue0() {
        assert(isValid());
        // TODO: add circularity check, go up on stack and check for owner == this

        if (!ComputedContext.isComputedContextStackEmpty())
            ComputedContext.peekComputedContextStack().addComputedState(this);

        return value;
    }
}
