/*_############################################################################
  _## 
  _##  SNMP4J - VariantVariable.java  
  _## 
  _##  Copyright (C) 2003-2026  Frank Fock (SNMP4J.org)
  _##  
  _##  Licensed 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 org.snmp4j.smi;

import org.snmp4j.asn1.BERInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * The {@code VariantVariable} provides a decorator for any type
 * of Variable instance, to be able to intercept or monitor variable
 * value modification by using a {@link VariantVariableCallback}.
 * <p>
 * This class will work for implementations that use {@link #getSyntax()}
 * method to determine the variables syntax. However "instanceof" will not
 * work.
 * <p>
 * In contrast to the native {@code Variable} implementations,
 * {@code VariantVariable} can be modified dynamically (i.e. while
 * a PDU is being BER encoded where this variable has been added to) without
 * causing BER encoding errors.
 *
 * @author Frank Fock
 * @version 1.8
 * @since 1.7
 */
public class VariantVariable extends AbstractVariable implements
        AssignableFromInteger,
        AssignableFromLong,
        AssignableFromString,
        AssignableFromByteArray {

    private static final long serialVersionUID = -3678564678835871188L;

    private final Variable variable;
    private transient VariantVariableCallback callback;

    /**
     * Creates a variant variable wrapping the specified value.
     * @param initialVariable
     *    a {@code Variable}.
     */
    public VariantVariable(Variable initialVariable) {
        if (initialVariable == null) {
            throw new NullPointerException();
        }
        this.variable = initialVariable;
    }

    /**
     * Creates a variant variable wrapping the specified value and a callback
     * that monitors value modifications.
     * @param initialVariable
     *    a {@code Variable}.
     * @param callback
     *    a callback handler that is called before the value is to be modified
     *    and after it has been modified.
     */
    public VariantVariable(Variable initialVariable,
                           VariantVariableCallback callback) {
        this(initialVariable);
        this.callback = callback;
    }

    public synchronized int compareTo(Variable o) {
        updateVariable();
        return variable.compareTo(o);
    }

    protected void updateVariable() {
        if (callback != null) {
            callback.updateVariable(this);
        }
    }

    protected void variableUpdated() {
        if (callback != null) {
            callback.variableUpdated(this);
        }
    }

    public synchronized void decodeBER(BERInputStream inputStream) throws IOException {
        variable.decodeBER(inputStream);
        variableUpdated();
    }

    public synchronized void encodeBER(OutputStream outputStream) throws IOException {
        updateVariable();
        variable.encodeBER(outputStream);
    }

    public synchronized void fromSubIndex(OID subIndex, boolean impliedLength) {
        variable.fromSubIndex(subIndex, impliedLength);
        variableUpdated();
    }

    public synchronized int getBERLength() {
        updateVariable();
        return variable.getBERLength();
    }

    public int getSyntax() {
        return variable.getSyntax();
    }

    public synchronized int toInt() {
        updateVariable();
        return variable.toInt();
    }

    public synchronized long toLong() {
        updateVariable();
        return variable.toLong();
    }

    public synchronized byte[] toByteArray() {
        updateVariable();
        if (variable instanceof AssignableFromByteArray) {
            return ((AssignableFromByteArray)variable).toByteArray();
        }
        throw new UnsupportedOperationException();
    }

    public synchronized OID toSubIndex(boolean impliedLength) {
        updateVariable();
        return variable.toSubIndex(impliedLength);
    }

    public synchronized boolean equals(Object o) {
        updateVariable();
        return variable.equals(o);
    }

    public synchronized int hashCode() {
        updateVariable();
        return variable.hashCode();
    }

    public synchronized String toString() {
        updateVariable();
        return variable.toString();
    }

    public Object clone() {
        updateVariable();
        return new VariantVariable((Variable)variable.clone());
    }

    public synchronized void setValue(int value) {
        if (variable instanceof AssignableFromInteger) {
            ((AssignableFromInteger)variable).setValue(value);
        }
        else {
            throw new ClassCastException("An integer value cannot be assigned to "+
                    variable);
        }
    }

    public synchronized void setValue(long value) {
        if (variable instanceof AssignableFromLong) {
            ((AssignableFromLong)variable).setValue(value);
        }
        else {
            throw new ClassCastException("A long value cannot be assigned to "+
                    variable);
        }
    }

    public synchronized void setValue(OctetString value) {
        if (variable instanceof AssignableFromByteArray) {
            ((AssignableFromByteArray)variable).setValue(value.getValue());
        }
        else {
            throw new ClassCastException("An OctetString value cannot be assigned to "+
                    variable);
        }
    }

    public synchronized void setValue(byte[] value) {
        if (variable instanceof AssignableFromByteArray) {
            ((AssignableFromByteArray)variable).setValue(value);
        }
        else {
            throw new ClassCastException("A byte array value cannot be assigned to "+
                    variable);
        }
    }

    public synchronized void setValue(String value) {
        if (variable instanceof AssignableFromString) {
            ((AssignableFromString)variable).setValue(value);
        }
        else {
            throw new ClassCastException("A string value cannot be assigned to "+
                    variable);
        }
    }

    /**
     * Returns the typed variable that holds the wrapped value.
     * @return
     *    a Variable instance.
     */
    public Variable getVariable() {
        return variable;
    }

    public boolean isDynamic() {
        return true;
    }

}
