/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Red Hat, Inc.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package hudson.model;

import hudson.Functions;
import hudson.console.AnnotatedLargeText;
import hudson.util.StreamTaskListener;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.lang.ref.WeakReference;
import java.nio.charset.Charset;
import org.kohsuke.stapler.framework.io.LargeText;
import org.kohsuke.stapler.framework.io.ByteBuffer;

/**
 * {@link Thread} for performing one-off task.
 *
 * <p>
 * Designed to be used inside {@link TaskAction}.
 *
 * 
 *
 * @author Kohsuke Kawaguchi
 * @since 1.191
 * @see TaskAction
 */
public abstract class TaskThread extends Thread {
    /**
     * @deprecated as of Hudson 1.350
     *      Use {@link #log}. It's the same object, in a better type.
     */
    @Deprecated
    private final LargeText text;

    /**
     * Represents the output from this task thread.
     */
    private final AnnotatedLargeText<TaskAction> log;

    /**
     * Represents the interface to produce output.
     */
    private TaskListener listener;

    private final TaskAction owner;

    private volatile boolean isRunning;

    /**
     *
     * @param output
     *      Determines where the output from this task thread goes.
     */
    protected TaskThread(TaskAction owner, ListenerAndText output) {
        //FIXME this failed to compile super(owner.getBuild().toString()+' '+owner.getDisplayName());
        //Please implement more general way how to get information about action owner, 
        //if you want it in the thread's name.
        super(owner.getDisplayName());
        this.owner = owner;
        this.text = this.log = output.text;
        this.listener = output.listener;
        this.isRunning = true;
    }

    public Reader readAll() throws IOException {
        // this method can be invoked from another thread.
        return text.readAll();
    }

    /**
     * Registers that this {@link TaskThread} is run for the specified
     * {@link TaskAction}. This can be explicitly called from subtypes
     * to associate a single {@link TaskThread} across multiple tag actions.
     */
    protected final void associateWith(TaskAction action) {
        action.workerThread = this;
        action.log = new WeakReference<>(log);
    }

    /**
     * Starts the task execution asynchronously.
     */
    @Override
    public void start() {
        associateWith(owner);
        super.start();
    }

    public boolean isRunning() {
        return isRunning;
    }

    /**
     * Determines where the output of this {@link TaskThread} goes.
     * <p>
     * Subclass can override this to send the output to a file, for example.
     */
    protected ListenerAndText createListener() throws IOException {
        return ListenerAndText.forMemory();
    }

    @Override
    public final void run() {
        isRunning = true;
        try {
            perform(listener);
            listener.getLogger().println("Completed");
            owner.workerThread = null;            
        } catch (InterruptedException e) {
            listener.getLogger().println("Aborted");
        } catch (Exception e) {
            Functions.printStackTrace(e, listener.getLogger());
        } finally {
            listener = null;
            isRunning =false;
        }
        log.markAsComplete();
    }

    /**
     * Do the actual work.
     *
     * @throws Exception
     *      The exception is recorded and reported as a failure.
     */
    protected abstract void perform(TaskListener listener) throws Exception;

    /**
     * Tuple of {@link TaskListener} and {@link AnnotatedLargeText}, representing
     * the interface for producing output and how to retrieve it later.
     */
    public static final class ListenerAndText {
        final TaskListener listener;
        final AnnotatedLargeText<TaskAction> text;

        public ListenerAndText(TaskListener listener, AnnotatedLargeText<TaskAction> text) {
            this.listener = listener;
            this.text = text;
        }

        /**
         * @deprecated as of Hudson 1.350
         *      Use {@link #forMemory(TaskAction)} and pass in the calling {@link TaskAction}
         */
        @Deprecated
        public static ListenerAndText forMemory() {
            return forMemory(null);
        }

        /**
         * @deprecated as of Hudson 1.350
         *      Use {@link #forFile(File, TaskAction)} and pass in the calling {@link TaskAction}
         */
        @Deprecated
        public static ListenerAndText forFile(File f) throws IOException {
            return forFile(f,null);
        }

        /**
         * Creates one that's backed by memory.
         */
        public static ListenerAndText forMemory(TaskAction context) {
            // StringWriter is synchronized
            ByteBuffer log = new ByteBuffer();

            return new ListenerAndText(
                new StreamTaskListener(log),
                new AnnotatedLargeText<>(log, Charset.defaultCharset(), false, context)
            );
        }

        /**
         * Creates one that's backed by a file. 
         */
        public static ListenerAndText forFile(File f, TaskAction context) throws IOException {
            return new ListenerAndText(
                new StreamTaskListener(f),
                new AnnotatedLargeText<>(f, Charset.defaultCharset(), false, context)
            );
        }
    }
}
