#include "GifImportPipeline.hpp"
#include "GifArtVectorizer.hpp"
#include "GifParallel.hpp"

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <exception>
#include <sstream>
#include <string>
#include <vector>

using namespace paimon::gifimport;

namespace {
ImportPlan lastPlan;
std::vector<std::uint8_t> lastPixels;
std::vector<std::uint8_t> lastGrid;
std::string lastInfo;

struct SerialExecution {
    SerialExecution() { enterParallelRegion(); }
    ~SerialExecution() { leaveParallelRegion(); }
};

std::string quote(std::string const& text) {
    std::string result = "\"";
    for (char c : text) {
        if (c == '\\' || c == '"') result += '\\';
        if (c == '\n') result += "\\n";
        else if (c == '\r') result += "\\r";
        else result += c;
    }
    return result + '"';
}

std::vector<std::uint8_t> resizeOpaque(
    std::vector<std::uint8_t> const& pixels, int fromWidth, int fromHeight,
    int width, int height
) {
    std::vector<std::uint8_t> output(static_cast<std::size_t>(width) * height * 4);
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            float const sx = std::clamp((x + 0.5f) * fromWidth / width - 0.5f, 0.f, fromWidth - 1.f);
            float const sy = std::clamp((y + 0.5f) * fromHeight / height - 0.5f, 0.f, fromHeight - 1.f);
            int const x0 = static_cast<int>(sx), y0 = static_cast<int>(sy);
            int const x1 = std::min(x0 + 1, fromWidth - 1), y1 = std::min(y0 + 1, fromHeight - 1);
            float const tx = sx - x0, ty = sy - y0;
            auto channel = [&](int px, int py, int c) {
                auto const index = (static_cast<std::size_t>(py) * fromWidth + px) * 4;
                float const alpha = pixels[index + 3] / 255.f;
                constexpr int background[3] = {6, 14, 15};
                return pixels[index + c] * alpha + background[c] * (1.f - alpha);
            };
            auto const index = (static_cast<std::size_t>(y) * width + x) * 4;
            for (int c = 0; c < 3; ++c) {
                float const a = channel(x0, y0, c) * (1.f - tx) + channel(x1, y0, c) * tx;
                float const b = channel(x0, y1, c) * (1.f - tx) + channel(x1, y1, c) * tx;
                output[index + c] = static_cast<std::uint8_t>(std::lround(a * (1.f - ty) + b * ty));
            }
            output[index + 3] = 255;
        }
    }
    return output;
}
}

extern "C" {
int paint_convert(std::uint8_t const* pixels, int width, int height,
    int dimension, int colors, int budget) {
    if (!pixels || width < 1 || height < 1 || width > 384 || height > 384 ||
        dimension < 4 || dimension > 96 || colors < 2 || colors > 24 ||
        budget < 100 || budget > 50000) {
        lastInfo = "{\"error\":\"Invalid Paint parameters\"}";
        return 0;
    }
    try {
        SerialExecution serial;
        SourceAnimation source;
        source.width = width; source.height = height;
        source.frames.resize(1);
        source.frames.front().rgba.assign(pixels, pixels + static_cast<std::size_t>(width) * height * 4);
        Options options;
        options.mode = ImportMode::Paint;
        options.maxDimension = dimension;
        options.maxColors = colors;
        options.maxFrames = 1;
        options.objectBudget = budget;
        options.background = BackgroundMode::Keep;
        options.sampling = SamplingMode::Smooth;
        options.loop = false;
        options.motion = false;
        options.dither = false;
        options.autoResolution = false;
        options.glow = GlowMode::Off;
        auto result = buildPlan(source, options);
        if (!result) {
            lastInfo = "{\"error\":" + quote(result.error) + "}";
            return 0;
        }
        lastPlan = std::move(result.plan);
        int const scale = std::clamp(static_cast<int>(std::ceil(
            static_cast<float>(std::max(width, height)) / std::max(lastPlan.width, lastPlan.height))), 1, 8);
        auto rendered = renderPlanFrame(lastPlan, 0, scale, false);
        lastPixels = resizeOpaque(rendered, lastPlan.width * scale, lastPlan.height * scale, width, height);
        lastGrid.assign(static_cast<std::size_t>(lastPlan.width) * lastPlan.height * 4, 0);
        for (std::size_t i = 0; i < lastPlan.frames.front().cells.size(); ++i) {
            int const cell = lastPlan.frames.front().cells[i];
            if (cell < 0) continue;
            auto const& color = lastPlan.palette[static_cast<std::size_t>(cell)];
            lastGrid[i * 4] = color.r; lastGrid[i * 4 + 1] = color.g;
            lastGrid[i * 4 + 2] = color.b; lastGrid[i * 4 + 3] = 255;
        }
        std::ostringstream info;
        info << "{\"mode\":\"paint\",\"objects\":" << lastPlan.totalObjects
             << ",\"visualObjects\":" << lastPlan.visualObjects << ",\"triggers\":" << lastPlan.triggerObjects
             << ",\"dimension\":" << lastPlan.actualDimension << ",\"width\":" << lastPlan.width
             << ",\"height\":" << lastPlan.height << ",\"blocks\":" << lastPlan.blockObjects
             << ",\"strokes\":" << lastPlan.strokeObjects << ",\"circles\":" << lastPlan.circleObjects
             << ",\"triangles\":" << lastPlan.triangleObjects << ",\"similarity\":" << lastPlan.similarity
             << ",\"geometrySimilarity\":" << lastPlan.geometrySimilarity << ",\"palette\":[";
        for (std::size_t i = 0; i < lastPlan.palette.size(); ++i) {
            if (i) info << ',';
            auto const& color = lastPlan.palette[i];
            info << '[' << static_cast<int>(color.r) << ',' << static_cast<int>(color.g) << ',' << static_cast<int>(color.b) << ']';
        }
        info << "]}";
        lastInfo = info.str();
        return 1;
    } catch (std::exception const& error) {
        lastInfo = "{\"error\":" + quote(error.what()) + "}";
        return 0;
    }
}

std::uint8_t const* paint_pixels() { return lastPixels.data(); }
std::uint8_t const* paint_grid() { return lastGrid.data(); }
char const* paint_info() { return lastInfo.c_str(); }
}
