package com.github.kostyasha.yad;

import com.github.kostyasha.yad.launcher.DockerComputerLauncher;
import com.github.kostyasha.yad.strategy.DockerOnceRetentionStrategy;
import com.github.kostyasha.yad_docker_java.com.google.common.base.MoreObjects;
import hudson.Extension;
import hudson.model.Descriptor.FormException;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.labels.LabelAtom;
import hudson.slaves.NodeProperty;
import hudson.slaves.RetentionStrategy;
import hudson.util.FormValidation;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static java.util.Objects.isNull;

/**
 * All configuration (jenkins and docker specific) required for launching slave instances.
 */
public class DockerSlaveTemplate extends DockerSlaveConfig {
    private static final Logger LOG = LoggerFactory.getLogger(DockerSlaveTemplate.class);
    private int configVersion = 1;

    private int maxCapacity = 10;

    private transient /*almost final*/ Set<LabelAtom> labelSet;
    private transient OsType osType;

    /**
     * Generates new unique ID for new instances.
     */
    public DockerSlaveTemplate() {
        super();
    }

    /**
     * Custom specified ID. When editing existed UI entry, UI sends it back.
     */
    public DockerSlaveTemplate(@Nonnull String id) throws FormException {
        super(id);
        if (isNull(id)) {
            throw new FormException("Hidden id must not be null", "id");
        }
    }

    /**
     * FIXME DescribableList doesn't work with DBS https://gist.github.com/KostyaSha/3414f4f453ea7c7406b4
     */
    @DataBoundConstructor
    public DockerSlaveTemplate(@Nonnull String id, List<NodeProperty<?>> nodeProperties)
            throws FormException {
        this(id);
        setNodeProperties(nodeProperties);
    }


    @DataBoundSetter
    public void setLabelString(String labelString) {
        super.setLabelString(labelString);
        this.labelSet = Label.parse(labelString);
    }

    public OsType getOsType() {
        return this.osType;
    }

    public void setOsType(OsType osType) {
        this.osType = osType;
    }

    public int getNumExecutors() {
        if (getRetentionStrategy() instanceof DockerOnceRetentionStrategy) {
            return 1; // works only with one executor!
        }

        return numExecutors;
    }

    /**
     * tmp fix for terminating boolean caching
     */
    public RetentionStrategy getRetentionStrategyCopy() {
        if (retentionStrategy instanceof DockerOnceRetentionStrategy) {
            DockerOnceRetentionStrategy onceRetention = (DockerOnceRetentionStrategy) retentionStrategy;
            return new DockerOnceRetentionStrategy(onceRetention.getIdleMinutes());
        }
        return retentionStrategy;
    }

    public int getMaxCapacity() {
        return maxCapacity;
    }

    @DataBoundSetter
    public void setMaxCapacity(int maxCapacity) {
        this.maxCapacity = maxCapacity;
    }

    @Nonnull
    public Set<LabelAtom> getLabelSet() {
        return labelSet != null ? labelSet : Collections.emptySet();
    }

    @Override
    public DockerComputerLauncher getLauncher() {
        return (DockerComputerLauncher) super.getLauncher();
    }

    /**
     * Initializes data structure that we don't persist.
     */
    @SuppressWarnings("unused")
    public Object readResolve() {
        if (configVersion < 1) {
            if (isNull(nodeProperties)) nodeProperties = new ArrayList<>();
            nodeProperties.add(new DockerNodeProperty("DOCKER_CONTAINER_ID", "JENKINS_CLOUD_ID", "DOCKER_HOST"));
            configVersion = 1;
        }

        // real @Nonnull
        if (mode == null) {
            mode = Node.Mode.NORMAL;
        }

        if (retentionStrategy == null) {
            retentionStrategy = new DockerOnceRetentionStrategy(10);
        }

        try {
            labelSet = Label.parse(getLabelString()); // fails sometimes under debugger
        } catch (Throwable t) {
            LOG.error("Can't parse labels: {}", t);
        }

        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        DockerSlaveTemplate that = (DockerSlaveTemplate) o;

        return new EqualsBuilder()
                .appendSuper(true)
                .append(maxCapacity, that.maxCapacity)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }

    public String getShortDescription() {
        return MoreObjects.toStringHelper(this)
                .add("image", dockerContainerLifecycle.getImage())
                .toString();
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    @Extension
    public static final class DescriptorImpl extends DockerSlaveConfig.DescriptorImpl {

        public FormValidation doCheckNumExecutors(@QueryParameter int numExecutors) {
            if (numExecutors > 1) {
                return FormValidation.warning("Experimental, see help");
            } else if (numExecutors < 1) {
                return FormValidation.error("Must be > 0");
            }
            return FormValidation.ok();
        }

        @Nonnull
        @Override
        public String getDisplayName() {
            return "Docker Template";
        }

        public Class getDockerTemplateBase() {
            return DockerSlaveTemplate.class;
        }
    }
}