#include "cgen_color.h"
#include <cairo.h>
#include <iostream>
#include <filesystem>
#include <numbers>
#include <optional>
using namespace std::literals;
// Compare two pngs, save the diff
// Return
//  3: file error
//  2: diff size (we also create diff file named '*_diff*')
//  1: diff (we also create diff file named '*_diff*')
//  0: same
int main(int argc, char** argv) {
    if (argc < 3 || argc > 4) {
        std::cerr << "Usage: " << std::filesystem::path(argv[0]).filename() << " new.png canonical.png [nofont-new.png]" << '\n';
        return 3;
    }
    // std::cerr << "doing " << argv[1] << '\n';
    auto s1 = cairo_image_surface_create_from_png(argv[1]),
         s2 = cairo_image_surface_create_from_png(argv[2]),
         s3 = argc >= 4 ? cairo_image_surface_create_from_png(argv[3]) : nullptr;
    if (cairo_surface_status(s1) + cairo_surface_status(s2) + (s3 ? cairo_surface_status(s3) : 0))
        return 3;
    // std::cerr << "succ reading\n";
    const auto w1 = cairo_image_surface_get_width(s1), h1 = cairo_image_surface_get_height(s1),
               w2 = cairo_image_surface_get_width(s2), h2 = cairo_image_surface_get_height(s2);
    auto f1 [[maybe_unused]] = cairo_image_surface_get_format(s1);
    _ASSERT(CAIRO_FORMAT_RGB24 == f1 || CAIRO_FORMAT_ARGB32 == f1); //we ignore the 'a' channel anyway
    if (s3) {
        auto cr = cairo_create(s3);
        cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);
        cairo_set_source_surface(cr, s1, 0, 0);
        cairo_paint(cr);
        const bool save_temps = getenv("PNGDIFF_SAVE_TEMPS") && getenv("PNGDIFF_SAVE_TEMPS")[0] && getenv("PNGDIFF_SAVE_TEMPS") != "0"sv;
        if (save_temps)
            cairo_surface_write_to_png(s3, (argv[3] + "_maskdiff.png"s).data());
        const auto w3 = cairo_image_surface_get_width(s3), h3 = cairo_image_surface_get_height(s3);
        cairo_surface_flush(s3);
        auto p3 = reinterpret_cast<uint32_t*>(cairo_image_surface_get_data(s3));
        // Need an alpha channel for the mask.
        // NB: cairo_fill operates on surfaces with the same stride, hence A8 is not enough
        auto mask = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w3, h3);
        cr = cairo_create(mask);
        cairo_set_source_rgba(cr, 1, 1, 1, 1);
        const double r = [] {
            try { return std::stod(getenv("PNGDIFF_NOFONT_MASK_RADIUS")); }
            catch (...) { return 2.; }
        }();
        for (auto u = 0; u < h3; ++u)
            for (auto v = 0; v < w3; ++v)
                if (p3[u*w3+v] & 0xffffff) {
                    cairo_arc(cr, v, u, r, 0, 2 * std::numbers::pi);
                    cairo_fill(cr);
                }
        if (save_temps)
            cairo_surface_write_to_png(mask, (argv[3] + "_mask_inflated.png"s).data());
        cr = cairo_create(s2);
        cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
        cairo_set_source_surface(cr, s1, 0, 0);
        cairo_mask_surface(cr, mask, 0, 0);
        cairo_fill(cr);
        if (save_temps)
            cairo_surface_write_to_png(s2, (argv[2] + "_masked.png"s).data());
    }
    cairo_surface_flush(s1);
    cairo_surface_flush(s2);
    auto p1 = reinterpret_cast<uint32_t*>(cairo_image_surface_get_data(s1));
    auto p2 = reinterpret_cast<uint32_t*>(cairo_image_surface_get_data(s2));
    auto luma_diff = [](ColorType c1, ColorType c2) -> std::optional<bool> { // if not equal then whether outside the allowed Luma difference
        if (c1 == c2)
            return {};
        auto luma = [](ColorType const& c) { return .299 * c.r + .587 * c.g + .114 * c.b; };
        static const auto allowed = [] {
            try { return std::abs(std::stod(getenv("PNGDIFF_LUMA_DIFF"))); }
            catch (...) { return .0; }
        }();
        return !allowed || std::abs(luma(c1) - luma(c2)) > allowed;
    };
    auto&& [changed, count] = [&] {
        bool changed = false;
        int count = 0;
        for (auto u = 0; u < std::min(h1, h2); ++u)
            for (auto v = 0; v < std::min(w1, w2); ++v)
                if (auto diff = luma_diff(ColorType(p1[u*w1+v]), ColorType(p2[u*w2+v]))) {
                    changed = true;
                    if (*diff)
                        ++count;
                    static const auto red = ColorType(255, 0, 0, 0).ConvertToUnsigned(), light = ColorType(255, 0, 0, 0).Lighter(.5).ConvertToUnsigned();
                    p1[u*w1+v] = *diff ? red : light;
                } else
                    p1[u*w1+v] = ColorType(p1[u*w1+v]).Lighter(0.85).ConvertToUnsigned(); //quite transparent
        return std::make_tuple(changed, count);
    }();
    if (changed) {
        cairo_surface_mark_dirty(s1);
        // I'm lazy for a regex
        std::string df = argv[1];
        auto dot = df.rfind('.');
        if (dot == df.npos)
            dot = df.size();
        // df = df.substr(0, dot) + "_diff" + df.substr(dot);
        df.insert(dot, "_diff");
        // std::cerr << "df " << df << '\n';
        cairo_surface_write_to_png(s1, df.c_str());
    }
    cairo_surface_destroy(s1);
    cairo_surface_destroy(s2);
    if (w1 != w2 || h1 != h2)
        return 2;
    return count > 0;
}
