ImagePixelData::ImagePixelData (const Image::PixelFormat format, const int w, const int h)
    : pixelFormat (format), width (w), height (h)
{
    jassert (format == Image::RGB || format == Image::ARGB || format == Image::SingleChannel);
    jassert (w > 0 && h > 0); // It's illegal to create a zero-sized image!
}

ImagePixelData::~ImagePixelData()
{
}

ImageType::ImageType() {}
ImageType::~ImageType() {}

Image ImageType::convert (const Image& source) const
{
    if (source.isNull() || getTypeID() == (ScopedPointer<ImageType> (source.getPixelData()->createType())->getTypeID()))
        return source;

    const Image::BitmapData src (source, Image::BitmapData::readOnly);

    Image newImage (create (src.pixelFormat, src.width, src.height, false));
    Image::BitmapData dest (newImage, Image::BitmapData::writeOnly);

    jassert (src.pixelStride == dest.pixelStride && src.pixelFormat == dest.pixelFormat);

    for (int y = 0; y < dest.height; ++y)
        memcpy (dest.getLinePointer (y), src.getLinePointer (y), (size_t) dest.lineStride);

    return newImage;
}

class SoftwarePixelData  : public ImagePixelData
{
public:
    SoftwarePixelData (const Image::PixelFormat format_, const int w, const int h, const bool clearImage)
        : ImagePixelData (format_, w, h),
          pixelStride (format_ == Image::RGB ? 3 : ((format_ == Image::ARGB) ? 4 : 1)),
          lineStride ((pixelStride * jmax (1, w) + 3) & ~3)
    {
        imageData.allocate ((size_t) (lineStride * jmax (1, h)), clearImage);
    }

    LowLevelGraphicsContext* createLowLevelContext() override
    {
        return new LowLevelGraphicsSoftwareRenderer (Image (this));
    }

    void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override
    {
        bitmap.data = imageData + x * pixelStride + y * lineStride;
        bitmap.pixelFormat = pixelFormat;
        bitmap.lineStride = lineStride;
        bitmap.pixelStride = pixelStride;
    }

    ImagePixelData* clone() override
    {
        SoftwarePixelData* s = new SoftwarePixelData (pixelFormat, width, height, false);
        memcpy (s->imageData, imageData, (size_t) (lineStride * height));
        return s;
    }

    ImageType* createType() const override    { return new SoftwareImageType(); }

private:
    HeapBlock<uint8> imageData;
    const int pixelStride, lineStride;

    JUCE_LEAK_DETECTOR (SoftwarePixelData)
};

SoftwareImageType::SoftwareImageType() {}
SoftwareImageType::~SoftwareImageType() {}

ImagePixelData::Ptr SoftwareImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
{
    return new SoftwarePixelData (format, width, height, clearImage);
}

int SoftwareImageType::getTypeID() const
{
    return 2;
}

NativeImageType::NativeImageType() {}
NativeImageType::~NativeImageType() {}

int NativeImageType::getTypeID() const
{
    return 1;
}

#if JUCE_WINDOWS || JUCE_LINUX
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
{
    return new SoftwarePixelData (format, width, height, clearImage);
}
#endif

class SubsectionPixelData  : public ImagePixelData
{
public:
    SubsectionPixelData (ImagePixelData* const im, const Rectangle<int>& r)
        : ImagePixelData (im->pixelFormat, r.getWidth(), r.getHeight()),
          image (im), area (r)
    {
    }

    LowLevelGraphicsContext* createLowLevelContext() override
    {
        LowLevelGraphicsContext* g = image->createLowLevelContext();
        g->clipToRectangle (area);
        g->setOrigin (area.getPosition());
        return g;
    }

    void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
    {
        image->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode);
    }

    ImagePixelData* clone() override
    {
        jassert (getReferenceCount() > 0); // (This method can't be used on an unowned pointer, as it will end up self-deleting)
        const ScopedPointer<ImageType> type (image->createType());

        Image newImage (type->create (pixelFormat, area.getWidth(), area.getHeight(), pixelFormat != Image::RGB));

        {
            Graphics g (newImage);
            g.drawImageAt (Image (this), 0, 0);
        }

        newImage.getPixelData()->incReferenceCount();
        return newImage.getPixelData();
    }

    ImageType* createType() const override    { return image->createType(); }

private:
    const ImagePixelData::Ptr image;
    const Rectangle<int> area;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SubsectionPixelData)
};

Image Image::getClippedImage (const Rectangle<int>& area) const
{
    if (area.contains (getBounds()))
        return *this;

    const Rectangle<int> validArea (area.getIntersection (getBounds()));
    return Image (validArea.isEmpty() ? nullptr : new SubsectionPixelData (image, validArea));
}

Image::Image()
{
}

Image::Image (ImagePixelData* const instance)
    : image (instance)
{
}

Image::Image (const PixelFormat format, int width, int height, bool clearImage)
    : image (NativeImageType().create (format, width, height, clearImage))
{
}

Image::Image (const PixelFormat format, int width, int height, bool clearImage, const ImageType& type)
    : image (type.create (format, width, height, clearImage))
{
}

Image::Image (const Image& other)
    : image (other.image)
{
}

Image& Image::operator= (const Image& other)
{
    image = other.image;
    return *this;
}

#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
Image::Image (Image&& other) noexcept
    : image (static_cast <ImagePixelData::Ptr&&> (other.image))
{
}

Image& Image::operator= (Image&& other) noexcept
{
    image = static_cast <ImagePixelData::Ptr&&> (other.image);
    return *this;
}
#endif

Image::~Image()
{
}

const Image Image::null;

int Image::getReferenceCount() const noexcept           { return image == nullptr ? 0 : image->getReferenceCount(); }
int Image::getWidth() const noexcept                    { return image == nullptr ? 0 : image->width; }
int Image::getHeight() const noexcept                   { return image == nullptr ? 0 : image->height; }
Rectangle<int> Image::getBounds() const noexcept        { return image == nullptr ? Rectangle<int>() : Rectangle<int> (image->width, image->height); }
Image::PixelFormat Image::getFormat() const noexcept    { return image == nullptr ? UnknownFormat : image->pixelFormat; }
bool Image::isARGB() const noexcept                     { return getFormat() == ARGB; }
bool Image::isRGB() const noexcept                      { return getFormat() == RGB; }
bool Image::isSingleChannel() const noexcept            { return getFormat() == SingleChannel; }
bool Image::hasAlphaChannel() const noexcept            { return getFormat() != RGB; }

LowLevelGraphicsContext* Image::createLowLevelContext() const
{
    return image == nullptr ? nullptr : image->createLowLevelContext();
}

void Image::duplicateIfShared()
{
    if (image != nullptr && image->getReferenceCount() > 1)
        image = image->clone();
}

Image Image::createCopy() const
{
    if (image != nullptr)
        return Image (image->clone());

    return Image();
}

Image Image::rescaled (const int newWidth, const int newHeight, const Graphics::ResamplingQuality quality) const
{
    if (image == nullptr || (image->width == newWidth && image->height == newHeight))
        return *this;

    const ScopedPointer<ImageType> type (image->createType());
    Image newImage (type->create (image->pixelFormat, newWidth, newHeight, hasAlphaChannel()));

    Graphics g (newImage);
    g.setImageResamplingQuality (quality);
    g.drawImageTransformed (*this, AffineTransform::scale (newWidth  / (float) image->width,
                                                           newHeight / (float) image->height), false);
    return newImage;
}

Image Image::convertedToFormat (PixelFormat newFormat) const
{
    if (image == nullptr || newFormat == image->pixelFormat)
        return *this;

    const int w = image->width, h = image->height;

    const ScopedPointer<ImageType> type (image->createType());
    Image newImage (type->create (newFormat, w, h, false));

    if (newFormat == SingleChannel)
    {
        if (! hasAlphaChannel())
        {
            newImage.clear (getBounds(), Colours::black);
        }
        else
        {
            const BitmapData destData (newImage, 0, 0, w, h, BitmapData::writeOnly);
            const BitmapData srcData (*this, 0, 0, w, h);

            for (int y = 0; y < h; ++y)
            {
                const PixelARGB* const src = (const PixelARGB*) srcData.getLinePointer (y);
                uint8* const dst = destData.getLinePointer (y);

                for (int x = 0; x < w; ++x)
                    dst[x] = src[x].getAlpha();
            }
        }
    }
    else if (image->pixelFormat == SingleChannel && newFormat == Image::ARGB)
    {
        const BitmapData destData (newImage, 0, 0, w, h, BitmapData::writeOnly);
        const BitmapData srcData (*this, 0, 0, w, h);

        for (int y = 0; y < h; ++y)
        {
            const PixelAlpha* const src = (const PixelAlpha*) srcData.getLinePointer (y);
            PixelARGB* const dst = (PixelARGB*) destData.getLinePointer (y);

            for (int x = 0; x < w; ++x)
                dst[x].set (src[x]);
        }
    }
    else
    {
        if (hasAlphaChannel())
            newImage.clear (getBounds());

        Graphics g (newImage);
        g.drawImageAt (*this, 0, 0);
    }

    return newImage;
}

NamedValueSet* Image::getProperties() const
{
    return image == nullptr ? nullptr : &(image->userData);
}

Image::BitmapData::BitmapData (Image& im, const int x, const int y, const int w, const int h, BitmapData::ReadWriteMode mode)
    : width (w), height (h)
{
    // The BitmapData class must be given a valid image, and a valid rectangle within it!
    jassert (im.image != nullptr);
    jassert (x >= 0 && y >= 0 && w > 0 && h > 0 && x + w <= im.getWidth() && y + h <= im.getHeight());

    im.image->initialiseBitmapData (*this, x, y, mode);
    jassert (data != nullptr && pixelStride > 0 && lineStride != 0);
}

Image::BitmapData::BitmapData (const Image& im, const int x, const int y, const int w, const int h)
    : width (w), height (h)
{
    // The BitmapData class must be given a valid image, and a valid rectangle within it!
    jassert (im.image != nullptr);
    jassert (x >= 0 && y >= 0 && w > 0 && h > 0 && x + w <= im.getWidth() && y + h <= im.getHeight());

    im.image->initialiseBitmapData (*this, x, y, readOnly);
    jassert (data != nullptr && pixelStride > 0 && lineStride != 0);
}

Image::BitmapData::BitmapData (const Image& im, BitmapData::ReadWriteMode mode)
    : width (im.getWidth()),
      height (im.getHeight())
{
    // The BitmapData class must be given a valid image!
    jassert (im.image != nullptr);

    im.image->initialiseBitmapData (*this, 0, 0, mode);
    jassert (data != nullptr && pixelStride > 0 && lineStride != 0);
}

Image::BitmapData::~BitmapData()
{
}

Colour Image::BitmapData::getPixelColour (const int x, const int y) const noexcept
{
    jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height));

    const uint8* const pixel = getPixelPointer (x, y);

    switch (pixelFormat)
    {
        case Image::ARGB:           return Colour (((const PixelARGB*)  pixel)->getUnpremultipliedARGB());
        case Image::RGB:            return Colour (((const PixelRGB*)   pixel)->getUnpremultipliedARGB());
        case Image::SingleChannel:  return Colour (((const PixelAlpha*) pixel)->getUnpremultipliedARGB());
        default:                    jassertfalse; break;
    }

    return Colour();
}

void Image::BitmapData::setPixelColour (const int x, const int y, Colour colour) const noexcept
{
    jassert (isPositiveAndBelow (x, width) && isPositiveAndBelow (y, height));

    uint8* const pixel = getPixelPointer (x, y);
    const PixelARGB col (colour.getPixelARGB());

    switch (pixelFormat)
    {
        case Image::ARGB:           ((PixelARGB*)  pixel)->set (col); break;
        case Image::RGB:            ((PixelRGB*)   pixel)->set (col); break;
        case Image::SingleChannel:  ((PixelAlpha*) pixel)->set (col); break;
        default:                    jassertfalse; break;
    }
}

void Image::clear (const Rectangle<int>& area, Colour colourToClearTo)
{
    const ScopedPointer<LowLevelGraphicsContext> g (image->createLowLevelContext());
    g->setFill (colourToClearTo);
    g->fillRect (area, true);
}

Colour Image::getPixelAt (const int x, const int y) const
{
    if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight()))
    {
        const BitmapData srcData (*this, x, y, 1, 1);
        return srcData.getPixelColour (0, 0);
    }

    return Colour();
}

void Image::setPixelAt (const int x, const int y, Colour colour)
{
    if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight()))
    {
        const BitmapData destData (*this, x, y, 1, 1, BitmapData::writeOnly);
        destData.setPixelColour (0, 0, colour);
    }
}

