package org.jboss.as.patching.installation;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.jboss.as.patching.Constants;
import org.jboss.as.patching.DirectoryStructure;
import org.jboss.as.patching.IoUtils;
import org.jboss.as.patching.metadata.Patch;
import org.jboss.as.patching.runner.PatchUtils;

/**
 * @author Emanuel Muckenhuber
 */
class MutableTargetImpl implements InstallationManager.MutablePatchingTarget {

    private final DirectoryStructure structure;
    private final PatchableTarget.TargetInfo current;
    // The mutable state
    private final List<String> patchIds;
    private final Properties properties;
    private String cumulativeID;
    private boolean modified = false;

    // this attribute will be null for layers and add-ons in their current implementation
    private String version;

    private Set<String> rolledback = Collections.emptySet();

    MutableTargetImpl(PatchableTarget.TargetInfo current) {
        this(current, null);
    }

    MutableTargetImpl(PatchableTarget.TargetInfo current, String currentVersion) {
        this.current = current;
        this.structure = current.getDirectoryStructure();
        this.cumulativeID = current.getCumulativePatchID();
        this.patchIds = new ArrayList<String>(current.getPatchIDs());
        this.properties = new Properties(current.getProperties());
        this.version = currentVersion;
    }

    @Override
    public boolean isApplied(String patchId) {
        if (cumulativeID.equals(patchId)) {
            return true;
        }
        return patchIds.contains(patchId);
    }

    @Override
    public void rollback(final String patchId) {
        if (!patchIds.remove(patchId)) {
            if (patchId.equals(cumulativeID)) {
                cumulativeID = Constants.NOT_PATCHED;
            } else {
                throw new IllegalStateException("cannot rollback not-applied patch " + patchId); // internal wrong usage, no i18n
            }
        } else {
            switch(rolledback.size()) {
                case 0:
                    rolledback = Collections.singleton(patchId);
                    break;
                case 1:
                    rolledback = new HashSet<String>(rolledback);
                default:
                    rolledback.add(patchId);
            }
        }
        modified = true;
    }

    @Override
    public boolean isRolledback(final String patchId) {
        return rolledback.contains(patchId);
    }

    @Override
    public void apply(String patchId, Patch.PatchType patchType) {
        if (patchType == Patch.PatchType.CUMULATIVE) {
            if (!patchIds.isEmpty()) {
                throw new IllegalStateException("cannot apply cumulative patch if there are other patches applied " +patchIds); // internal wrong usage, no i18n
            }
            cumulativeID = patchId;
        } else {
            patchIds.add(0, patchId);
        }
        modified = true;
    }

    @Override
    public String getCumulativePatchID() {
        return current.getCumulativePatchID();
    }

    @Override
    public List<String> getPatchIDs() {
        return current.getPatchIDs();
    }

    @Override
    public Properties getProperties() {
        return current.getProperties();
    }

    @Override
    public DirectoryStructure getDirectoryStructure() {
        return structure;
    }

    public String getVersion() {
        return version;
    }

    public void setResultingVersion(String version) {
        this.version = version;
    }

    protected Properties getMutableProperties() {
        return properties;
    }

    protected void persist() throws IOException {
        if (modified) {
            // persist the state for bundles and modules directory
            persist(cumulativeID, patchIds, properties);
        }
    }

    protected void restore() throws IOException {
        if (modified) {
            // persist the state for bundles and modules directory
            persist(current.getCumulativePatchID(), current.getPatchIDs(), current.getProperties());
        }
    }

    protected void persist(final String cumulativeID, final List<String> patches, final Properties properties) throws IOException {
        assert cumulativeID != null;
        // Create the parent
        IoUtils.mkdir(structure.getInstallationInfo().getParentFile());

        final List<String> consolidate = new ArrayList<String>();
        consolidate.addAll(patches);
        if (!Constants.BASE.equals(cumulativeID)) {
            consolidate.add(cumulativeID);
        }

        if (structure.getModuleRoot() != null) {
            final File overlays = new File(structure.getModuleRoot(), Constants.OVERLAYS);
            final File refs = new File(overlays, Constants.OVERLAYS);
            PatchUtils.writeRefs(refs, consolidate);
        }

        // Update the properties
        properties.put(Constants.CUMULATIVE, cumulativeID);
        properties.put(Constants.PATCHES, PatchUtils.asString(patches));
        if(version != null) {
            properties.put(Constants.CURRENT_VERSION, version);
        }

        // Write layer.conf
        PatchUtils.writeProperties(structure.getInstallationInfo(), properties);
    }

    @Override
    public PatchableTarget.TargetInfo getModifiedState() {
        if (modified) {
            return new LayerInfo.TargetInfoImpl(properties, cumulativeID, patchIds, structure);
        } else {
            return current;
        }
    }

}
