package org.jboss.as.patching.installation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.jboss.as.patching.Constants;
import org.jboss.as.patching.PatchingException;
import org.jboss.as.patching.logging.PatchLogger;
import org.jboss.as.patching.metadata.LayerType;
import org.jboss.as.patching.runner.PatchUtils;

/**
 * @author Emanuel Muckenhuber
 */
abstract class InstallationModificationImpl extends MutableTargetImpl implements InstallationManager.InstallationModification {

    private final String name;
    private final InstallationState installationState;
    private final List<String> allPatches;
    private final AtomicBoolean done = new AtomicBoolean();

    protected InstallationModificationImpl(final PatchableTarget.TargetInfo identity, final String name,
                                           final String version, final List<String> allPatches,
                                           final InstallationState installationState) {
        super(identity, version);
        this.name = name;
        this.installationState = installationState;
        this.allPatches = new ArrayList<String>(allPatches);
    }

    @Override
    public InstallationManager.MutablePatchingTarget resolve(String name, LayerType type) {
        if (type == LayerType.Layer) {
            return installationState.layers.get(name);
        } else {
            return installationState.addOns.get(name);
        }
    }

    @Override
    public void addInstalledPatch(String patchId) throws PatchingException {
        if (allPatches.contains(patchId)) {
            throw PatchLogger.ROOT_LOGGER.alreadyApplied(patchId);
        }
        allPatches.add(patchId);
    }

    @Override
    public void removeInstalledPatch(String patchId) throws PatchingException {
        if (! allPatches.contains(patchId)) {
            throw PatchLogger.ROOT_LOGGER.cannotRollbackPatch(patchId);
        }
        allPatches.remove(patchId);
    }

    List<String> getAllPatches() {
        return allPatches;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    protected void persist() throws IOException {
        getMutableProperties().put(Constants.ALL_PATCHES, PatchUtils.asString(allPatches));
        super.persist();
    }

    boolean setDone() {
        return done.compareAndSet(false, true);
    }

    InstallationState internalComplete() throws Exception {
        if (!setDone()) {
            throw new IllegalStateException();
        }
        try {
            installationState.persist();
        } catch (Exception e) {
            installationState.restore();
            throw e;
        }
        try {
            persist();
        } catch (Exception e) {
            installationState.restore();
        }
        return installationState;
    }

    static class InstallationState {

        private final Map<String, MutableTargetImpl> layers = new LinkedHashMap<String, MutableTargetImpl>();
        private final Map<String, MutableTargetImpl> addOns = new LinkedHashMap<String, MutableTargetImpl>();

        protected void putLayer(final Layer layer) throws IOException {
            putPatchableTarget(layer.getName(), layer, layers);
        }

        protected void putAddOn(final AddOn addOn) throws IOException {
            putPatchableTarget(addOn.getName(), addOn, addOns);
        }

        protected void putPatchableTarget(final String name, final PatchableTarget target, Map<String, MutableTargetImpl> map) throws IOException {
            final PatchableTarget.TargetInfo info = target.loadTargetInfo();
            map.put(name, new MutableTargetImpl(info));
        }

        Map<String, MutableTargetImpl> getLayers() {
            return layers;
        }

        Map<String, MutableTargetImpl> getAddOns() {
            return addOns;
        }

        protected void persist() throws IOException {
            for (final MutableTargetImpl target : layers.values()) {
                target.persist();
            }
            for (final MutableTargetImpl target : addOns.values()) {
                target.persist();
            }
        }

        private void restore() {
            for (final MutableTargetImpl target : layers.values()) {
                try {
                    target.restore();
                } catch (IOException e) {
                    PatchLogger.ROOT_LOGGER.debugf(e, "failed to restore original state for layer %s", target);
                }
            }
            for (final MutableTargetImpl target : addOns.values()) {
                try {
                    target.restore();
                } catch (IOException e) {
                    PatchLogger.ROOT_LOGGER.debugf(e, "failed to restore original state for add-on %s", target);
                }
            }
        }

    }

}