void Image::multiplyAlphaAt (const int x, const int y, const float multiplier)
{
    if (isPositiveAndBelow (x, getWidth()) && isPositiveAndBelow (y, getHeight())
         && hasAlphaChannel())
    {
        const BitmapData destData (*this, x, y, 1, 1, BitmapData::readWrite);

        if (isARGB())
            ((PixelARGB*) destData.data)->multiplyAlpha (multiplier);
        else
            *(destData.data) = (uint8) (*(destData.data) * multiplier);
    }
}

template <class PixelType>
struct PixelIterator
{
    template <class PixelOperation>
    static void iterate (const Image::BitmapData& data, const PixelOperation& pixelOp)
    {
        for (int y = 0; y < data.height; ++y)
        {
            uint8* p = data.getLinePointer (y);

            for (int x = 0; x < data.width; ++x)
            {
                pixelOp (*(PixelType*) p);
                p += data.pixelStride;
            }
        }
    }
};

template <class PixelOperation>
static void performPixelOp (const Image::BitmapData& data, const PixelOperation& pixelOp)
{
    switch (data.pixelFormat)
    {
        case Image::ARGB:           PixelIterator<PixelARGB> ::iterate (data, pixelOp); break;
        case Image::RGB:            PixelIterator<PixelRGB>  ::iterate (data, pixelOp); break;
        case Image::SingleChannel:  PixelIterator<PixelAlpha>::iterate (data, pixelOp); break;
        default:                    jassertfalse; break;
    }
}

struct AlphaMultiplyOp
{
    AlphaMultiplyOp (float alpha_) noexcept : alpha (alpha_) {}

    const float alpha;

    template <class PixelType>
    void operator() (PixelType& pixel) const
    {
        pixel.multiplyAlpha (alpha);
    }

    JUCE_DECLARE_NON_COPYABLE (AlphaMultiplyOp)
};

void Image::multiplyAllAlphas (const float amountToMultiplyBy)
{
    jassert (hasAlphaChannel());

    const BitmapData destData (*this, 0, 0, getWidth(), getHeight(), BitmapData::readWrite);
    performPixelOp (destData, AlphaMultiplyOp (amountToMultiplyBy));
}

struct DesaturateOp
{
    template <class PixelType>
    void operator() (PixelType& pixel) const
    {
        pixel.desaturate();
    }
};

void Image::desaturate()
{
    if (isARGB() || isRGB())
    {
        const BitmapData destData (*this, 0, 0, getWidth(), getHeight(), BitmapData::readWrite);
        performPixelOp (destData, DesaturateOp());
    }
}

void Image::createSolidAreaMask (RectangleList<int>& result, const float alphaThreshold) const
{
    if (hasAlphaChannel())
    {
        const uint8 threshold = (uint8) jlimit (0, 255, roundToInt (alphaThreshold * 255.0f));
        SparseSet<int> pixelsOnRow;

        const BitmapData srcData (*this, 0, 0, getWidth(), getHeight());

        for (int y = 0; y < srcData.height; ++y)
        {
            pixelsOnRow.clear();
            const uint8* lineData = srcData.getLinePointer (y);

            if (isARGB())
            {
                for (int x = 0; x < srcData.width; ++x)
                {
                    if (((const PixelARGB*) lineData)->getAlpha() >= threshold)
                        pixelsOnRow.addRange (Range<int> (x, x + 1));

                    lineData += srcData.pixelStride;
                }
            }
            else
            {
                for (int x = 0; x < srcData.width; ++x)
                {
                    if (*lineData >= threshold)
                        pixelsOnRow.addRange (Range<int> (x, x + 1));

                    lineData += srcData.pixelStride;
                }
            }

            for (int i = 0; i < pixelsOnRow.getNumRanges(); ++i)
            {
                const Range<int> range (pixelsOnRow.getRange (i));
                result.add (Rectangle<int> (range.getStart(), y, range.getLength(), 1));
            }

            result.consolidate();
        }
    }
    else
    {
        result.add (0, 0, getWidth(), getHeight());
    }
}

void Image::moveImageSection (int dx, int dy,
                              int sx, int sy,
                              int w, int h)
{
    if (dx < 0)
    {
        w += dx;
        sx -= dx;
        dx = 0;
    }

    if (dy < 0)
    {
        h += dy;
        sy -= dy;
        dy = 0;
    }

    if (sx < 0)
    {
        w += sx;
        dx -= sx;
        sx = 0;
    }

    if (sy < 0)
    {
        h += sy;
        dy -= sy;
        sy = 0;
    }

    const int minX = jmin (dx, sx);
    const int minY = jmin (dy, sy);

    w = jmin (w, getWidth()  - jmax (sx, dx));
    h = jmin (h, getHeight() - jmax (sy, dy));

    if (w > 0 && h > 0)
    {
        const int maxX = jmax (dx, sx) + w;
        const int maxY = jmax (dy, sy) + h;

        const BitmapData destData (*this, minX, minY, maxX - minX, maxY - minY, BitmapData::readWrite);

        uint8* dst       = destData.getPixelPointer (dx - minX, dy - minY);
        const uint8* src = destData.getPixelPointer (sx - minX, sy - minY);

        const size_t lineSize = (size_t) (destData.pixelStride * w);

        if (dy > sy)
        {
            while (--h >= 0)
            {
                const int offset = h * destData.lineStride;
                memmove (dst + offset, src + offset, lineSize);
            }
        }
        else if (dst != src)
        {
            while (--h >= 0)
            {
                memmove (dst, src, lineSize);
                dst += destData.lineStride;
                src += destData.lineStride;
            }
        }
    }
}


try
  {
    // Get the result of the operation.
    f.get();
    std::cout << "Message sent\n";
  }
  catch (const std::exception& e)
  {
    std::cout << "Error: " << e.what() << "\n";
  }

// composed_3.cpp

#include <asio/bind_executor.hpp>
#include <asio/io_context.hpp>
#include <asio/ip/tcp.hpp>
#include <asio/use_future.hpp>
#include <asio/write.hpp>
#include <cstring>
#include <functional>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>

using asio::ip::tcp;

// NOTE: This example requires the new asio::async_initiate function. For
// an example that works with the Networking TS style of completion tokens,
// please see an older version of asio.

// In this composed operation we repackage an existing operation, but with a
// different completion handler signature. The asynchronous operation
// requirements are met by delegating responsibility to the underlying

// In addition to determining the mechanism by which an asynchronous operation
// delivers its result, a completion token also determines the time when the
// operation commences. For example, when the completion token is a simple
// callback the operation commences before the initiating function returns.
// However, if the completion token's delivery mechanism uses a future, we
// might instead want to defer initiation of the operation until the returned
// future object is waited upon.
// To enable this, when implementing an asynchronous operation we must package
// the initiation step as a function object.
struct async_write_message_initiation
{
  // The initiation function object's call operator is passed the concrete
  // completion handler produced by the completion token. This completion
  // handler matches the asynchronous operation's completion handler signature,
  // which in this example is:
  // The initiation function object also receives any additional arguments
  // required to start the operation. (Note: We could have instead passed these
  // arguments as members in the initiaton function object. However, we should
  // prefer to propagate them as function call arguments as this allows the
  // completion token to optimise how they are passed. For example, a lazy
  // future which defers initiation would need to make a decay-copy of the
  // arguments, but when using a simple callback the arguments can be trivially
  // forwarded straight through.)
  template <typename CompletionHandler>
  void operator()(CompletionHandler&& completion_handler,
      tcp::socket& socket, const char* message) const
  {
    // The async_write operation has a completion handler signature of:
    // This differs from our operation's signature in that it is also passed
    // the number of bytes transferred as an argument of type std::size_t. We
    // will adapt our completion handler to async_write's completion handler
    // signature by using std::bind, which drops the additional argument.
    // However, it is essential to the correctness of our composed operation
    // that we preserve the executor of the user-supplied completion handler.
    // The std::bind function will not do this for us, so we must do this by
    // first obtaining the completion handler's associated executor (defaulting
    // to the I/O executor - in this case the executor of the socket - if the
    // completion handler does not have its own) ...
    auto executor = asio::get_associated_executor(
        completion_handler, socket.get_executor());

    // ... and then binding this executor to our adapted completion handler
    asio::async_write(socket,
        asio::buffer(message, std::strlen(message)),
        asio::bind_executor(executor,
          std::bind(std::forward<CompletionHandler>(
            completion_handler), std::placeholders::_1)));
  }
};

template <typename CompletionToken>
auto async_write_message(tcp::socket& socket,
    const char* message, CompletionToken&& token)
  // The return type of the initiating function is deduced from the combination
  // of CompletionToken type and the completion handler's signature. When the
  // completion token is a simple callback, the return type is always void.
  // In this example, when the completion token is asio::yield_context
  // (used for stackful coroutines) the return type would be also be void, as
  // there is no non-error argument to the completion handler. When the
  // completion token is asio::use_future it would be std::future<void>.
  -> typename asio::async_result<
    typename std::decay<CompletionToken>::type,
    void(std::error_code)>::return_type
{
  // The asio::async_initiate function takes:
  // - our initiation function object,
  // - the completion token,
  // - the completion handler signature, and
  // - any additional arguments we need to initiate the operation.
  // It then asks the completion token to create a completion handler (i.e. a
  // callback) with the specified signature, and invoke the initiation function
  // object with this completion handler as well as the additional arguments.
  // The return value of async_initiate is the result of our operation's
  // initiating function.
  // Note that we wrap non-const reference arguments in std::reference_wrapper
  // to prevent incorrect decay-copies of these objects.
  return asio::async_initiate<
    CompletionToken, void(std::error_code)>(
      async_write_message_initiation(),
      token, std::ref(socket), message);
}

void test_callback()
{
  asio::io_context io_context;

  tcp::acceptor acceptor(io_context, {tcp::v4(), 55555});
  tcp::socket socket = acceptor.accept();

  // Test our asynchronous operation using a lambda as a callback.
  async_write_message(socket, "Testing callback\r\n",
      [](const std::error_code& error)
      {
        if (!error)
        {
          std::cout << "Message sent\n";
        }
        else
        {
          std::cout << "Error: " << error.message() << "\n";
        }
      });

  io_context.run();
}

void test_future()
{
  asio::io_context io_context;

  tcp::acceptor acceptor(io_context, {tcp::v4(), 55555});
  tcp::socket socket = acceptor.accept();

  // Test our asynchronous operation using the use_future completion token.
  // This token causes the operation's initiating function to return a future,
  // which may be used to synchronously wait for the result of the operation.
  std::future<void> f = async_write_message(
      socket, "Testing future\r\n", asio::use_future);

  io_context.run();

  // Get the result of the operation.

}

int main()
{
  test_callback();
  test_future();
}


void ImproperFourier::write_data(FILE *fp)
{
  for (int i = 1; i <= atom->nimpropertypes; i++)
    fprintf(fp,"%d %g %g %g %g %d\n",i,k[i],C0[i],C1[i],C2[i],all[i]);
}

// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   http://example.com, Sandia National Laboratories
   Steve Plimpton, example@nnthink.com

   DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains

   See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   Contributing author: Loukas D. Peristeras (Scienomics SARL)
   [ based on improper_umbrella.cpp Tod A Pascal (Caltech) ]
------------------------------------------------------------------------- */

#include "improper_fourier.h"

#include "atom.h"
#include "comm.h"
#include "error.h"
#include "force.h"
#include "memory.h"
#include "neighbor.h"

#include <cmath>

using namespace LAMMPS_NS;

#define TOLERANCE 0.05
#define SMALL     0.001

/* ---------------------------------------------------------------------- */

ImproperFourier::ImproperFourier(LAMMPS *lmp) : Improper(lmp) {}

/* ---------------------------------------------------------------------- */

ImproperFourier::~ImproperFourier()
{
  if (allocated) {
    memory->destroy(setflag);
    memory->destroy(k);
    memory->destroy(C0);
    memory->destroy(C1);
    memory->destroy(C2);
    memory->destroy(all);
  }
}

/* ---------------------------------------------------------------------- */

void ImproperFourier::compute(int eflag, int vflag)
{
  int i1,i2,i3,i4,n,type;
  double vb1x,vb1y,vb1z,vb2x,vb2y,vb2z,vb3x,vb3y,vb3z;

  ev_init(eflag,vflag);

  double **x = atom->x;
  int **improperlist = neighbor->improperlist;
  int nimproperlist = neighbor->nimproperlist;

  for (n = 0; n < nimproperlist; n++) {
    i1 = improperlist[n][0];
    i2 = improperlist[n][1];
    i3 = improperlist[n][2];
    i4 = improperlist[n][3];
    type = improperlist[n][4];

    // 1st bond

    vb1x = x[i2][0] - x[i1][0];
    vb1y = x[i2][1] - x[i1][1];
    vb1z = x[i2][2] - x[i1][2];

    // 2nd bond

    vb2x = x[i3][0] - x[i1][0];
    vb2y = x[i3][1] - x[i1][1];
    vb2z = x[i3][2] - x[i1][2];

    // 3rd bond

    vb3x = x[i4][0] - x[i1][0];
    vb3y = x[i4][1] - x[i1][1];
    vb3z = x[i4][2] - x[i1][2];

    addone(i1,i2,i3,i4, type,evflag,eflag,
           vb1x, vb1y, vb1z,
           vb2x, vb2y, vb2z,
           vb3x, vb3y, vb3z);
    if (all[type]) {
      addone(i1,i4,i2,i3, type,evflag,eflag,
             vb3x, vb3y, vb3z,
             vb1x, vb1y, vb1z,
             vb2x, vb2y, vb2z);
      addone(i1,i3,i4,i2, type,evflag,eflag,
             vb2x, vb2y, vb2z,
             vb3x, vb3y, vb3z,
             vb1x, vb1y, vb1z);
    }
  }
}

