
#include "fl/gfx/gradient.h"
#include "fl/stl/assert.h"
#include "fl/gfx/colorutils.h"
#include "fl/stl/noexcept.h"

namespace fl {

namespace {
struct Visitor {
    Visitor(u8 index) : index(index) {}
    void accept(const CRGBPalette16 *palette) {
        CRGB c = ColorFromPalette(*palette, index);
        return_val = c;
    }

    void accept(const CRGBPalette32 *palette) {
        CRGB c = ColorFromPalette(*palette, index);
        return_val = c;
    }

    void accept(const CRGBPalette256 *palette) {
        CRGB c = ColorFromPaletteExtended(*palette, index);
        return_val = c;
    }

    void accept(const Gradient::GradientFunction &func) {
        CRGB c = func(index);
        return_val = c;
    }

    template <typename T> void accept(const T &obj) {
        // This should never be called, but we need to provide a default
        // implementation to avoid compilation errors.
        accept(&obj);
    }

    CRGB return_val;
    u8 index;
};

struct VisitorFill {
    VisitorFill(span<const u8> indices, span<CRGB> output)
        : output(output), indices(indices) {
        // This assert was triggering on the corkscrew example. Not sure why
        // but the corrective action of taking the min was corrective action.
        // FASTLED_ASSERT(
        //     indices.size() == output.size(),
        //     "Gradient::fill: indices and output must be the same size"
        //     "\nSize was" << indices.size() << " and " << output.size());
        n = fl::min(indices.size(), output.size());
    }
    void accept(const CRGBPalette16 *palette) {
        for (fl::size i = 0; i < n; ++i) {
            output[i] = ColorFromPalette(*palette, indices[i]);
        }
    }

    void accept(const CRGBPalette32 *palette) {
        for (fl::size i = 0; i < n; ++i) {
            output[i] = ColorFromPalette(*palette, indices[i]);
        }
    }

    void accept(const CRGBPalette256 *palette) {
        for (fl::size i = 0; i < n; ++i) {
            output[i] = ColorFromPaletteExtended(*palette, indices[i]);
        }
    }

    void accept(const Gradient::GradientFunction &func) {
        for (fl::size i = 0; i < n; ++i) {
            output[i] = func(indices[i]);
        }
    }

    template <typename T> void accept(const T &obj) {
        // This should never be called, but we need to provide a default
        // implementation to avoid compilation errors.
        accept(&obj);
    }

    span<CRGB> output;
    span<const u8> indices;
    u8 n = 0;
};

} // namespace

CRGB Gradient::colorAt(u8 index) const {
    Visitor visitor(index);
    mVariant.visit(visitor);
    return visitor.return_val;
}

template <typename T> Gradient::Gradient(T *palette) { set(palette); }

Gradient::Gradient(const Gradient &other) : mVariant(other.mVariant) {}

Gradient::Gradient(Gradient &&other) FL_NOEXCEPT
    : mVariant(move(other.mVariant)) {}

void Gradient::set(const CRGBPalette32 *palette) { mVariant = palette; }

void Gradient::set(const CRGBPalette256 *palette) { mVariant = palette; }

void Gradient::set(const CRGBPalette16 *palette) { mVariant = palette; }

void Gradient::set(const GradientFunction &func) { mVariant = func; }

Gradient &Gradient::operator=(const Gradient &other) FL_NOEXCEPT {
    if (this != &other) {
        mVariant = other.mVariant;
    }
    return *this;
}

void Gradient::fill(span<const u8> input, span<CRGB> output) const {
    VisitorFill visitor(input, output);
    mVariant.visit(visitor);
}

CRGB GradientInlined::colorAt(u8 index) const {
    Visitor visitor(index);
    mVariant.visit(visitor);
    return visitor.return_val;
}
void GradientInlined::fill(span<const u8> input,
                           span<CRGB> output) const {
    VisitorFill visitor(input, output);
    mVariant.visit(visitor);
}

Gradient::Gradient(const GradientInlined &other) {
    // Visitor is cumbersome but guarantees all paths are handled.
    struct Copy {
        Copy(Gradient &owner) : mOwner(owner) {}
        void accept(const CRGBPalette16 &palette) { mOwner.set(&palette); }
        void accept(const CRGBPalette32 &palette) { mOwner.set(&palette); }
        void accept(const CRGBPalette256 &palette) { mOwner.set(&palette); }
        void accept(const GradientFunction &func) { mOwner.set(func); }
        Gradient &mOwner;
    };
    Copy copy_to_self(*this);
    other.getVariant().visit(copy_to_self);
}

} // namespace fl
