/*
 * Copyright (C) 2015 Original Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.fabric8.kubernetes.workflow;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.LauncherDecorator;
import hudson.model.Computer;
import hudson.model.TaskListener;
import io.fabric8.kubernetes.api.model.EnvVar;
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.BodyInvoker;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import javax.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

public class PodStepExecution extends AbstractStepExecutionImpl {

    private static final transient Logger LOGGER = Logger.getLogger(PodStepExecution.class.getName());

    @Inject
    private PodStep step;
    @StepContextParameter private transient FilePath workspace;
    @StepContextParameter private transient EnvVars env;
    @StepContextParameter private transient TaskListener listener;
    @StepContextParameter private transient Computer computer;
    private String podName;

    @Override
    public boolean start() throws Exception {
        StepContext context = getContext();
        podName = step.getName() + "-" + UUID.randomUUID().toString();
        final AtomicBoolean podAlive = new AtomicBoolean(false);
        final CountDownLatch podStarted = new CountDownLatch(1);
        final CountDownLatch podFinished = new CountDownLatch(1);

        //The body is executed async. so we can't use try with resource here.
        final KubernetesFacade kubernetes = new KubernetesFacade();
        kubernetes.createPod(podName, step.getImage(), step.getServiceAccount(), step.getPrivileged(), step.getSecrets(), step.getHostPathMounts(), step.getEmptyDirs(), workspace.getRemote(), createPodEnv(step.getEnv()), "cat");
        kubernetes.watch(podName, podAlive, podStarted, podFinished, true);
        podStarted.await();

        context.newBodyInvoker()
                .withContext(BodyInvoker
                        .mergeLauncherDecorators(getContext().get(LauncherDecorator.class), new PodExecDecorator(kubernetes, podName, podAlive, podStarted, podFinished)))
                .withCallback(new PodCallback(kubernetes, podName))
                .start();
        return false;
    }

    @Override
    public void stop(Throwable cause) throws Exception {
        try (KubernetesFacade kubernetes = new KubernetesFacade()) {
            kubernetes.deletePod(podName);
        }
    }

    private List<EnvVar> createPodEnv(Map<String,String> env) throws IOException, InterruptedException {
        List<EnvVar> podEnv = new ArrayList<EnvVar>();
        if (env == null || env.isEmpty()) {
            return podEnv;
        }
        EnvVars envReduced = new EnvVars(env);
        EnvVars envHost = computer.getEnvironment();
        envReduced.entrySet().removeAll(envHost.entrySet());

        for (Map.Entry<String, String> entry : envReduced.entrySet()) {
            podEnv.add(new EnvVar(entry.getKey(), entry.getValue(), null));
        }

        for (Map.Entry<String, String> entry : env.entrySet()) {
            podEnv.add(new EnvVar(entry.getKey(), entry.getValue(), null));
        }
        return podEnv;
    }

    private class PodCallback extends BodyExecutionCallback {

        private final transient KubernetesFacade kubernetes;
        private final String podName;

        private PodCallback(KubernetesFacade kubernetes, String podName) {
            this.kubernetes = kubernetes;
            this.podName = podName;
        }

        @Override
        public void onSuccess(StepContext context, Object result) {
            try {
                kubernetes.deletePod(podName);
            } finally {
                try {
                    kubernetes.close();
                } catch (IOException e) {
                    LOGGER.warning("Failed to properly cleanup");
                }
                context.onSuccess(result);
            }
        }

        @Override
        public void onFailure(StepContext context, Throwable t) {
            try {
                kubernetes.deletePod(podName);
            } finally {
                try {
                    kubernetes.close();
                } catch (IOException e) {
                    LOGGER.warning("Failed to properly cleanup");
                }
                context.onFailure(t);
            }
        }
    }
}

@@ -3031,24 +3031,32 @@
             
+        
 .withCallbac
@@ -3073,28 +3073,16 @@
 allback(
-kubernetes, 
 podName)
@@ -4148,69 +4148,8 @@
  %7B%0A%0A
-        private final transient KubernetesFacade kubernetes;%0A
     
@@ -4215,37 +4215,8 @@
 ack(
-KubernetesFacade kubernetes, 
 Stri
@@ -4233,50 +4233,8 @@
 ) %7B%0A
-            this.kubernetes = kubernetes;%0A
     
@@ -4366,37 +4366,81 @@
 ) %7B%0A            
-try %7B
+listener.getLogger().println(%22SUCCESS CALLBACK%22);
 %0A               
@@ -4428,37 +4428,38 @@
 %22);%0A            
-    k
+try (K
 ubernetes.delete
@@ -4455,79 +4455,53 @@
 etes
-.deletePod(podName);%0A            %7D finally %7B%0A                try %7B%0A    
+Facade kubernetes = new KubernetesFacade())%7B%0A
     
@@ -4519,37 +4519,44 @@
  kubernetes.
-close();%0A    
+deletePod(podName);%0A
             
@@ -4573,36 +4573,32 @@
 OException e) %7B%0A
-    
                 
@@ -4647,37 +4647,43 @@
 %22);%0A            
-    %7D
+%7D finally %7B
 %0A               
@@ -4823,37 +4823,81 @@
 ) %7B%0A            
-try %7B
+listener.getLogger().println(%22FAILURE CALLBACK%22);
 %0A               
@@ -4885,37 +4885,38 @@
 %22);%0A            
-    k
+try (K
 ubernetes.delete
@@ -4912,79 +4912,53 @@
 etes
-.deletePod(podName);%0A            %7D finally %7B%0A                try %7B%0A    
+Facade kubernetes = new KubernetesFacade())%7B%0A
     
@@ -4984,21 +4984,28 @@
 tes.
-close();%0A    
+deletePod(podName);%0A
     
@@ -5030,36 +5030,32 @@
 OException e) %7B%0A
-    
                 
@@ -5104,37 +5104,43 @@
 %22);%0A            
-    %7D
+%7D finally %7B
 %0A               

Fix "inside pod" hanging issue.