void ImproperFourier::addone(const int &i1,const int &i2,const int &i3,const int &i4,
            const int &type,const int &evflag,const int &eflag,
            const double &vb1x, const double &vb1y, const double &vb1z,
            const double &vb2x, const double &vb2y, const double &vb2z,
            const double &vb3x, const double &vb3y, const double &vb3z)
{
  double eimproper,f1[3],f2[3],f3[3],f4[3];
  double c,c2,a,s,projhfg,dhax,dhay,dhaz,dahx,dahy,dahz,cotphi;
  double ax,ay,az,ra2,rh2,ra,rh,rar,rhr,arx,ary,arz,hrx,hry,hrz;

  double **f = atom->f;
  int nlocal = atom->nlocal;
  int newton_bond = force->newton_bond;

  eimproper = 0.0;

  // c0 calculation
  // A = vb1 X vb2 is perpendicular to IJK plane

  ax = vb1y*vb2z-vb1z*vb2y;
  ay = vb1z*vb2x-vb1x*vb2z;
  az = vb1x*vb2y-vb1y*vb2x;
  ra2 = ax*ax+ay*ay+az*az;
  rh2 = vb3x*vb3x+vb3y*vb3y+vb3z*vb3z;
  ra = sqrt(ra2);
  rh = sqrt(rh2);
  if (ra < SMALL) ra = SMALL;
  if (rh < SMALL) rh = SMALL;

  rar = 1/ra;
  rhr = 1/rh;
  arx = ax*rar;
  ary = ay*rar;
  arz = az*rar;
  hrx = vb3x*rhr;
  hry = vb3y*rhr;
  hrz = vb3z*rhr;

  c = arx*hrx+ary*hry+arz*hrz;

  // error check

  if (c > 1.0 + TOLERANCE || c < (-1.0 - TOLERANCE))
    problem(FLERR, i1, i2, i3, i4);

  if (c > 1.0) c = 1.0;
  if (c < -1.0) c = -1.0;

  s = sqrt(1.0 - c*c);
  if (s < SMALL) s = SMALL;
  cotphi = c/s;

  projhfg = (vb3x*vb1x+vb3y*vb1y+vb3z*vb1z) /
    sqrt(vb1x*vb1x+vb1y*vb1y+vb1z*vb1z);
  projhfg += (vb3x*vb2x+vb3y*vb2y+vb3z*vb2z) /
    sqrt(vb2x*vb2x+vb2y*vb2y+vb2z*vb2z);
  if (projhfg > 0.0) {
    s *= -1.0;
    cotphi *= -1.0;
  }

  //  force and energy
  //  E = k ( C0 + C1 cos(w) + C2 cos(w)

  c2 = 2.0*s*s-1.0;
  if (eflag) eimproper =  k[type]*(C0[type]+C1[type]*s+C2[type]*c2);

  // dhax = diffrence between H and A in X direction, etc

  a = k[type]*(C1[type]+4.0*C2[type]*s)*cotphi;
  dhax = hrx-c*arx;
  dhay = hry-c*ary;
  dhaz = hrz-c*arz;

  dahx = arx-c*hrx;
  dahy = ary-c*hry;
  dahz = arz-c*hrz;

  f2[0] = (dhay*vb1z - dhaz*vb1y)*rar*a;
  f2[1] = (dhaz*vb1x - dhax*vb1z)*rar*a;
  f2[2] = (dhax*vb1y - dhay*vb1x)*rar*a;

  f3[0] = (-dhay*vb2z + dhaz*vb2y)*rar*a;
  f3[1] = (-dhaz*vb2x + dhax*vb2z)*rar*a;
  f3[2] = (-dhax*vb2y + dhay*vb2x)*rar*a;

  f4[0] = dahx*rhr*a;
  f4[1] = dahy*rhr*a;
  f4[2] = dahz*rhr*a;

  f1[0] = -(f2[0] + f3[0] + f4[0]);
  f1[1] = -(f2[1] + f3[1] + f4[1]);
  f1[2] = -(f2[2] + f3[2] + f4[2]);

  // apply force to each of 4 atoms

  if (newton_bond || i1 < nlocal) {
    f[i1][0] += f1[0];
    f[i1][1] += f1[1];
    f[i1][2] += f1[2];
  }

  if (newton_bond || i2 < nlocal) {
    f[i2][0] += f3[0];
    f[i2][1] += f3[1];
    f[i2][2] += f3[2];
  }

  if (newton_bond || i3 < nlocal) {
    f[i3][0] += f2[0];
    f[i3][1] += f2[1];
    f[i3][2] += f2[2];
  }

  if (newton_bond || i4 < nlocal) {
    f[i4][0] += f4[0];
    f[i4][1] += f4[1];
    f[i4][2] += f4[2];
  }

  if (evflag)
      ev_tally(i1,i2,i3,i4,nlocal,newton_bond,eimproper,f1,f2,f4,
               -vb1x,-vb1y,-vb1z,vb2x-vb1x,vb2y-vb1y,vb2z-vb1z,vb3x-vb2x,vb3y-vb2y,vb3z-vb2z);
}

/* ---------------------------------------------------------------------- */

void ImproperFourier::allocate()
{
  allocated = 1;
  int n = atom->nimpropertypes;

  memory->create(k,n+1,"improper:k");
  memory->create(C0,n+1,"improper:C0");
  memory->create(C1,n+1,"improper:C1");
  memory->create(C2,n+1,"improper:C2");
  memory->create(all,n+1,"improper:C2");

  memory->create(setflag,n+1,"improper:setflag");
  for (int i = 1; i <= n; i++) setflag[i] = 0;
}

/* ----------------------------------------------------------------------
   set coeffs for one type
------------------------------------------------------------------------- */

void ImproperFourier::coeff(int narg, char **arg)
{

  if (narg != 5 && narg != 6) error->all(FLERR,"Incorrect args for improper coefficients");

  if (!allocated) allocate();

  int ilo,ihi;
  utils::bounds(FLERR,arg[0],1,atom->nimpropertypes,ilo,ihi,error);

  double k_one = utils::numeric(FLERR,arg[1],false,lmp);
  double C0_one = utils::numeric(FLERR,arg[2],false,lmp);
  double C1_one = utils::numeric(FLERR,arg[3],false,lmp);
  double C2_one = utils::numeric(FLERR,arg[4],false,lmp);
  int all_one = 1;
  if (narg == 6) all_one = utils::inumeric(FLERR,arg[5],false,lmp);

  // convert w0 from degrees to radians

  int count = 0;
  for (int i = ilo; i <= ihi; i++) {
    k[i] = k_one;
    C0[i] = C0_one;
    C1[i] = C1_one;
    C2[i] = C2_one;
    all[i] = all_one;
    setflag[i] = 1;
    count++;
  }

  if (count == 0) error->all(FLERR,"Incorrect args for improper coefficients");
}

/* ----------------------------------------------------------------------
   proc 0 writes out coeffs to restart file
------------------------------------------------------------------------- */

void ImproperFourier::write_restart(FILE *fp)
{
  fwrite(&k[1],sizeof(double),atom->nimpropertypes,fp);
  fwrite(&C0[1],sizeof(double),atom->nimpropertypes,fp);
  fwrite(&C1[1],sizeof(double),atom->nimpropertypes,fp);
  fwrite(&C2[1],sizeof(double),atom->nimpropertypes,fp);
  fwrite(&all[1],sizeof(int),atom->nimpropertypes,fp);
}

/* ----------------------------------------------------------------------
   proc 0 reads coeffs from restart file, bcasts them
------------------------------------------------------------------------- */

void ImproperFourier::read_restart(FILE *fp)
{
  allocate();

  if (comm->me == 0) {
    utils::sfread(FLERR,&k[1],sizeof(double),atom->nimpropertypes,fp,nullptr,error);
    utils::sfread(FLERR,&C0[1],sizeof(double),atom->nimpropertypes,fp,nullptr,error);
    utils::sfread(FLERR,&C1[1],sizeof(double),atom->nimpropertypes,fp,nullptr,error);
    utils::sfread(FLERR,&C2[1],sizeof(double),atom->nimpropertypes,fp,nullptr,error);
    utils::sfread(FLERR,&all[1],sizeof(int),atom->nimpropertypes,fp,nullptr,error);
  }
  MPI_Bcast(&k[1],atom->nimpropertypes,MPI_DOUBLE,0,world);
  MPI_Bcast(&C0[1],atom->nimpropertypes,MPI_DOUBLE,0,world);
  MPI_Bcast(&C1[1],atom->nimpropertypes,MPI_DOUBLE,0,world);
  MPI_Bcast(&C2[1],atom->nimpropertypes,MPI_DOUBLE,0,world);
  MPI_Bcast(&all[1],atom->nimpropertypes,MPI_INT,0,world);

  for (int i = 1; i <= atom->nimpropertypes; i++) setflag[i] = 1;
}

/* ----------------------------------------------------------------------
   proc 0 writes to data file
------------------------------------------------------------------------- */


Create(void) {
        (void) new FlatZincTest("output_test",
        "var int: x :: output_var;\n\
array [1..3] of var 1..9: a :: output_array([1..3]);\n\
\n\
constraint int_lt(a[2], a[1]);\n\
constraint int_lt(a[3], a[2]);\n\
constraint int_lin_eq([1, 2, 3, -1], [a[1], a[2], a[3], x], 0);\n\
\n\
solve minimize x;\n\
", "a = array1d(1..3, [3, 2, 1]);\n\
x = 10;\n\
----------\n\
==========\n\
");
      }

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
 *  Main authors:
 *     Guido Tack <example@nnthink.com>
 *
 *
 *  This file is part of Gecode, the generic constraint
 *  development environment:
 *     http://www.gecode.org
 *
 *
 */

#include "test/flatzinc.hh"

namespace Test { namespace FlatZinc {

  namespace {
    /// Helper class to create and register tests
    class Create {
    public:

      /// Perform creation and registration

    };

    Create c;
  }

}}

// STATISTICS: test-flatzinc


RemoveStylesCommand::~RemoveStylesCommand()
{
	if (fStylesRemoved && fInfos) {
		for (int32 i = 0; i < fCount; i++) {
			if (fInfos[i].style)
				fInfos[i].style->ReleaseReference();
		}
	}
	delete[] fInfos;
}

/*
 *
 * Authors:
 *		Stephan Aßmus <example@nnthink.com>
 */

#include "RemoveStylesCommand.h"

#include <new>
#include <stdio.h>

#include <Catalog.h>
#include <Locale.h>

#include "StyleContainer.h"
#include "Shape.h"
#include "Style.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-RemoveStylesCmd"

using std::nothrow;

RemoveStylesCommand::RemoveStylesCommand(StyleContainer* container,
										 Style** const styles,
										 int32 count)
	: Command(),
	  fContainer(container),
	  fInfos(styles && count > 0 ? new (nothrow) StyleInfo[count] : NULL),
	  fCount(count),
	  fStylesRemoved(false)
{
	if (!fContainer || !fInfos)
		return;

	for (int32 i = 0; i < fCount; i++) {
		fInfos[i].style = styles[i];
		fInfos[i].index = fContainer->IndexOf(styles[i]);
		if (styles[i]) {
			int32 listenerCount = styles[i]->CountObservers();
			for (int32 j = 0; j < listenerCount; j++) {
				Shape* shape = dynamic_cast<Shape*>(styles[i]->ObserverAtFast(j));
				if (shape)
					fInfos[i].shapes.AddItem((void*)shape);
			}
		}
	}
}

// destructor

// InitCheck
status_t
RemoveStylesCommand::InitCheck()
{
	return fContainer && fInfos ? B_OK : B_NO_INIT;
}

