package de.renew.unify;

import java.util.HashMap;
import java.util.Map;

/**
 * This class is used to copy objects of type {@link Unifiable}.
 */
public class Copier implements ICopier {
    private final Map<IdentityWrapper, IdentityWrapper> _converted;

    /**
     * Constructor for the class Copier.
     * Initializes the data structure that is used to store the copies of the objects.
     */
    public Copier() {
        _converted = new HashMap<>();
    }

    @Override
    public Object copy(Object obj) {
        if (obj instanceof Unifiable) {
            if (obj instanceof Variable variable) {
                return new Variable(copy(variable.getValue()), null);
            } else if (obj instanceof SilentlyUnifiable silentlyUnifiable) {
                return silentlyUnifiable.copy(this);
            } else if (obj instanceof Tuple tuple) {
                return tuple.copy(this);
            } else if (obj instanceof List list) {
                return list.copy(this);
            } else if (obj instanceof Unknown) {
                return copyUnknown(obj);
            } else if (obj instanceof Calculator calculator) {
                // Calculators are replaced by unknowns.
                // If another calculation is to be registered,
                // it must be done by hand. In Renew the calculation
                // will be actually initiated.
                return copyUnknown(calculator);
            } else {
                throw new RuntimeException("An unknown subclass of Unifiable occurred.");
            }
        } else {
            return obj;
        }
    }

    /**
     * This method copies an {@link Unknown} object from the internal hashtable.
     * If the object is not found in the hashtable, a new {@link Unknown} object
     * is created and added to the hashtable.
     * <p>
     * This method might be called with an unknown or with
     * a calculator as an argument.
     *
     * @param obj the object to be copied
     * @return the copied {@link Unknown} object or a new unknown object
     *         if obj is not found in the hashtable
     */
    private Unknown copyUnknown(Object obj) {
        IdentityWrapper wrappedKey = new IdentityWrapper(obj);
        if (_converted.containsKey(wrappedKey)) {
            IdentityWrapper wrappedUnk = _converted.get(wrappedKey);
            return (Unknown) wrappedUnk.getObject();
        } else {
            Unknown newUnknown = new Unknown();
            _converted.put(wrappedKey, new IdentityWrapper(newUnknown));
            return newUnknown;
        }
    }
}