package com.mesosphere.velocity.marathon;

import com.mesosphere.velocity.marathon.fields.MarathonVars;
import hudson.util.FormValidation;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import com.mesosphere.velocity.marathon.exceptions.MarathonFileInvalidException;
import com.mesosphere.velocity.marathon.exceptions.MarathonFileMissingException;
import com.mesosphere.velocity.marathon.fields.MarathonLabel;
import com.mesosphere.velocity.marathon.fields.MarathonUri;
import com.mesosphere.velocity.marathon.interfaces.AppConfig;
import com.mesosphere.velocity.marathon.interfaces.MarathonBuilder;
import com.mesosphere.velocity.marathon.util.MarathonBuilderUtils;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Item;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.ListBoxModel;
import mesosphere.marathon.client.MarathonException;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

public class MarathonStep extends AbstractStepImpl implements AppConfig {
    private final String              url;
    private       List<MarathonUri>   uris;
    private       List<MarathonLabel> labels;   // this does not work :(
    private       List<MarathonVars>  env;
    private       String              appid;
    private       String              id;
    private       String              docker;
    private       String              containerType;
    private       boolean             dockerForcePull;
    private       String              filename;
    private       String              credentialsId;
    private       boolean             forceUpdate;

    @DataBoundConstructor
    public MarathonStep(final String url) {
        this.url = MarathonBuilderUtils.rmSlashFromUrl(url);
        this.uris = new ArrayList<MarathonUri>(5);
        this.labels = new ArrayList<MarathonLabel>(5);
        this.env = new ArrayList<MarathonVars>(5);
    }

    @Override
    public String getAppId() {
        return this.id;
    }

    public String getUrl() {
        return url;
    }

    @Override
    public boolean getForceUpdate() {
        return forceUpdate;
    }

    @Override
    public String getContainerType() {
        return this.containerType;
    }

    @DataBoundSetter
    public void setForceUpdate(final boolean forceUpdate) {
        this.forceUpdate = forceUpdate;
    }

    public List<MarathonVars> getEnv() {
        final List<MarathonVars> marathonVarsList = new ArrayList<MarathonVars>(this.env.size());
        for (final MarathonVars envElem : this.env) {
            marathonVarsList.add(new MarathonVars(envElem.getName(), envElem.getValue()));
        }
        return marathonVarsList;
    }

    @DataBoundSetter
    public void setEnv(final List<MarathonVars> env) {
        this.env = env;
    }

    public String getDocker() {
        return docker;
    }

    public boolean getDockerForcePull() {
        return dockerForcePull;
    }

    @Override
    public String getCredentialsId() {
        return credentialsId;
    }

    @DataBoundSetter
    public void setCredentialsId(final String credentialsId) {
        this.credentialsId = credentialsId;
    }

    public List<MarathonUri> getUris() {
        final List<MarathonUri> marathonUris = new ArrayList<MarathonUri>(this.uris.size());
        for (final MarathonUri u : this.uris) {
            marathonUris.add(new MarathonUri(u.getUri()));
        }
        return marathonUris;
    }

    @DataBoundSetter
    public void setUris(final List<MarathonUri> uris) {
        this.uris = uris;
    }

    public List<MarathonLabel> getLabels() {
        final List<MarathonLabel> marathonLabels = new ArrayList<MarathonLabel>(this.labels.size());
        for (final MarathonLabel label : this.labels) {
            marathonLabels.add(new MarathonLabel(label.getName(), label.getValue()));
        }
        return marathonLabels;
    }

    @DataBoundSetter
    public void setLabels(final List<MarathonLabel> labels) {
        this.labels = labels;
    }

    @DataBoundSetter
    public void setDockerForcePull(final boolean dockerForcePull) {
        this.dockerForcePull = dockerForcePull;
    }

    @DataBoundSetter
    public void setDocker(final String docker) {
        this.docker = docker;
    }

    @DataBoundSetter
    public void setContainerType(final String containerType) {
        this.containerType = containerType;
    }

    /**
     * Get the application id for the "appid" field.
     *
     * @return application id
     * @deprecated use {@link #getId()}
     */
    @Deprecated
    public String getAppid() {
        return appid;
    }

    /**
     * Set the application id for the "appid" field.
     *
     * @param appid application id
     * @deprecated use {@link #setId(String)}
     */
    @Deprecated
    @DataBoundSetter
    public void setAppid(final String appid) {
        this.appid = appid;
    }

    public String getFilename() {
        return filename;
    }

    @DataBoundSetter
    public void setFilename(@Nonnull final String filename) {
        if (filename.trim().length() > 0)
            this.filename = filename;
    }

    /**
     * Get the application id for the "id" field.
     *
     * @return application id
     * @since 1.3.3
     */
    public String getId() {
        return id;
    }

    /**
     * Set the application id for the "id" field.
     *
     * @param id application id
     * @since 1.3.3
     */
    @DataBoundSetter
    public void setId(final String id) {
        this.id = id;
    }

    @Extension
    public static class DescriptorImpl extends AbstractStepDescriptorImpl {
        @Inject
        private MarathonRecorder.DescriptorImpl delegate;

        public DescriptorImpl() {
            super(MarathonStepExecution.class);
        }

        public FormValidation doCheckUrl(@QueryParameter final String value) {
            return delegate.doCheckUrl(value);
        }

        public FormValidation doCheckUri(@QueryParameter final String value) {
            return delegate.doCheckUri(value);
        }

        @Override
        public String getFunctionName() {
            return "marathon";
        }

        @Nonnull
        @Override
        public String getDisplayName() {
            return "Marathon Deployment";
        }

        public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item project) {
            return delegate.doFillCredentialsIdItems(project);
        }
    }

    public static class MarathonStepExecution extends AbstractSynchronousStepExecution<Void> {
        private static final long serialVersionUID = 6213649171165833159L;
        /*
                 * Need the listener to append to console log.
                 */
        @StepContextParameter
        transient         TaskListener listener;
        @StepContextParameter
        private transient FilePath     ws;
        @StepContextParameter
        private transient EnvVars      envVars;
        @StepContextParameter
        private transient Run          run;
        @Inject
        private transient MarathonStep step;

        @Override
        protected Void run() throws Exception {
            if (step.getAppid() != null && !step.getAppid().equals("")) {
                listener.getLogger().println("[Marathon] DEPRECATION WARNING: This configuration is using \"appid\" instead of \"id\". Please update this configuration.");
                step.setId(step.getAppid());
            }

            try {
                MarathonBuilder
                        .getBuilder(step)
                        .setEnvVars(envVars)
                        .setWorkspace(ws)
                        .read(step.filename)
                        .build()
                        .toFile()
                        .update();
            } catch (MarathonException | MarathonFileInvalidException | MarathonFileMissingException me) {
                final String errorMsg = String.format("[Marathon] %s", me.getMessage());
                listener.error(errorMsg);
                run.setResult(Result.FAILURE);
            }

            return null;
        }
    }
}