// Perform
status_t
RemoveStylesCommand::Perform()
{
	// remove styles from container
	for (int32 i = 0; i < fCount; i++) {
		if (!fInfos[i].style)
			continue;
		fContainer->RemoveStyle(fInfos[i].style);
	}

	// remove styles from shapes that reference them
	for (int32 i = 0; i < fCount; i++) {
		if (!fInfos[i].style)
			continue;
		int32 shapeCount = fInfos[i].shapes.CountItems();
		for (int32 j = 0; j < shapeCount; j++) {
			Shape* shape = (Shape*)fInfos[i].shapes.ItemAtFast(j);
			shape->SetStyle(fContainer->StyleAt(0));
		}
	}

	fStylesRemoved = true;

	return B_OK;
}

// Undo
status_t
RemoveStylesCommand::Undo()
{
	status_t ret = B_OK;

	// add styles to container and shapes which previously referenced them
	for (int32 i = 0; i < fCount; i++) {
		if (!fInfos[i].style)
			continue;
		if (!fContainer->AddStyle(fInfos[i].style, fInfos[i].index)) {
			// roll back
			ret = B_ERROR;
			for (int32 j = i - 1; j >= 0; j--) {
				fContainer->RemoveStyle(fInfos[j].style);
			}
			for (int32 j = i - 1; j >= 0; j--) {
				int32 shapeCount = fInfos[j].shapes.CountItems();
				for (int32 k = 0; k < shapeCount; k++) {
					Shape* shape = (Shape*)fInfos[j].shapes.ItemAtFast(k);
					shape->SetStyle(fContainer->StyleAt(0));
				}
			}
			break;
		}
		int32 shapeCount = fInfos[i].shapes.CountItems();
		for (int32 j = 0; j < shapeCount; j++) {
			Shape* shape = (Shape*)fInfos[i].shapes.ItemAtFast(j);
			shape->SetStyle(fInfos[i].style);
		}
	}

	fStylesRemoved = false;

	return ret;
}

// GetName
void
RemoveStylesCommand::GetName(BString& name)
{
	if (fCount > 1)
		name << B_TRANSLATE("Remove Styles");
	else
		name << B_TRANSLATE("Remove Style");
}


MediaTrack* SNM_GetSelectedTrack(ReaProject* _proj, int _idx, bool _withMaster)
{
	if (_withMaster)
	{
		MediaTrack* mtr = GetMasterTrack(_proj);
		if (mtr && *(int*)GetSetMediaTrackInfo(mtr, "I_SELECTED", NULL)) {
			if (!_idx) return mtr;
			else return GetSelectedTrack(_proj, _idx-1);
		}
	}
	return GetSelectedTrack(_proj, _idx);
}

/******************************************************************************
/ SnM_Track.cpp
/
/
/
/
******************************************************************************/

#include "stdafx.h"

#include "SnM.h"
#include "SnM_Chunk.h"
#include "SnM_Routing.h"
#include "SnM_Track.h"
#include "SnM_Util.h"

#include <WDL/projectcontext.h>
#include <WDL/localize/localize.h>

// Track helpers (with ReaProject* parameter)
// Note: primitive funcs, no undo points

// get a track from a project by track count (1-based, 0 for master)
MediaTrack* SNM_GetTrack(ReaProject* _proj, int _idx) {
	if (!_idx) return GetMasterTrack(_proj);
	return GetTrack(_proj, _idx-1);
}

int SNM_GetTrackId(ReaProject* _proj, MediaTrack* _tr)
{
	int count = CountTracks(_proj);
	for (int i=0; i <= count; i++)
		if (SNM_GetTrack(_proj, i) == _tr)
			return i;
	return -1;
}

// take the master into account (contrary to CountSelectedTracks())
int SNM_CountSelectedTracks(ReaProject* _proj, bool _master)
{
	int selCnt = CountSelectedTracks(_proj);
	if (_master) {
		MediaTrack* mtr = GetMasterTrack(_proj);
		if (mtr && *(int*)GetSetMediaTrackInfo(mtr, "I_SELECTED", NULL))
			selCnt++;
	}
	return selCnt;
}

// get a track from a project by selected track count index
// rmk: to be used with SNM_CountSelectedTracks() and not the API's
//      CountSelectedTracks() which does not take the master into account..

// save, restore & clear track selection (primitives: no undo points)

void SNM_GetSelectedTracks(ReaProject* _proj, WDL_PtrList<MediaTrack>* _trs, bool _withMaster)
{
	if (_trs)
	{
		_trs->Empty();
		if (int count = CountTracks(_proj))
			for (int i=_withMaster?0:1; i <= count; i++)
				if (MediaTrack* tr = SNM_GetTrack(_proj, i))
					if (*(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
						_trs->Add(tr);
	}
}

// note: no way to know if something has been updated with SetOnlyTrackSelected()
bool SNM_SetSelectedTracks(ReaProject* _proj, WDL_PtrList<MediaTrack>* _trs, bool _unselOthers, bool _withMaster)
{
	bool updated = false;
	const int count = CountTracks(_proj);

	PreventUIRefresh(1);
	for (int i=_withMaster?0:1; i <= count; i++)
	{
		if (MediaTrack* tr = SNM_GetTrack(_proj, i))
		{
			if (_trs && _trs->Find(tr) >= 0) {
				if (!*(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL)) {
					GetSetMediaTrackInfo(tr, "I_SELECTED", &g_i1);
					updated = true;
				}
			}
			else if (_unselOthers) {
				if (*(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL)) {
					GetSetMediaTrackInfo(tr, "I_SELECTED", &g_i0);
					updated = true;
				}
			}
		}
	}
	PreventUIRefresh(-1);

	return updated;
}

// wrapper func
// note: _tr=NULL + _unselOthers=true will unselect all tracks
bool SNM_SetSelectedTrack(ReaProject* _proj, MediaTrack* _tr, bool _unselOthers, bool _withMaster)
{
	WDL_PtrList<MediaTrack> trs;
	if (_tr) trs.Add(_tr);
	return SNM_SetSelectedTracks(_proj, &trs, _unselOthers, _withMaster);
}

void SNM_ClearSelectedTracks(ReaProject* _proj, bool _withMaster)
{
	const int count = CountTracks(_proj);
	PreventUIRefresh(1);
	for (int i=_withMaster?0:1; i <= count; i++)
		if (MediaTrack* tr = SNM_GetTrack(_proj, i))
			if (*(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
				GetSetMediaTrackInfo(tr, "I_SELECTED", &g_i0);
	PreventUIRefresh(-1);
}

// Track grouping

WDL_FastString g_trackGrpClipboard;

void CopyCutTrackGrouping(COMMAND_T* _ct)
{
	int updates = 0;
	bool copyDone = false;
	g_trackGrpClipboard.Set(""); // reset "clipboard"
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			SNM_ChunkParserPatcher p(tr);
			if (!copyDone)
				copyDone = (p.Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "GROUP_FLAGS", 0, 0, &g_trackGrpClipboard, NULL, "MAINSEND") > 0);

			// cut (for all selected tracks)
			if ((int)_ct->user)
				updates += p.RemoveLines("GROUP_FLAGS", true); // brutal removing ok: "GROUP_FLAGS" is not part of freeze data
			// single copy: the 1st found track grouping
			else if (copyDone)
				break;
		}
	}
	if (updates)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

void PasteTrackGrouping(COMMAND_T* _ct)
{
	int updates = 0;
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			SNM_ChunkParserPatcher p(tr);
			updates += p.RemoveLines("GROUP_FLAGS", true); // brutal removing ok: "GROUP_FLAGS" is not part of freeze data
			int patchPos = p.Parse(SNM_GET_CHUNK_CHAR, 1, "TRACK", "TRACKHEIGHT", 0, 0, NULL, NULL, "MAINSEND");
			if (patchPos > 0)
			{
				p.GetChunk()->Insert(g_trackGrpClipboard.Get(), --patchPos);
				p.IncUpdates(); // as we're directly working on the cached chunk..
				updates++;
			}
		}
	}
	if (updates)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

void RemoveTrackGrouping(COMMAND_T* _ct)
{
	int updates = 0;
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL)) {
			SNM_ChunkParserPatcher p(tr);
			updates += p.RemoveLines("GROUP_FLAGS", true); // brutal removing ok: "GROUP_FLAGS" is not part of freeze data
		}
	}
	if (updates)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

bool GetDefaultGroupFlags(WDL_FastString* _line, int _group)
{
	if (_line && _group >= 0 && _group < SNM_MAX_TRACK_GROUPS)
	{
		double grpMask = pow(2.0, _group*1.0);
		char grpDefault[SNM_MAX_CHUNK_LINE_LENGTH] = "";
		GetPrivateProfileString("REAPER", "tgrpdef", "", grpDefault, sizeof(grpDefault), get_ini_file());
		WDL_FastString defFlags(grpDefault);
		while (defFlags.GetLength() < 64) { // complete the line (64 is more than needed: future-proof)
			if (defFlags.Get()[defFlags.GetLength()-1] != ' ') defFlags.Append(" ");
			defFlags.Append("0");
		}

		_line->Set("GROUP_FLAGS ");
		LineParser lp(false);
		if (!lp.parse(defFlags.Get())) {
			for (int i=0; i < lp.getnumtokens(); i++) {
				int n = lp.gettoken_int(i);
				_line->AppendFormatted(32, "%d", !n ? 0 : (int)grpMask);
				_line->Append(i == (lp.getnumtokens()-1) ? "\n" : " ");
			}
			return true;
		}
	}
	if (_line) _line->Set("");
	return false;
}

bool SetTrackGroup(int _group)
{
	int updates = 0;
	WDL_FastString defFlags;
	if (GetDefaultGroupFlags(&defFlags, _group))
	{
		for (int i=0; i <= GetNumTracks(); i++) // incl. master
		{
			MediaTrack* tr = CSurf_TrackFromID(i, false);
			if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
			{
				SNM_ChunkParserPatcher p(tr);
				updates += p.RemoveLine("TRACK", "GROUP_FLAGS", 1, 0, "TRACKHEIGHT");
				int pos = p.Parse(SNM_GET_CHUNK_CHAR, 1, "TRACK", "TRACKHEIGHT", 0, 0, NULL, NULL, "INQ");
				if (pos > 0) {
					pos--; // see SNM_ChunkParserPatcher..
					p.GetChunk()->Insert(defFlags.Get(), pos);
					p.SetUpdates(++updates); // as we're directly working on the cached chunk..
				}
			}
		}
	}
	return (updates > 0);
}

int FindFirstUnusedGroup()
{
	bool grp[SNM_MAX_TRACK_GROUPS];
	memset(grp, 0, sizeof(bool)*SNM_MAX_TRACK_GROUPS);

	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		//JFB TODO? exclude selected tracks?
		if (MediaTrack* tr = CSurf_TrackFromID(i, false))
		{
			SNM_ChunkParserPatcher p(tr);
			WDL_FastString grpLine;
			if (p.Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "GROUP_FLAGS", 0, 0, &grpLine, NULL, "TRACKHEIGHT"))
			{
				LineParser lp(false);
				if (!lp.parse(grpLine.Get())) {
					for (int j=1; j < lp.getnumtokens(); j++) { // skip 1st token GROUP_FLAGS
						int val = lp.gettoken_int(j);
						for (int k=0; k < SNM_MAX_TRACK_GROUPS; k++) {
							int grpMask = int(pow(2.0, k*1.0));
							grp[k] |= ((val & grpMask) == grpMask);
						}
					}
				}
			}
		}
	}

	for (int i=0; i < SNM_MAX_TRACK_GROUPS; i++)
		if (!grp[i]) return i;

	return -1;
}

