/*
 * DISTRHO Plugin Framework (DPF)
 * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any purpose with
 * or without fee is hereby granted, provided that the above copyright notice and this
 * permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// needed for IDE
#include "DistrhoPluginInfo.h"

#include "DistrhoUI.hpp"

#define MPV_TEST
// #define KDE_FIFO_TEST

#ifdef KDE_FIFO_TEST
// Extra includes for current path and fifo stuff
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif

START_NAMESPACE_DISTRHO

#ifdef KDE_FIFO_TEST
// TODO: generate a random, not-yet-existing, filename
const char* const kFifoFilename = "/tmp/dpf-fifo-test";

// Helper to get current path of this plugin
static const char* getCurrentPluginFilename()
{
    Dl_info exeInfo;
    void* localSymbol = (void*)kFifoFilename;
    dladdr(localSymbol, &exeInfo);
    return exeInfo.dli_fname;
}

// Helper to check if a file exists
static bool fileExists(const char* const filename)
{
    return access(filename, F_OK) != -1;
}

// Helper function to keep trying to write until it succeeds or really errors out
static ssize_t
writeRetry(int fd, const void* src, size_t size)
{
    ssize_t error;
    int attempts = 0;

    do {
        error = write(fd, src, size);
    } while (error == -1 && (errno == EINTR || errno == EPIPE) && ++attempts < 5);

    return error;
}
#endif

// -----------------------------------------------------------------------------------------------------------

class ExternalExampleUI : public UI
{
public:
    ExternalExampleUI()
        : UI(405, 256),
#ifdef KDE_FIFO_TEST
          fFifo(-1),
          fExternalScript(getNextBundlePath()),
#endif
          fValue(0.0f)
    {
#ifdef KDE_FIFO_TEST
        if (fExternalScript.isEmpty())
        {
            fExternalScript = getCurrentPluginFilename();
            fExternalScript.truncate(fExternalScript.rfind('/'));
        }

        fExternalScript += "/ExternalLauncher.sh";
        d_stdout("External script = %s", fExternalScript.buffer());
#endif
        if (isVisible() || isEmbed())
            visibilityChanged(true);
    }

    ~ExternalExampleUI()
    {
        if (isEmbed())
            terminateAndWaitForExternalProcess();
    }

protected:
   /* --------------------------------------------------------------------------------------------------------
    * DSP/Plugin Callbacks */

   /**
      A parameter has changed on the plugin side.
      This is called by the host to inform the UI about parameter changes.
    */
    void parameterChanged(uint32_t index, float value) override
    {
        if (index != 0)
            return;

        fValue = value;

#ifdef KDE_FIFO_TEST
        if (fFifo == -1)
            return;

        // NOTE: This is a terrible way to pass values, also locale might get in the way...
        char valueStr[24];
        std::memset(valueStr, 0, sizeof(valueStr));
        std::snprintf(valueStr, 23, "%i\n", static_cast<int>(value + 0.5f));

        DISTRHO_SAFE_ASSERT(writeRetry(fFifo, valueStr, 24) == sizeof(valueStr));
#endif
    }

   /* --------------------------------------------------------------------------------------------------------
    * External Window overrides */

   /**
      Keep-alive.
    */
    void uiIdle() override
    {
#ifdef KDE_FIFO_TEST
        if (fFifo == -1)
            return;

        writeRetry(fFifo, "idle\n", 5);
#endif
    }

   /**
      Manage external process and IPC when UI is requested to be visible.
    */
    void visibilityChanged(const bool visible) override
    {
#ifdef KDE_FIFO_TEST
        if (visible)
        {
            DISTRHO_SAFE_ASSERT_RETURN(fileExists(fExternalScript),);

            mkfifo(kFifoFilename, 0666);
            sync();

            char winIdStr[24];
            std::memset(winIdStr, 0, sizeof(winIdStr));
            std::snprintf(winIdStr, 23, "%lu", getTransientWindowId());

            const char* args[] = {
                fExternalScript.buffer(),
                kFifoFilename,
                "--progressbar", "External UI example",
                "--title", getTitle(),
                nullptr,
            };
            DISTRHO_SAFE_ASSERT_RETURN(startExternalProcess(args),);

            // NOTE: this can lockup the current thread if the other side does not read the file!
            fFifo = open(kFifoFilename, O_WRONLY);
            DISTRHO_SAFE_ASSERT_RETURN(fFifo != -1,);

            parameterChanged(0, fValue);
        }
        else
        {
            if (fFifo != -1)
            {
                if (isRunning())
                {
                    DISTRHO_SAFE_ASSERT(writeRetry(fFifo, "quit\n", 5) == 5);
                    fsync(fFifo);
                }
                ::close(fFifo);
                fFifo = -1;
            }

            unlink(kFifoFilename);
            terminateAndWaitForExternalProcess();
        }
#endif
#ifdef MPV_TEST
        if (visible)
        {
            const char* const file = "/home/falktx/Videos/HD/"; // TODO make this a state file?

            if (isEmbed())
            {
                char winIdStr[64];
                snprintf(winIdStr, sizeof(winIdStr), "--wid=%lu", getParentWindowHandle());
                const char* args[] = {
                    "mpv",
                    "--ao=jack",
                    winIdStr,
                    file,
                    nullptr
                };
                unsetenv("LD_LIBRARY_PATH");
                startExternalProcess(args);
            }
            else
            {
                const char* args[] = {
                    "mpv",
                    "--ao=jack",
                    file,
                    nullptr
                };
                startExternalProcess(args);
            }
        }
        else
        {
            terminateAndWaitForExternalProcess();
        }
#endif
    }

    // -------------------------------------------------------------------------------------------------------

private:
#ifdef KDE_FIFO_TEST
    // IPC Stuff
    int fFifo;

    // Path to external ui script
    String fExternalScript;
#endif

    // Current value, cached for when UI becomes visible
    float fValue;

   /**
      Set our UI class as non-copyable and add a leak detector just in case.
    */
    DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ExternalExampleUI)
};

/* ------------------------------------------------------------------------------------------------------------
 * UI entry point, called by DPF to create a new UI instance. */

UI* createUI()
{
    return new ExternalExampleUI();
}

// -----------------------------------------------------------------------------------------------------------

END_NAMESPACE_DISTRHO
