/*
 * Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package lib;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker;

/**
 *
 * @author Alistair Neil <info@dazzleships.net>
 */
public class ClientProcess {

    public static final int CLIENT_STOPPED = 0;
    public static final int CLIENT_RUNNING = 1;
    public static final int CLIENT_TIMEDOUT = 2;
    private long initialdelay = 0;
    private volatile long startupTimeout;
    private SwingWorker<ArrayList<String>, String> swWorker;
    private volatile Process procApplication;
    private PrintWriter pwWriter;
    private volatile Thread threadSleep;
    private int intStatus = CLIENT_STOPPED;

    /**
     * Constructor
     */
    public ClientProcess() {
        setStartupTimeout(15);
    }

    /**
     * Set the initial delay that is executed prior to starting the process
     *
     * @param ms
     */
    public final void setStartupDelay(long ms) {
        initialdelay = ms;
    }

    /**
     * Set startup timeout
     *
     * @param seconds
     */
    public final void setStartupTimeout(long seconds) {
        startupTimeout = seconds * 1000;
    }

    /**
     * Starts the client process
     *
     * @param processpath
     */
    public final void start(final String processpath) {
        swWorker = new SwingWorker<ArrayList<String>, String>() {

            String line;
            int endstatus;

            @Override
            protected ArrayList<String> doInBackground() {
                BufferedReader brProcInput = null;
                BufferedReader brProcError = null;
                try {
                    endstatus = CLIENT_STOPPED;
                    if (isCancelled()) {
                        return null;
                    }
                    threadSleep = Thread.currentThread();
                    if (initialdelay > 0) {
                        try {
                            Thread.sleep(initialdelay);
                        } catch (InterruptedException ex) {
                        }
                    }
                    threadSleep = null;
                    initialdelay = 0;
                    if (isCancelled()) {
                        return null;
                    }
                    procApplication = Runtime.getRuntime().exec(processpath);
                    if (procApplication == null) {
                        return null;
                    }
                    brProcInput = new BufferedReader(new InputStreamReader(procApplication.getInputStream()), 512);
                    brProcError = new BufferedReader(new InputStreamReader(procApplication.getErrorStream()), 512);
                    pwWriter = new PrintWriter(procApplication.getOutputStream());
                    long timeout = System.currentTimeMillis() + startupTimeout;
                    while (!isCancelled()) {
                        if (brProcInput.ready()) {
                            timeout = System.currentTimeMillis() + startupTimeout;
                            // Read process notifications
                            line = brProcInput.readLine();
                            publish(line);
                        }
                        if (brProcError.ready()) {
                            timeout = System.currentTimeMillis() + startupTimeout;
                            // Read process notifications
                            line = brProcError.readLine();
                            publish(line);
                        }
                        if (System.currentTimeMillis() > timeout && startupTimeout > 0) {
                            endstatus = CLIENT_TIMEDOUT;
                            break;
                        }
                        threadSleep = Thread.currentThread();
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException ex) {
                        }
                        threadSleep = null;
                    }
                } catch (Exception ex) {
                    Logger.getGlobal().logp(Level.INFO, ClientProcess.class.getName(),
                            "SwingWorker Loop Exception", ex.getMessage());
                } finally {
                    // Cleanup resources
                    try {
                        if (brProcInput != null) {
                            brProcInput.close();
                        }
                        if (brProcError != null) {
                            brProcError.close();
                        }
                        pwWriter.close();
                        // Destroy process
                        if (procApplication != null) {
                            procApplication.getOutputStream().close();
                            procApplication.getInputStream().close();
                            procApplication.getErrorStream().close();
                            procApplication.destroy();
                        }
                    } catch (Exception ex) {
                        Logger.getGlobal().logp(Level.INFO, ClientProcess.class.getName(),
                                "SwingWorker Cleanup", ex.getMessage());
                    }
                }
                return null;
            }

            @Override
            protected void process(List<String> chunks) {
                for (String s : chunks) {
                    if (s != null) {
                        clientProcessEventFired(s);
                    }
                }
            }

            @Override
            protected void done() {
                intStatus = endstatus;
                clientProcessEventFired("");
            }

        };
        intStatus = CLIENT_RUNNING;
        clientProcessEventFired("");
        // Start client process
        swWorker.execute();
    }

    public final int getClientStatus() {
        return intStatus;
    }

    /**
     * Return the client system process
     *
     * @return a process
     */
    public final Process getProcess() {
        return procApplication;
    }

    /**
     * Stops the client process
     *
     */
    public final void stopProcess() {
        if (threadSleep != null) {
            threadSleep.interrupt();
        }
        if (swWorker != null) {
            swWorker.cancel(true);
        }
        try {
            procApplication.waitFor();
        } catch (Exception ex) {
            Logger.getGlobal().logp(Level.INFO, ClientProcess.class.getName(),
                    "stopProcess", ex.getMessage());
        }
    }

    /**
     * Send a message to the process
     *
     * @param message
     */
    public final void sendMessage(String message) {
        pwWriter.write(message);
    }

    /**
     * Event from the process, should be overriden by sub class
     *
     * @param data
     */
    public void clientProcessEventFired(String data) {
    }
}