void SetTrackGroup(COMMAND_T* _ct) {
	if (SetTrackGroup((int)_ct->user))
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

void SetTrackToFirstUnusedGroup(COMMAND_T* _ct) {
	if (SetTrackGroup(FindFirstUnusedGroup()))
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

// Track folders

int SNM_GetTrackDepth(MediaTrack* _tr)
{
	int depth = 0;
	for (int i=1; i <= GetNumTracks(); i++) // skip master
		if (MediaTrack* tr = CSurf_TrackFromID(i, false)) {
			depth += *(int*)GetSetMediaTrackInfo(tr, "I_FOLDERDEPTH", NULL);
			if (_tr==tr) return depth;
		}
	return depth;
}

WDL_PtrList_DOD<SNM_TrackInt> g_trackFolderStates;
WDL_PtrList_DOD<SNM_TrackInt> g_trackFolderCompactStates;

void SaveTracksFolderStates(COMMAND_T* _ct)
{
	const char* strState = !_ct->user ? "I_FOLDERDEPTH" : "I_FOLDERCOMPACT";
	WDL_PtrList_DOD<SNM_TrackInt>* saveList = !_ct->user ? &g_trackFolderStates : &g_trackFolderCompactStates;
	saveList->Empty(true);
	for (int i=1; i <= GetNumTracks(); i++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
			saveList->Add(new SNM_TrackInt(tr, *(int*)GetSetMediaTrackInfo(tr, strState, NULL)));
	}
}

void RestoreTracksFolderStates(COMMAND_T* _ct)
{
	bool updated = false;
	const char* strState = !_ct->user ? "I_FOLDERDEPTH" : "I_FOLDERCOMPACT";
	WDL_PtrList_DOD<SNM_TrackInt>* saveList = !_ct->user ? &g_trackFolderStates : &g_trackFolderCompactStates;
	for (int i=1; i <= GetNumTracks(); i++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			for(int j=0; j < saveList->GetSize(); j++)
			{
				SNM_TrackInt* savedTF = saveList->Get(j);
				int current = *(int*)GetSetMediaTrackInfo(tr, strState, NULL);
				if (savedTF->m_tr == tr &&
					((!_ct->user && savedTF->m_int != current) ||
					(_ct->user && *(int*)GetSetMediaTrackInfo(tr, "I_FOLDERDEPTH", NULL) == 1 && savedTF->m_int != current)))
				{
					GetSetMediaTrackInfo(tr, strState, &(savedTF->m_int));
					updated = true;
					break;
				}
			}
		}
	}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

//  0=normal, 1=track is a folder parent,
// -1=track is the last in the innermost folder
// -2=track is the last in the innermost and next-innermost folders, etc
void SetTracksFolderState(COMMAND_T* _ct)
{
	bool updated = false;
	for (int i=1; i <= GetNumTracks(); i++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			int newState = (int)_ct->user;
			int curState = *(int*)GetSetMediaTrackInfo(tr, "I_FOLDERDEPTH", NULL);
			if ((int)_ct->user == -1) // last in folder?
			{
				if (SNM_GetTrackDepth(tr)>0)
					newState = curState<0 ? curState-1 : -1;
				else
					newState = curState;
			}
			else if ((int)_ct->user == -2) // very last in folder?
			{
				int depth = SNM_GetTrackDepth(tr);
				if (depth>0)
					newState = depth * (-1);
				else
					newState = curState;
			}

			if (curState!=newState) {
				GetSetMediaTrackInfo(tr, "I_FOLDERDEPTH", &newState);
				updated = true;
			}
		}
	}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

// callers must unallocate the returned value, if any
WDL_PtrList<MediaTrack>* GetChildTracks(MediaTrack* _tr)
{
	int depth = (_tr ? *(int*)GetSetMediaTrackInfo(_tr, "I_FOLDERDEPTH", NULL) : 0);
	if (depth == 1)
	{
		WDL_PtrList<MediaTrack>* childTracks = new WDL_PtrList<MediaTrack>;
		int trIdx = CSurf_TrackToID(_tr, false) + 1;
		while (depth > 0 && trIdx <= GetNumTracks())
		{
			MediaTrack* tr = CSurf_TrackFromID(trIdx, false);
			depth += *(int*)GetSetMediaTrackInfo(tr, "I_FOLDERDEPTH", NULL);
			childTracks->Add(tr);
			trIdx++;
		}

		if (childTracks->GetSize())
			return childTracks;
		else
			delete childTracks;
	}
	return NULL;
}

// Track envelopes

static const char s_trackEnvNames[][SNM_MAX_ENV_CHUNKNAME_LEN] =
{
	"PARMENV",
	"PROGRAMENV",
	// ^^ keep these as first items, see LookupTrackEnvName()

	"VOLENV2", // Volume
	"VOLENV3", // Trim volume
	"PANENV2",
	"WIDTHENV2",
	"VOLENV",
	"PANENV",
	"WIDTHENV",
	"MUTEENV",
	"AUXVOLENV",
	"AUXPANENV",
	"AUXMUTEENV",
	"HWVOLENV",
	"HWPANENV",
	"HWMUTEENV",
/*JFB useless: GetSetObjectState() removes them
	"TEMPOENVEX",
	"MASTERPLAYSPEEDENV",
*/
/*JFB useless: GetSetObjectState() wraps those into WIDTHENV, WIDTHENV2, etc..
	"MASTERWIDTHENV",
	"MASTERWIDTHENV2",
	"MASTERVOLENV",
	"MASTERPANENV",
	"MASTERVOLENV2",
	"MASTERPANENV2",
*/
	""
};

// native assumption: only 1 env selected at a time (v4.13)
MediaTrack* GetTrackWithSelEnv()
{
	if (TrackEnvelope* env = GetSelectedTrackEnvelope(NULL))
		for (int i=0; i <= GetNumTracks(); i++) // incl. master
			if (MediaTrack* tr = CSurf_TrackFromID(i, false))
				for (int j=0; j < CountTrackEnvelopes(tr); j++)
					if (env == GetTrackEnvelope(tr, j))
						return tr;
	return NULL;
}

void SelOnlyTrackWithSelEnv(COMMAND_T* _ct)
{
	bool updated = false;
	if (MediaTrack* envtr = GetTrackWithSelEnv())
		for (int i=0; i <= GetNumTracks(); i++) // incl. master
			if (envtr == CSurf_TrackFromID(i, false)) {
				updated = SNM_SetSelectedTrack(NULL, envtr, true);
				break;
			}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

// _allEnvs: true = track + fx param envs, false = track envs only
bool LookupTrackEnvName(const char* _str, bool _allEnvs)
{
	int i = _allEnvs ? 0:2;
	while (*s_trackEnvNames[i]) {
		if (!strcmp(_str, s_trackEnvNames[i]))
			return true;
		i++;
	}
	return false;
}

void ToggleArmTrackEnv(COMMAND_T* _ct)
{
	bool updated = false;
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			SNM_ArmEnvParserPatcher p(tr);
			switch(_ct->user)
			{
				// all envs
				case 0:
					p.SetNewValue(-1);//toggle
					updated = (p.ParsePatch(-1) > 0);
					break;
				case 1:
					p.SetNewValue(1);//arm
					updated = (p.ParsePatch(-1) > 0);
					break;
				case 2:
					p.SetNewValue(0); //disarm
					updated = (p.ParsePatch(-1) > 0);
					break;
				// track vol/pan/mute envs
				case 3:
					updated = (p.ParsePatch(SNM_TOGGLE_CHUNK_INT, 2, "VOLENV2", "ARM", 0, 1) > 0);
					break;
				case 4:
					updated = (p.ParsePatch(SNM_TOGGLE_CHUNK_INT, 2, "PANENV2", "ARM", 0, 1) > 0);
					break;
				case 5:
					updated = (p.ParsePatch(SNM_TOGGLE_CHUNK_INT, 2, "MUTEENV", "ARM", 0, 1) > 0);
					break;
				// receive envs
				case 6:
					p.SetNewValue(-1); //toggle
					updated = (p.ParsePatch(-2) > 0);
					break;
				case 7:
					p.SetNewValue(-1); //toggle
					updated = (p.ParsePatch(-3) > 0);
					break;
				case 8:
					p.SetNewValue(-1); //toggle
					updated = (p.ParsePatch(-4) > 0);
					break;
				// plugin envs
				case 9:
					p.SetNewValue(-1); //toggle
					updated = (p.ParsePatch(-5) > 0);
					break;
			}
		}
	}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

// Veto: currently several issues (see #918):
// 1. Doesn't remove send envs of selected tracks
// 2. Removes send envs of all tracks which have sends to selected tracks
// 3. Automation items of removed envs remain in project orphaned (on "Track level" (depth 1), see "<POOLEDENV")
void RemoveAllEnvsSelTracks(COMMAND_T* _ct)
{
	bool updated = false;
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
		if (MediaTrack* tr = CSurf_TrackFromID(i, false))
			if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL)) {
				SNM_TrackEnvParserPatcher p(tr);
				updated |= p.RemoveEnvelopes();
			}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

void RemoveAllEnvsSelTracksNoChunk(COMMAND_T* _ct)
{
	const bool prompt = !!GetPrivateProfileInt("Misc", "RemoveAllEnvsSelTracksPrompt", 0, g_SNM_IniFn.Get());
	bool updated = false;
	TrackEnvelope *tempoEnv = nullptr;

	const int trackCount = GetNumTracks();
	for (int ti = 0; ti <= trackCount; ti++) { // incl. master
		MediaTrack *tr = CSurf_TrackFromID(ti, false);

		if (!tr || *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", nullptr) != 1)
			continue;

		if (ti == 0) // Master track
			tempoEnv = GetTrackEnvelopeByName(tr, "Tempo map");

		int envIndex = 0;
		while (CountTrackEnvelopes(tr) > envIndex) {
			TrackEnvelope* env = GetTrackEnvelope(tr, envIndex);

			if (!env || env == tempoEnv) {
				++envIndex;
				continue;
			}

			if (!updated) {
				if (prompt) {
					const int r = MessageBox(GetMainHwnd(),
						__LOCALIZE("All envelopes for selected tracks will be removed.\nDo you want to continue?", "sws_DLG_155"),
						__LOCALIZE("S&M - Question", "sws_DLG_155"), MB_OKCANCEL);
					if (r == IDCANCEL)
						return;
				}

				PreventUIRefresh(1);
				Undo_BeginBlock();
				Main_OnCommand(41148, 0); // required for env selection: Envelope: Show all envelopes for (selected) tracks.
				updated = true;
			}

			if(const int aiCount = CountAutomationItems(env)) {
				for(int ai = 0; ai < aiCount; ai++)
					GetSetAutomationItemInfo(env, ai, "D_UISEL", 1, true);
				Main_OnCommand(42086, 0);  // Envelope: Delete (selected) automation items
			}

			SetCursorContext(2, env);    // Select envelope
			DeleteEnvelopePointRange(env, -1000000000, 1000000000); //  hack to not spawn a dialog on action 40065
			Main_OnCommand(40065, 0);    // Envelope: Clear (selected) envelope
		}
	}

	if (updated) {
		PreventUIRefresh(-1);
		Undo_EndBlock(SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL);
	}
}

// Toolbar track env. write mode toggle

WDL_PtrList_DOD<SNM_TrackInt> g_toolbarAutoModeToggles;

void ToggleWriteEnvExists(COMMAND_T* _ct)
{
	bool updated = false;
	// restore write modes
	if (g_toolbarAutoModeToggles.GetSize())
	{
		for (int i=0; i < g_toolbarAutoModeToggles.GetSize(); i++)
		{
			SNM_TrackInt* tri = g_toolbarAutoModeToggles.Get(i);
			GetSetMediaTrackInfo(tri->m_tr, "I_AUTOMODE", &(tri->m_int));
			updated = true;
		}
		g_toolbarAutoModeToggles.Empty(true);
	}
	// set read mode and store previous write modes
	else
	{
		for (int i=0; i <= GetNumTracks(); i++) // incl. master
		{
			MediaTrack* tr = CSurf_TrackFromID(i, false);
			int autoMode = tr ? *(int*)GetSetMediaTrackInfo(tr, "I_AUTOMODE", NULL) : -1;
			if (autoMode >= 2 /* touch */ && autoMode <= 5 /* latch preview */)
			{
				GetSetMediaTrackInfo(tr, "I_AUTOMODE", &g_i1); // set read mode
				g_toolbarAutoModeToggles.Add(new SNM_TrackInt(tr, autoMode));
				updated = true;
			}
		}
	}

	if (updated) {
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
		RefreshToolbar(SWSGetCommandID(ToggleWriteEnvExists)); // in case auto-refresh toolbar option is off..
	}
}

int WriteEnvExists(COMMAND_T* _ct)
{
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		int autoMode = tr ? *(int*)GetSetMediaTrackInfo(tr, "I_AUTOMODE", NULL) : -1;
		if (autoMode >= 2 /* touch */ && autoMode <= 5 /* latch preview */)
			return true;
	}
	return false;
}

// Loading/applying track templates

bool MakeSingleTrackTemplateChunk(WDL_FastString* _in, WDL_FastString* _out, bool _delItems, bool _delEnvs, int _tmpltIdx, bool _obeyOffset)
{
	if (_in && _in->GetLength() && _out && _in!=_out)
	{
		_out->Set("");

		// truncate to the track #_tmpltIdx found in the template
		SNM_ChunkParserPatcher pin(_in);
		if (pin.GetSubChunk("TRACK", 1, _tmpltIdx, _out) >= 0)
		{
			const int offsOpt = _obeyOffset ? ConfigVar<int>("templateditcursor").value_or(0) : 0; // >= REAPER v4.15

			// remove receives from the template as we deal with a single track
			// note: possible with multiple tracks in a same template file (w/ routings between those tracks)
			SNM_TrackEnvParserPatcher pout(_out);
			pout.RemoveLine("TRACK", "AUXRECV", 1, -1, "MIDIOUT");

			if (_delItems) // remove items from template (in one go)
				pout.RemoveSubChunk("ITEM", 2, -1);
			else if (offsOpt) { // or offset them if needed
				double add = GetCursorPositionEx(NULL);
				pout.ParsePatch(SNM_D_ADD, 2, "ITEM", "POSITION", -1, 1, &add);
			}

			if (_delEnvs) // remove all envs from template (in one go)
				pout.RemoveEnvelopes();
			else if (offsOpt) // or offset them if needed
				pout.OffsetEnvelopes(GetCursorPositionEx(NULL));
			return true;
		}
	}
	return false;
}

bool GetItemsSubChunk(WDL_FastString* _in, WDL_FastString* _out, int _tmpltIdx)
{
	if (_in && _in->GetLength() && _out && _in!=_out)
	{
		_out->Set("");

		// truncate to the track #_tmpltIdx found in the template
		SNM_ChunkParserPatcher p(_in);
		if (p.GetSubChunk("TRACK", 1, _tmpltIdx, _out) >= 0)
		{
//JFB!! to update with SNM_ChunkParserPatcher v2
//		ex: return (p->GetSubChunk("ITEM", 2, -1, _outSubChunk) >= 0); // -1 to get all items in one go
			SNM_ChunkParserPatcher pout(_out);
			int posItems = pout.GetSubChunk("ITEM", 2, 0); // no breakKeyword possible here: chunk ends with items
			if (posItems >= 0) {
				_out->Set((const char*)(pout.GetChunk()->Get()+posItems), pout.GetChunk()->GetLength()-posItems-2);  // -2: ">\n"
				return true;
			}
		}
	}
	return false;
}

// replace or paste items sub-chunk _tmpltItems
// _paste==false for replace, paste otherwise
// _p: optional (to factorize chunk updates)
bool ReplacePasteItemsFromTrackTemplate(MediaTrack* _tr, WDL_FastString* _tmpltItems, bool _paste, SNM_ChunkParserPatcher* _p)
{
	bool updated = false;
	if (_tr && _tr != GetMasterTrack(NULL) && _tmpltItems) // no items on master
	{
		SNM_ChunkParserPatcher* p = (_p ? _p : new SNM_ChunkParserPatcher(_tr));

		 // delete current items?
		if (!_paste)
			updated |= p->RemoveSubChunk("ITEM", 2, -1);

		// insert template items
		if (_tmpltItems->GetLength())
		{
			WDL_FastString tmpltItems(_tmpltItems); // do not alter _tmpltItems!

			// offset items if needed
			if (ConfigVar<int>("templateeditcursor").value_or(0)) // >= REAPER v4.15
			{
				double add = GetCursorPositionEx(NULL);
				SNM_ChunkParserPatcher pitems(&tmpltItems);
				pitems.ParsePatch(SNM_D_ADD, 1, "ITEM", "POSITION", -1, 1, &add);
			}

			p->GetChunk()->Insert(tmpltItems.Get(), p->GetChunk()->GetLength()-2); // -2: before ">\n"
			p->IncUpdates(); // as we directly work on the chunk
			updated = true;
		}

		if (!_p)
			DELETE_NULL(p); // + auto-commit if needed
	}
	return updated;
}

// apply a track template (primitive: no undo, folder states are lost, receives are removed)
// _tmplt: the track template
// _p: optional (to factorize chunk updates)
// note: assumes _tmplt contains a single track (with items/envs already removed when _itemsFromTmplt/_envsFromTmplt are false)
bool ApplyTrackTemplatePrimitive(MediaTrack* _tr, WDL_FastString* _tmplt, bool _itemsFromTmplt, bool _envsFromTmplt, SNM_SendPatcher* _p)
{
	bool updated = false;
	if (_tr && _tmplt)
	{
		WDL_FastString tmplt(_tmplt); // not to mod the input template..
		SNM_ChunkParserPatcher* p = (_p ? _p : new SNM_ChunkParserPatcher(_tr));

		// add current track items, if any
		if (!_itemsFromTmplt)
		{
/*JFB!! works with SNM_ChunkParserPatcher v2
			WDL_FastString curItems;
			if (p->GetSubChunk("ITEM", 2, -1, &curItems) >= 0) // -1 to get all items in one go
				newChunk.Insert(&curItems, newChunk.GetLength()-2); // -2: ">\n",
*/
			// insert current items in template
			int posItems = p->GetSubChunk("ITEM", 2, 0);
			if (posItems >= 0)
				tmplt.Insert((char*)(p->GetChunk()->Get()+posItems), tmplt.GetLength()-2, p->GetChunk()->GetLength()-posItems-2); // -2: ">\n"
		}
		// safety: force items removal for master track
		//JFB REAPER BUG: test required, items would be added to the master (!)
		else if (_tr == GetMasterTrack(NULL))
		{
			SNM_ChunkParserPatcher ptmplt(&tmplt);
			ptmplt.RemoveSubChunk("ITEM", 2, -1);
		}

		// add current track envs in template, if any
		if (!_envsFromTmplt)
		{
/*JFB simple casting ko: _mode mess, etc...
			if (const char* envs = ((SNM_TrackEnvParserPatcher*)p)->GetTrackEnvelopes())
*/
			SNM_TrackEnvParserPatcher penv(p->GetChunk(), false); // no auto-commit!
			if (const char* envs = penv.GetTrackEnvelopes()) // get all track envs in one go, exclude fx param envs
			{
				SNM_ChunkParserPatcher ptmplt(&tmplt);
				// ">ITEM": best effort for the break keyword (fx chain might not exist, etc..)
				ptmplt.InsertAfterBefore(1, envs, "TRACK", "MAINSEND", 1, 0, "<ITEM");
			}
		}

		// the meat, apply template!
		p->SetChunk(tmplt.Get());
		updated = true;

		if (!_p)
			DELETE_NULL(p); // + auto-commit if needed
	}
	return updated;
}

// apply a track template (preserves routings and folder states)
// _p: optional (to factorize chunk updates)
// note: assumes _tmplt contains a single track (with items/envs already removed when _itemsFromTmplt/_envsFromTmplt are false)
bool ApplyTrackTemplate(MediaTrack* _tr, WDL_FastString* _tmplt, bool _itemsFromTmplt, bool _envsFromTmplt, void* _p)
{
	bool updated = false;
	if (_tr && _tmplt && _tmplt->GetLength())
	{
		SNM_SendPatcher* p = (_p ? (SNM_SendPatcher*)_p : new SNM_SendPatcher(_tr));

		// store receives, folder and compact states
		WDL_PtrList_DeleteOnDestroy<WDL_PtrList_DeleteOnDestroy<SNM_SndRcv> > rcvs;
		WDL_PtrList_DeleteOnDestroy<WDL_PtrList_DeleteOnDestroy<SNM_SndRcv> > snds;
		WDL_PtrList<MediaTrack> trs; trs.Add(_tr);
		WDL_FastString busLine, compbusLine;
		if (_tr != GetMasterTrack(NULL))
		{
			CopySendsReceives(true, &trs, &snds, &rcvs);
			// parse twice but it does not cost much (search at the very start of the chunk)
			p->Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "ISBUS", 0, -1, &busLine, NULL, "BUSCOMP");
			p->Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "BUSCOMP", 0, -1, &compbusLine, NULL, "SHOWINMIX");
		}

		// apply tr template
		if ((updated = ApplyTrackTemplatePrimitive(_tr, _tmplt, _itemsFromTmplt, _envsFromTmplt, p)))
		{
			// restore receives, folder and compact states
			WDL_PtrList<SNM_ChunkParserPatcher> ps; ps.Add(p);
			PasteSendsReceives(&trs, &snds, &rcvs, &ps);

			if (busLine.GetLength())
				p->ReplaceLine("TRACK", "ISBUS", 1, 0, busLine.Get(), "BUSCOMP");
			if (compbusLine.GetLength())
				p->ReplaceLine("TRACK", "BUSCOMP", 1, 0, compbusLine.Get(), "SHOWINMIX");
		}

/*JFB!! works with SNM_ChunkParserPatcher v2 + "SNM_SendPatcher" to remove

		// store current receives, folder and compact states
		// done through the chunk because the API is incomplete for folder states (tcp only)
		WDL_FastString rcvs, busLine, compbusLine;
		if (_tr != GetMasterTrack(NULL))
		{
			// parse twice but it does not cost much (search at the very start of the chunk)
			p->Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "AUXRECV", -1, -1, &rcvs, NULL, "MIDIOUT");
			p->Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "ISBUS", 0, -1, &busLine, NULL, "BUSCOMP");
			p->Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "BUSCOMP", 0, -1, &compbusLine, NULL, "SHOWINMIX");
		}

		// apply tr template
		if (updated = ApplyTrackTemplatePrimitive(_tr, _tmpltChunk, _itemsFromTmplt, _envsFromTmplt, p))
		{
			// restore receives, folder and compact states
			if (busLine.GetLength())
				p->ReplaceLine("TRACK", "ISBUS", 1, 0, busLine.Get(), "BUSCOMP");
			if (compbusLine.GetLength())
				p->ReplaceLine("TRACK", "BUSCOMP", 1, 0, compbusLine.Get(), "SHOWINMIX");
			if (rcvs.GetLength())
				p->InsertAfterBefore(0, rcvs.Get(), "TRACK", "MIDIOUT", 1, 0, "MAINSEND");
		}
*/
		if (!_p)
			DELETE_NULL(p); // + auto-commit if needed
	}
	return updated;
}

