package com.indeed.proctor.webapp.controllers;

import com.google.common.collect.Lists;
import com.indeed.proctor.webapp.extensions.AfterBackgroundJobExecute;
import com.indeed.proctor.webapp.extensions.BeforeBackgroundJobExecute;
import org.apache.log4j.Logger;

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

/**
 */
public abstract class BackgroundJob<T> implements Callable<T> {
    private static final Logger LOGGER = Logger.getLogger(BackgroundJob.class);

    private Future<T> future;
    private String status = "PENDING";
    protected final StringBuilder logBuilder = new StringBuilder();

    private Long id;

    private final long createdTime = System.currentTimeMillis();

    // URL to direct users to upon completion
    private final List<ResultUrl> urls = Lists.newArrayList();

    private String endMessage = "";

    private Throwable error = null;

    public void log(final String message) {
        logBuilder.append(message).append("\n");
    }

    public String getLog() {
        return logBuilder.toString();
    }

    public String getStatus() {
        if (error != null) {
            setStatus("FAILED");
        } else if (future != null) {
            if (future.isCancelled()) {
                setStatus("CANCELLED");
            } else if (future.isDone()) {
                setStatus("DONE");
            }
        }
        return status;
    }

    public void setStatus(final String status) {
        this.status = status;
    }

    public Future<T> getFuture() {
        return future;
    }

    public void setFuture(final Future<T> future) {
        this.future = future;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public List<ResultUrl> getUrls() {
        return urls;
    }

    public void addUrl(final String url, final String text) {
        this.addUrl(url, text, "");
    }

    public void addUrl(final String url, final String text, final String target) {
        this.addUrl(new ResultUrl(url, text, target));
    }

    public void addUrl(final ResultUrl url) {
        this.urls.add(url);
    }

    public String getEndMessage() {
        return endMessage;
    }

    public void setEndMessage(final String endMessage) {
        this.endMessage = endMessage;
    }

    public Throwable getError() {
        return error;
    }

    public void setError(final Throwable error) {
        this.error = error;
    }

    public long getCreatedTime() {
        return createdTime;
    }

    public String toString() {
        return id + ": " + status;
    }

    public boolean isRunning() {
        return future == null || (!future.isDone() && !future.isCancelled());
    }

    public abstract String getTitle();

    public JobType getJobType() {
        return JobType.UNKNOWN;
    }

    public void logFailedJob(final Throwable t) {
        log("Failed:");
        setError(t);
        Throwable cause = t;
        final StringBuilder level = new StringBuilder(10);
        while (cause != null) {
            log(level.toString() + cause.getMessage());
            cause = cause.getCause();
            level.append("-- ");
        }
    }

    public static class ResultUrl {
        private final String href;
        private final String text;
        private final String target;

        public ResultUrl(final String href,
                         final String text,
                         final String target) {
            this.href = href;
            this.text = text;
            this.target = target;
        }

        public String getHref() {
            return href;
        }

        public String getTarget() {
            return target;
        }

        public String getText() {
            return text;
        }
    }

    protected abstract List<BeforeBackgroundJobExecute> getBeforeBackgroundJobExecutes();
    protected abstract List<AfterBackgroundJobExecute> getAfterBackgroundJobExecutes();

    protected abstract T execute() throws Exception;

    @Override
    public T call() throws Exception {
        T result = null;

        try {
            for (final BeforeBackgroundJobExecute beforeBackgroundJobExecute : getBeforeBackgroundJobExecutes()) {
                beforeBackgroundJobExecute.beforeExecute(this);
            }
        } catch (final Exception e) {
            LOGGER.error("BeforeBackgroundJobExecute Failed: " + getTitle(), e);
            logFailedJob(e);
            return null;
        }

        try {
            result = execute();
        } catch (final Exception e) {
            LOGGER.error("Background Job Failed: " + getTitle(), e);
            logFailedJob(e);
        }

        try {
            for (final AfterBackgroundJobExecute afterBackgroundJobExecute : getAfterBackgroundJobExecutes()) {
                afterBackgroundJobExecute.afterExecute(this, result);
            }
        } catch (final Exception e) {
            LOGGER.error("AfterBackgroundJobExecute Failed: " + getTitle(), e);
            logFailedJob(e);
        }

        return result;
    }

    public enum JobType {
        TEST_CREATION("test-creation"),
        TEST_EDIT("test-edit"),
        TEST_DELETION("test-deletion"),
        TEST_PROMOTION("test-promotion"),
        WORKING_DIRECTORY_CLEANING("working-directory-cleaning"),
        JOB_TEST("job-test"),
        UNKNOWN("unknown");

        private final String name;

        JobType(final String name) {
            this.name = name;
        }

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

@@ -550,22 +550,25 @@
 private 
-String
+JobStatus
  status 
@@ -573,17 +573,25 @@
 s = 
-%22
+JobStatus.
 PENDING
-%22
 ;%0A  
@@ -1118,308 +1118,31 @@
 lic 
-String getStatus() %7B%0A        if (error != null) %7B%0A            setStatus(%22FAILED%22);%0A        %7D else if (future != null) %7B%0A            if (future.isCancelled()) %7B%0A                setStatus(%22CANCELLED%22);%0A            %7D else if (future.isDone()) %7B%0A                setStatus(%22DONE%22);%0A            %7D%0A        %7D
+JobStatus getStatus() %7B
 %0A   
@@ -1200,22 +1200,25 @@
 s(final 
-String
+JobStatus
  status)
@@ -2709,19 +2709,43 @@
     
-setError(t)
+JobStatus status = JobStatus.FAILED
 ;%0A  
@@ -2972,38 +2972,203 @@
     
-level.append(%22-- %22);%0A        %7D
+if (cause instanceof InterruptedException) %7B%0A                status = JobStatus.CANCELLED;%0A            %7D%0A            level.append(%22-- %22);%0A        %7D%0A        setError(t);%0A        setStatus(status);
 %0A   
@@ -4521,24 +4521,63 @@
  execute();%0A
+            setStatus(JobStatus.DONE);%0A
         %7D ca
@@ -5602,11 +5602,429 @@
  %7D%0A    %7D
+%0A%0A    public enum JobStatus %7B%0A        PENDING(%22PENDING%22),%0A        DONE(%22DONE%22),%0A        CANCELLED(%22CANCELLED%22),%0A        FAILED(%22FAILED%22);%0A%0A        private final String name;%0A%0A        JobStatus(final String name) %7B%0A            this.name = name;%0A        %7D%0A%0A        public String getName() %7B%0A            return name;%0A        %7D%0A%0A        @Override%0A        public String toString() %7B%0A            return name;%0A        %7D%0A    %7D
 %0A%7D%0A

Fix the logic of status detection of background job