package com.atlassian.confluence.extra.graphviz;

import com.atlassian.confluence.servlet.download.ExportDownload;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.confluence.util.io.IOUtils;
import com.atlassian.renderer.v2.macro.MacroException;
import org.apache.log4j.Category;

import java.io.*;
import java.util.Properties;

import bucket.util.FileUtils;

/**
 * This class runs dot, and allows access to the results
 */
public class DotRunner
{
    private static final Category log = Category.getInstance(DotRunner.class);
    private File outputFile;
    private String outputPath;
    private StringBuffer output;
    private String verifiedDotExecutableName = null;

    public String getOutputUrl()
    {
        return ExportDownload.getUrl(outputFile, "image/png");
    }

    public int getExitCode()
    {
        return exitCode;
    }

    public String getError()
    {
        return error;
    }

    private int exitCode;
    private String error;

    /**
     * @param format     String giving format of file to generate
     * @param commands   String containing dot commands
     * @param createFile a boolean indicating whether the output of the dot command should be written to a temporary file,
     *                   or collected in a String
     * @throws MacroException
     */
    public DotRunner(String format, String commands, boolean createFile) throws MacroException
    {
        outputFile = null;
        outputPath = null;
        if (createFile)
        {
            try
            {
                outputFile = ExportDownload.createTempFile("graphviz", "." + format);
                outputPath = outputFile.getCanonicalPath();
            }
            catch (IOException ioe)
            {
                throw new MacroException("Couldn't open temporary file", ioe);
            }
        }
        Process p = null;
        String executableName = findValidDotName();
        if (executableName == null)
        {
            throw new MacroException("No useable executable name defined in graphviz.properties");
        }
        try
        {
            String[] args;
            if (createFile)
            {
                args = new String[3];
                args[2] = "-o" + outputPath;
            }
            else
            {
                args = new String[2];
            }
            args[0] = executableName;
            args[1] = "-T" + format;
            p = Runtime.getRuntime().exec(args);
            OutputStream dotStdin = p.getOutputStream();
            dotStdin.write(commands.getBytes());
            dotStdin.close();
            if (!createFile)
            {
                output = new StringBuffer();
                BufferedReader dotStdout = new BufferedReader(new InputStreamReader(p.getInputStream()));
                String line = dotStdout.readLine();
                while (line != null)
                {
                    output.append(line);
                    line = dotStdout.readLine();
                }
            }
            while (true)
                try
                {
                    exitCode = p.waitFor();
                    if (exitCode != 0)
                    {
                        // dot returned an error code
                        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
                        error = "";
                        String line = reader.readLine();
                        while (line != null)
                        {
                            error += line;
                            line = reader.readLine();
                        }
                    }
                    break;
                }
                catch (InterruptedException e)
                {
                    // try again if we are interrupted before waitFor returns;
                }
        }
        catch (IOException ioe)
        {
            throw new MacroException("Couldn't run '" + verifiedDotExecutableName + "', make sure it is in your PATH", ioe);
        }
        finally
        {
            if (p != null)
            {
                destroyProcess(p);
            }
        }
    }

    private String findValidDotName()
    {
        if (verifiedDotExecutableName == null)
        {
            Properties p = GeneralUtil.getProperties("graphviz.properties", DotRunner.class);
            String dotExecutableName = p.getProperty("dot.executable.name");
            if (dotExecutableName != null)
            {
                verifiedDotExecutableName = dotExecutableName;
            }
            else
            {
                String namesToTry = p.getProperty("dot.candidate.paths");
                if (namesToTry == null)
                {
                    return null;
                }
                else
                {
                    String[] names = namesToTry.split(",");
                    for (int i = 0; i < names.length; ++i)
                    {
                        try
                        {
                            log.info("trying path " + names[i]);
                            String[] args = new String[2];
                            args[0] = names[i];
                            args[1] = "-V";
                            Process proc = Runtime.getRuntime().exec(args);
                            // if we get here, we found the executable
                            destroyProcess(proc);
                            verifiedDotExecutableName = names[i];
                            break;
                        }
                        catch (IOException ioe)
                        {
                            log.info("dot path " + names[i] + " failed, trying next path.", ioe);
                        }
                    }
                }
            }
        }
        return verifiedDotExecutableName;
    }

    private void destroyProcess(Process p)
    {
        if (p != null)
        {
            IOUtils.close(p.getInputStream());
            IOUtils.close(p.getOutputStream());
            IOUtils.close(p.getErrorStream());
            p.destroy();
        }
    }


    public String getOutput()
    {
        return output.toString();
    }
}