// Save track templates

// appends _tr's state to _chunkOut
//JFB TODO: save envs with beat position info (like it is done for items)
void SaveSingleTrackTemplateChunk(MediaTrack* _tr, WDL_FastString* _chunkOut, bool _delItems, bool _delEnvs)
{
	if (_tr && _chunkOut)
	{
		SNM_TrackEnvParserPatcher p(_tr, false); // no auto-commit!
		if (_delEnvs)
			p.RemoveEnvelopes();

		// systematically remove items whatever is _delItems, then
		// replace them with items + optional beat pos info, if !_delItems (i.e. mimic native templates)
		p.RemoveSubChunk("ITEM", 2, -1);

		_chunkOut->Append(p.GetChunk()->Get());

		if (!_delItems)
		{
			double d;
			WDL_FastString beatInfo;
			for (int i=0; i<GetTrackNumMediaItems(_tr); i++)
			{
				if (MediaItem* item = GetTrackMediaItem(_tr, i))
				{
					SNM_ChunkParserPatcher pitem(item, false); // no auto-commit!
					int posChunk = pitem.GetLinePos(1, "ITEM", "POSITION", 1, 0); // look for the *next* line
					if (--posChunk>=0) { // --posChunk to zap "\n"
						TimeMap2_timeToBeats(NULL, *(double*)GetSetMediaItemInfo(item, "D_POSITION", NULL), NULL, NULL, &d, NULL);
						beatInfo.SetFormatted(32, " %.14f", d);
						pitem.GetChunk()->Insert(beatInfo.Get(), posChunk);
					}
					posChunk = pitem.GetLinePos(1, "ITEM", "SNAPOFFS", 1, 0);
					if (--posChunk>=0) {
						TimeMap2_timeToBeats(NULL, *(double*)GetSetMediaItemInfo(item, "D_SNAPOFFSET", NULL), NULL, NULL, &d, NULL);
						beatInfo.SetFormatted(32, " %.14f", d);
						pitem.GetChunk()->Insert(beatInfo.Get(), posChunk);
					}
					posChunk = pitem.GetLinePos(1, "ITEM", "LENGTH", 1, 0);
					if (--posChunk>=0) {
						TimeMap2_timeToBeats(NULL, *(double*)GetSetMediaItemInfo(item, "D_LENGTH", NULL), NULL, NULL, &d, NULL);
						beatInfo.SetFormatted(32, " %.14f", d);
						pitem.GetChunk()->Insert(beatInfo.Get(), posChunk);
					}
					_chunkOut->Insert(pitem.GetChunk(), _chunkOut->GetLength()-2); // -2: before ">\n"
				}
			}
		}
	}
}

// supports folders, multi-selection and routings between tracks too
void SaveSelTrackTemplates(bool _delItems, bool _delEnvs, WDL_FastString* _chunkOut)
{
	if (!_chunkOut)
		return;

	WDL_PtrList<MediaTrack> tracks;

	// append selected track chunks (+ folders) -------------------------------
	for (int i=0; i <= GetNumTracks(); i++) // incl. master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			SaveSingleTrackTemplateChunk(tr, _chunkOut, _delItems, _delEnvs);
			tracks.Add(tr);

			// folder: save child templates
			WDL_PtrList<MediaTrack>* childTracks = GetChildTracks(tr);
			if (childTracks)
			{
				for (int j=0; j < childTracks->GetSize(); j++) {
					SaveSingleTrackTemplateChunk(childTracks->Get(j), _chunkOut, _delItems, _delEnvs);
					tracks.Add(childTracks->Get(j));
				}
				i += childTracks->GetSize(); // skip children
				delete childTracks;
			}
		}
	}

	// update receives ids ----------------------------------------------------
	// no break keyword used here: multiple tracks in the template
	SNM_ChunkParserPatcher p(_chunkOut);
	WDL_FastString line;
	int occurence = 0;
	int pos = p.Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "AUXRECV", occurence, 1, &line);
	while (pos > 0)
	{
		pos--; // see SNM_ChunkParserPatcher

		bool replaced = false;
		line.SetLen(line.GetLength()-1); // remove trailing '\n'
		LineParser lp(false);
		if (!lp.parse(line.Get()) && lp.getnumtokens() > 1)
		{
			int success, curId = lp.gettoken_int(1, &success);
			if (success)
			{
				MediaTrack* tr = CSurf_TrackFromID(curId+1, false);
				int newId = tracks.Find(tr);
				if (newId >= 0)
				{
					const char* p3rdTokenToEol = strchr(line.Get(), ' ');
					if (p3rdTokenToEol) p3rdTokenToEol = strchr((char*)(p3rdTokenToEol+1), ' ');
					if (p3rdTokenToEol)
					{
						WDL_FastString newRcv;
						newRcv.SetFormatted(SNM_MAX_CHUNK_LINE_LENGTH, "AUXRECV %d%s\n", newId, p3rdTokenToEol);
						replaced = p.ReplaceLine(pos, newRcv.Get());
						if (replaced)
							occurence++;
					}
				}
			}
		}

		if (!replaced)
			replaced = p.ReplaceLine(pos, "");
		if (!replaced) // skip, just in case..
			occurence++;

		line.Set("");
		pos = p.Parse(SNM_GET_SUBCHUNK_OR_LINE, 1, "TRACK", "AUXRECV", occurence, 1, &line);
	}
}

// Track input actions

void SetMIDIInputChannel(COMMAND_T* _ct)
{
	bool updated = false;
	int ch = ((int)_ct->user)+1; // ch=0: all channels
	for (int i=1; i <= GetNumTracks(); i++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			int in = *(int*)GetSetMediaTrackInfo(tr, "I_RECINPUT", NULL);
			if (((in & 0x1000) == 0x1000) && ((in & 0x1F) != ch))
			{
				in &= 0x1FE0;
				in |= ch;
				GetSetMediaTrackInfo(tr, "I_RECINPUT", &in);
				updated = true;
			}
		}
	}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

void RemapMIDIInputChannel(COMMAND_T* _ct)
{
	bool updated = false;
	int ch = ((int)_ct->user)+1; // ch=0: source channel

	char pLine[SNM_MAX_CHUNK_LINE_LENGTH] = "";
	if (ch && snprintfStrict(pLine, sizeof(pLine), "MIDI_INPUT_CHANMAP %d\n", ch-1) <= 0)
		return;

	for (int i=1; i <= GetNumTracks(); i++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
		{
			int in = *(int*)GetSetMediaTrackInfo(tr, "I_RECINPUT", NULL);
			if ((in & 0x1000) == 0x1000) // midi in?
			{
				SNM_ChunkParserPatcher p(tr);
				char currentCh[3] = "";
				int chunkPos = p.Parse(SNM_GET_CHUNK_CHAR, 1, "TRACK", "MIDI_INPUT_CHANMAP", 0, 1, currentCh, NULL, "TRACKID");
				if (chunkPos > 0) {
					if (!ch || atoi(currentCh) != (ch-1))
						updated |= p.ReplaceLine(--chunkPos, pLine); // pLine can be "", i.e. remove line
				}
				else
					updated |= p.InsertAfterBefore(0, pLine, "TRACK", "TRACKHEIGHT", 1, 0, "TRACKID");
			}
		}
	}
	if (updated)
		Undo_OnStateChangeEx2(NULL, SWS_CMD_SHORTNAME(_ct), UNDO_STATE_ALL, -1);
}

// Play track preview

// helper funcs
void TrackPreviewInitDeleteMutex(preview_register_t* _prev, bool _init) {
	if (_init)
#ifdef _WIN32
		InitializeCriticalSection(&_prev->cs);
#else
		pthread_mutex_init(&_prev->mutex, NULL);
#endif
	else
#ifdef _WIN32
		DeleteCriticalSection(&_prev->cs);
#else
		pthread_mutex_destroy(&_prev->mutex);
#endif
}

void TrackPreviewLockUnlockMutex(preview_register_t* _prev, bool _lock) {
	if (_lock)
#ifdef _WIN32
		EnterCriticalSection(&_prev->cs);
#else
		pthread_mutex_lock(&_prev->mutex);
#endif
	else
#ifdef _WIN32
		LeaveCriticalSection(&_prev->cs);
#else
		pthread_mutex_unlock(&_prev->mutex);
#endif
}

void DeleteTrackPreview(void* _prev)
{
	if (_prev)
	{
		preview_register_t* prev = (preview_register_t*)_prev;
		StopTrackPreview2(NULL, prev);
		DELETE_NULL(prev->src);
		TrackPreviewInitDeleteMutex(prev, false);
		DELETE_NULL(prev);
	}
}

// can be used to start/pause all track previews in sync, such a lock should be avoided though
// => the related pref is intentionally burried in S&M.ini (for very specific use-cases)
void TrackPreviewLockUnlockTracks(bool _lock)
{
	if (g_SNM_MediaFlags&1)
	{
		if (_lock) MainThread_LockTracks();
		else MainThread_UnlockTracks();
	}
}

class PausedPreview
{
public:
	PausedPreview(const char* _fn, MediaTrack* _tr, double _pos)
		: m_fn(_fn),m_tr(_tr),m_pos(_pos) {}
	bool IsMatching(preview_register_t* _prev)
	{
		return (_prev && _prev->preview_track == m_tr &&
			_prev->src && _prev->src->GetFileName() &&
			!_stricmp(_prev->src->GetFileName(), m_fn.Get()));
	}
	WDL_FastString m_fn; MediaTrack* m_tr; double m_pos;
};

// no WDL_PtrList_DOD here: need to stop the preview (and delete it, delete its src, etc)
WDL_PtrList_DeleteOnDestroy<preview_register_t> g_playPreviews(DeleteTrackPreview);
WDL_PtrList_DOD<PausedPreview> g_pausedPreviews;

// stop playing track previews if needed
// polled from the main thread via SNM_CSurfRun()
void StopTrackPreviewsRun()
{
	static WDL_PtrList<void> ano_trs;
	for (int i=g_playPreviews.GetSize()-1; i >=0; i--)
	{
		if (preview_register_t* prev = g_playPreviews.Get(i))
		{
			bool want_delete=false;

			TrackPreviewLockUnlockMutex(prev, true);
			if (!prev->loop &&
				(prev->volume < 0.5 || // stop has been requested
				prev->curpos >= prev->src->GetLength() || // preview has been entirely played
				fabs(prev->curpos - prev->src->GetLength()) < 0.000001))
			{
				// prepare all notes off, if needed (i.e. only for stopped MIDI files)
				if (prev->volume < 0.5 && // stopped files only, no cc123 is sent when files end normally
					!strncmp(prev->src->GetType(), "MIDI", 4) &&
					ano_trs.Find(prev->preview_track) < 0)
				{
					ano_trs.Add(prev->preview_track);
				}
				want_delete=true;
			}
			TrackPreviewLockUnlockMutex(prev, false);

			// stop and delete the preview, its src, etc
			if (want_delete) g_playPreviews.Delete(i, true, DeleteTrackPreview);
		}
	}

	// send all notes off, if needed
	SendAllNotesOff(&ano_trs, 1|2);
	ano_trs.Empty();
}

// from askjf.com:
// if 1 is set in bufflags, it will buffer-ahead (in case your source needs to do some cpu intensive processing, a good idea
// -- if it needs to be low latency, then don't set the 1 bit).
// MSI is the measure start interval, which if set to n greater than 0, means start synchronized to playback synchronized to a multiple of n measures
bool SNM_PlayTrackPreview(MediaTrack* _tr, PCM_source* _src, bool _pause, bool _loop, double _msi)
{
	bool ok = false;
	if (_src)
	{
		preview_register_t* prev = new preview_register_t;
		memset(prev, 0, sizeof(preview_register_t));
		TrackPreviewInitDeleteMutex(prev, true);
		prev->src = _src;
		prev->m_out_chan = -1;
		prev->curpos = 0.0;
		prev->loop = _loop;
		prev->volume = 1.0;
		prev->preview_track = _tr;

		// update start position for paused items
		// note: no need to mutex "prev" here as it is not started yet
		if (_pause)
		{
			for (int i=g_pausedPreviews.GetSize()-1; i>=0; i--)
			{
				if (g_pausedPreviews.Get(i)->IsMatching(prev))
				{
					prev->curpos = g_pausedPreviews.Get(i)->m_pos;
					g_pausedPreviews.Delete(i, true);
				}
			}
		}

		g_playPreviews.Add(prev);
		ok = (PlayTrackPreview2Ex(NULL, prev, !!_msi, _msi) != 0);
	}
	return ok;
}

// primitive func: _fn must be a valid/existing file
bool SNM_PlayTrackPreview(MediaTrack* _tr, const char* _fn, bool _pause, bool _loop, double _msi)
{
	if (PCM_source* src = PCM_Source_CreateFromFileEx(_fn, true)) // "true" so that the src is not imported as in-project data
		return SNM_PlayTrackPreview(_tr, src, _pause, _loop, _msi);
	return false;
}

void SNM_PlaySelTrackPreviews(const char* _fn, bool _pause, bool _loop, double _msi)
{
	TrackPreviewLockUnlockTracks(true);
	for (int i=1; i <= GetNumTracks(); i++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(i, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
			SNM_PlayTrackPreview(tr, _fn, _pause, _loop, _msi);
	}
	TrackPreviewLockUnlockTracks(false);
}

// returns true if something done
bool SNM_TogglePlaySelTrackPreviews(const char* _fn, bool _pause, bool _loop, double _msi)
{
	TrackPreviewLockUnlockTracks(true);

	bool done = false;
	WDL_PtrList<void> trsToStart;
	for (int j=1; j <= GetNumTracks(); j++) // skip master
	{
		MediaTrack* tr = CSurf_TrackFromID(j, false);
		if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
			trsToStart.Add(tr);
	}

	{
		// stop play if needed, store if paused
		for (int i=g_playPreviews.GetSize()-1; i >=0; i--)
		{
			preview_register_t* prev = g_playPreviews.Get(i);
			for (int j=1; j <= GetNumTracks(); j++) // skip master
			{
				MediaTrack* tr = CSurf_TrackFromID(j, false);
				if (tr && *(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
				{
					TrackPreviewLockUnlockMutex(prev, true);
					if (prev->preview_track == tr &&
						prev->volume > 0.5 && // playing
						!_stricmp(prev->src->GetFileName(), _fn))
					{
						prev->loop = false;
						prev->volume = 0.0; // => will be stopped by next call to StopTrackPreviewsRun()
						if (_pause) g_pausedPreviews.Insert(0, new PausedPreview(_fn, tr, prev->curpos));
						trsToStart.Delete(trsToStart.Find(tr));
						done = true;
					}
					TrackPreviewLockUnlockMutex(prev, false);
				}
			}
		}
	}

	// start play if needed
	for (int i=0; i <= trsToStart.GetSize(); i++)
		if (MediaTrack* tr = (MediaTrack*)trsToStart.Get(i))
			done |= SNM_PlayTrackPreview(tr, _fn, _pause, _loop, _msi);

	TrackPreviewLockUnlockTracks(false);

	return done;
}

void StopTrackPreviews(bool _selTracksOnly)
{
	TrackPreviewLockUnlockTracks(true);
	for (int i=g_playPreviews.GetSize()-1; i >= 0; i--)
	{
		preview_register_t* prev = g_playPreviews.Get(i);
		for (int j=1; j <= GetNumTracks(); j++) // skip master
		{
			MediaTrack* tr = CSurf_TrackFromID(j, false);
			if (tr && (!_selTracksOnly ||*(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL)))
			{
				TrackPreviewLockUnlockMutex(prev, true);

				if (prev->preview_track == tr) {
					prev->loop = false;
					prev->volume = 0.0; // => will be stopped by next call to StopTrackPreviewsRun()
				}

				for (int i=g_pausedPreviews.GetSize()-1; i>=0; i--)
					if (g_pausedPreviews.Get(i)->m_tr == tr)
						g_pausedPreviews.Delete(i, true);

				TrackPreviewLockUnlockMutex(prev, false);
			}
		}
	}
	TrackPreviewLockUnlockTracks(false);
}

void StopTrackPreviews(COMMAND_T* _ct) {
	StopTrackPreviews((int)_ct->user == 1);
}

// Send CC123//CC64/CC120 on all channels

class AllNoteOffSource : public PCM_source
{
public:
  AllNoteOffSource(int flags) { m_hasdone=0; m_flags=flags; }
  ~AllNoteOffSource() { }
  PCM_source *Duplicate() { return new AllNoteOffSource(m_flags); }
  bool SetFileName(const char *newfn) { return false; }
  bool IsAvailable() { return true; }
  const char *GetType() { return "S&M_ALLNOTEOFFS"; };
  int GetNumChannels() { return 1; }
  double GetSampleRate() { return 0.0; }
  double GetLength() { return 10000000000000.0; }
  int PropertiesWindow(HWND hwndParent) { return -1; }
  void SaveState(ProjectStateContext *ctx) {}
  int LoadState(const char *firstline, ProjectStateContext *ctx) { return -1; }
  void Peaks_Clear(bool deleteFile) { }
  int PeaksBuild_Begin() { return 0; }
  int PeaksBuild_Run() { return 0; }
  void PeaksBuild_Finish() { }
  void GetPeakInfo(PCM_source_peaktransfer_t *block) { block->peaks_out=0; }

  void GetSamples(PCM_source_transfer_t *block)
  {
    block->samples_out=0;
    if (block->midi_events && !m_hasdone)
    {
      m_hasdone=1;
      block->samples_out=block->length;
      memset(block->samples,0,block->length*block->nch*sizeof(double));
      for (int x = 0; x < 16; x ++)
      {
        MIDI_event_t e={0, 3, {static_cast<unsigned char>(0xb0 + x)}};
        if (m_flags&1)
        {
          e.midi_message[1]=64;
          block->midi_events->AddItem(&e);
        }
        if (m_flags&2)
        {
          e.midi_message[1]=123;
          block->midi_events->AddItem(&e);
        }
        if (m_flags&4)
        {
          e.midi_message[1]=120;
          block->midi_events->AddItem(&e);
        }
      }
    }
  }

  int m_hasdone;
  int m_flags;
};

// _cc_flags&1=send cc#64 messages (reset sustain), &2=cc#123 (all notes off), &4=cc#120 (all sounds off)
bool SendAllNotesOff(WDL_PtrList<void>* _trs, int _cc_flags)
{
	const int trs_sz=_trs->GetSize();
	if (!_trs || !trs_sz) return false;

	int i, cnt = 0;
	WDL_PtrList_DeleteOnDestroy<preview_register_t> anos_prevs(DeleteTrackPreview);
	for (i=0; i<trs_sz; i++)
	{
		AllNoteOffSource *anos = new AllNoteOffSource(_cc_flags);

		preview_register_t *prev = anos_prevs.Add(new preview_register_t);
		memset(prev, 0, sizeof(preview_register_t));
		TrackPreviewInitDeleteMutex(prev, true);
		prev->src = anos;
		prev->m_out_chan = -1;
		prev->curpos = 0.0;
		prev->loop = false;
		prev->volume = 1.0;
		prev->preview_track = _trs->Get(i);

		if (PlayTrackPreview2Ex(NULL, prev, 0, 0.0)) cnt++;
		else anos->m_hasdone=true; // no locking needed here (not played)
  }

  // wait for all-notes-off to be sent before stopping
  int safety=0;
  int prev_cnt;
  while (safety<250 && (prev_cnt=anos_prevs.GetSize()))
  {
    for (i=prev_cnt-1; i>=0; i--)
    {
      preview_register_t *prev = anos_prevs.Get(i);
      AllNoteOffSource *anos = (AllNoteOffSource*)prev->src;

      // DeleteTrackPreview() will stop and delete the preview, its src, etc
      if (anos->m_hasdone) anos_prevs.Delete(i, true, DeleteTrackPreview);
    }
#ifdef _WIN32
    // keep the UI updating
    if (safety)
    {
      MSG msg;
      while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
      DispatchMessage(&msg);
    }
#endif
    Sleep(1);
    safety++;
  }
  return !!cnt;
}

// wrapping func
bool SendAllNotesOff(MediaTrack* _tr, int _cc_flags)
{
  if (_tr)
  {
    WDL_PtrList<void> trs; trs.Add(_tr);
    return SendAllNotesOff(&trs, _cc_flags);
  }
  return false;
}

void SendAllNotesOff(COMMAND_T* _ct)
{
	WDL_PtrList<void> trs;
	for (int j=1; j <= GetNumTracks(); j++) // skip master
		if (MediaTrack* tr = CSurf_TrackFromID(j, false))
			if (*(int*)GetSetMediaTrackInfo(tr, "I_SELECTED", NULL))
				trs.Add(tr);
	SendAllNotesOff(&trs, (int)_ct->user);
}

// ReaScript export

bool SNM_AddTCPFXParm(MediaTrack* _tr, int _fxId, int _prmId)
{
	bool updated = false;
	if (_tr && _fxId>=0 && _prmId>=0 && _fxId<TrackFX_GetCount(_tr) && _prmId<TrackFX_GetNumParams(_tr, _fxId))
	{
		// already exists?
		int fxId, prmId;
		for (int i=0; i<CountTCPFXParms(NULL, _tr); i++)
			if (GetTCPFXParm(NULL, _tr, i, &fxId, &prmId))
				if (fxId==_fxId && prmId==_prmId)
					return false;

		SNM_ChunkParserPatcher p(_tr);

		// first get the fx chain: a straight search for the fx would fail (possible mismatch with frozen track, item fx, etc..)
		WDL_FastString chainChunk;
		if (p.GetSubChunk("FXCHAIN", 2, 0, &chainChunk, "<ITEM") > 0)
		{
			SNM_ChunkParserPatcher pfxc(&chainChunk, false);
			int pos = pfxc.Parse(SNM_GET_CHUNK_CHAR,1,"FXCHAIN","WAK",_fxId,0);
			if (pos>0)
			{
				char line[SNM_MAX_CHUNK_LINE_LENGTH] = "";
				if (snprintfStrict(line, sizeof(line), "PARM_TCP %d\n", _prmId) > 0)
				{
					pfxc.GetChunk()->Insert(line, --pos);
					if (p.ReplaceSubChunk("FXCHAIN", 2, 0, pfxc.GetChunk()->Get(), "<ITEM")) {
						updated = true;
						p.Commit(true);
					}
				}
			}
		}
	}
	return updated;
}

// Misc. track helpers/actions

void ScrollSelTrack(bool _tcp, bool _mcp)
{
	MediaTrack* tr = SNM_GetSelectedTrack(NULL, 0, true);
	if (tr && (_tcp || _mcp))
	{
		if (_tcp)
			Main_OnCommand(40913,0); // vertical scroll to selected tracks

		// REAPER BUG (last check v4.20):
		// SetMixerScroll() has no effect with most right tracks
		if (_mcp)
			SetMixerScroll(tr);
	}
}

void ScrollSelTrack(COMMAND_T* _ct) {
	int flags = (int)_ct->user;
	ScrollSelTrack((flags&1)==1, (flags&2)==2); // == for warning C4800
}

//JFB change/restore sel programatically => not cool for control surfaces
void ScrollTrack(MediaTrack* _tr, bool _tcp, bool _mcp)
{
	if (_tr)
	{
		PreventUIRefresh(1);
		WDL_PtrList<MediaTrack> selTrs;
		SNM_GetSelectedTracks(NULL, &selTrs, true);
		SNM_SetSelectedTrack(NULL, _tr, true, true);
		ScrollSelTrack(true, false);
		SNM_SetSelectedTracks(NULL, &selTrs, true);
		PreventUIRefresh(-1);
		// restoration => no need to UpdateArrange();
	}
}

//JFB change/restore sel programatically => not cool for control surfaces
void ShowTrackRoutingWindow(MediaTrack* _tr)
{
	if (_tr)
	{
		PreventUIRefresh(1);
		WDL_PtrList<MediaTrack> selTrs;
		SNM_GetSelectedTracks(NULL, &selTrs, true);
		SNM_SetSelectedTrack(NULL, _tr, true, true);
		Main_OnCommand(40293, 0);
		SNM_SetSelectedTracks(NULL, &selTrs, true);
		PreventUIRefresh(-1);
	}
}


void CDlgCompanionManager::OnCancel()
{
	DestroyWindow();
	return;
}

#include "Stdafx.h"
#include "Resource.h"
#include "DlgCompanionManager.h"

BEGIN_MESSAGE_MAP(CDlgCompanionManager, CSkinDialogEx)
END_MESSAGE_MAP()

//构造函数
CDlgCompanionManager::CDlgCompanionManager() : CSkinDialogEx(IDD_COMPANION_MANAGER)
{
	m_pICompanionManager=NULL;
}

//析构函数
CDlgCompanionManager::~CDlgCompanionManager()
{
}

//控件绑定
void CDlgCompanionManager::DoDataExchange(CDataExchange* pDX)
{
	__super::DoDataExchange(pDX);
	DDX_Control(pDX,IDCANCEL,m_btClose);
	DDX_Control(pDX,IDC_FRIEND_LIST,m_FriendListCtrl);
	DDX_Control(pDX,IDC_DETEST_LIST,m_DetestListCtrl);
	DDX_Control(pDX,IDC_COMPANION_TREE,m_CompanionTreeCtrl);
}

//初始化函数
BOOL CDlgCompanionManager::OnInitDialog()
{
	__super::OnInitDialog();

	//设置标题
	SetWindowText(TEXT("用户资料管理"));

	//加入信息
	WORD wIndex=0;
	const tagCompanionItem * pCompanionItem=NULL;
	do
	{
		//枚举关系
		pCompanionItem=m_pICompanionManager->EmunCompanionItem(wIndex++);
		if (pCompanionItem==NULL) break;

		//控制界面
		m_CompanionTreeCtrl.InsertCompanionItem(pCompanionItem);
		if (pCompanionItem->Companion==enCompanion_Friend) m_FriendListCtrl.InsertCompanionItem(pCompanionItem);
		if (pCompanionItem->Companion==enCompanion_Detest) m_DetestListCtrl.InsertCompanionItem(pCompanionItem);
	} while (true);

	//展开列表
	m_CompanionTreeCtrl.ExpandListItem();

	return TRUE;
}

//取消消息

//设置接口
bool CDlgCompanionManager::SetCompanionManager(IUnknownEx * pIUnknownEx)
{
	ASSERT(pIUnknownEx!=NULL);
	m_pICompanionManager=GET_OBJECTPTR_INTERFACE(pIUnknownEx,ICompanionManager);
	ASSERT(m_pICompanionManager!=NULL);
	return (m_pICompanionManager!=NULL);
}

//加入关系
bool CDlgCompanionManager::InsertCompanionItem(const tagCompanionItem * pCompanionItem)
{
	if (GetSafeHwnd()!=NULL)
	{
		m_CompanionTreeCtrl.InsertCompanionItem(pCompanionItem);
		if (pCompanionItem->Companion==enCompanion_Friend)
		{
			m_FriendListCtrl.InsertCompanionItem(pCompanionItem);
		}
		else if (pCompanionItem->Companion==enCompanion_Detest)
		{
			m_DetestListCtrl.InsertCompanionItem(pCompanionItem);
		}
	}

	return true;
}

//更新关系
bool CDlgCompanionManager::UpdateCompanionItem(const tagCompanionItem * pCompanionItem, bool bChangeCompanion)
{
	if (GetSafeHwnd()!=NULL)
	{
		if (bChangeCompanion==true)
		{
			//删除老数据
			m_FriendListCtrl.DeleteCompanionItem(pCompanionItem);
			m_DetestListCtrl.DeleteCompanionItem(pCompanionItem);
			m_CompanionTreeCtrl.DeleteCompanionItem(pCompanionItem);

			//插入新数据
			m_CompanionTreeCtrl.InsertCompanionItem(pCompanionItem);
			if (pCompanionItem->Companion==enCompanion_Friend) m_FriendListCtrl.InsertCompanionItem(pCompanionItem);
			if (pCompanionItem->Companion==enCompanion_Detest) m_DetestListCtrl.InsertCompanionItem(pCompanionItem);
		}
		else
		{
			if (pCompanionItem->Companion==enCompanion_Friend) m_FriendListCtrl.UpdateCompanionItem(pCompanionItem);
			if (pCompanionItem->Companion==enCompanion_Detest) m_DetestListCtrl.UpdateCompanionItem(pCompanionItem);
		}
	}

	return true;
}

//删除关系
bool CDlgCompanionManager::DeleteCompanionItem(const tagCompanionItem * pCompanionItem)
{
	if (GetSafeHwnd()!=NULL)
	{
		m_CompanionTreeCtrl.DeleteCompanionItem(pCompanionItem);
		if (pCompanionItem->Companion==enCompanion_Friend)
		{
			m_FriendListCtrl.DeleteCompanionItem(pCompanionItem);
		}
		else if (pCompanionItem->Companion==enCompanion_Detest)
		{
			m_DetestListCtrl.DeleteCompanionItem(pCompanionItem);
		}
	}

	return true;
}