/*
 * Copyright 2025 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "rust/icc/FFI.h"
#include "rust/icc/FFI.rs.h"  // Generated by CXX bridge
#include "modules/skcms/skcms.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>

namespace rust_icc {

static constexpr uint64_t kMaxGridPoints = 350000000;

// Keep this at the skcms bridge boundary: moxcms has its own parser limits,
// while C++ tests and future callers can still construct rust_icc::IccProfile directly.
static bool ValidateGridPoints(const uint8_t grid_points[4], uint32_t active_channels) {
    if (active_channels < 1 || active_channels > 4) {
        return false;
    }

    uint64_t total_grid_points = 1;
    for (uint32_t i = 0; i < active_channels; ++i) {
        if (grid_points[i] < 2) {
            return false;
        }
        total_grid_points *= grid_points[i];
        if (total_grid_points > kMaxGridPoints) {
            return false;
        }
    }
    return true;
}

void ToSkcmsMatrix3x3(const Matrix3x3& rust_matrix, skcms_Matrix3x3* out_skcms) {
    // Note: std::is_layout_compatible_v (C++20) is not yet implemented in LLVM (P0466R5).
    // Use sizeof/alignof + standard_layout + trivially_copyable as the next best alternative.
    static_assert(sizeof(Matrix3x3) == sizeof(skcms_Matrix3x3),
                  "Matrix3x3 must have same size as skcms_Matrix3x3 for memcpy");
    static_assert(alignof(Matrix3x3) == alignof(skcms_Matrix3x3),
                  "Matrix3x3 must have same alignment as skcms_Matrix3x3 for memcpy");
    static_assert(std::is_standard_layout_v<Matrix3x3>,
                  "Matrix3x3 must have standard layout for safe memcpy");
    static_assert(std::is_trivially_copyable_v<Matrix3x3>,
                  "Matrix3x3 must be trivially copyable for memcpy");
    memcpy(out_skcms, &rust_matrix, sizeof(skcms_Matrix3x3));
}

void ToSkcmsTransferFunction(const TransferFunction& rust_tf,
                             skcms_TransferFunction* out_skcms) {
    static_assert(sizeof(TransferFunction) == sizeof(skcms_TransferFunction),
                  "TransferFunction must have same size as skcms_TransferFunction for memcpy");
    static_assert(alignof(TransferFunction) == alignof(skcms_TransferFunction),
                  "TransferFunction must have same alignment as skcms_TransferFunction for memcpy");
    static_assert(std::is_standard_layout_v<TransferFunction>,
                  "TransferFunction must have standard layout for safe memcpy");
    static_assert(std::is_trivially_copyable_v<TransferFunction>,
                  "TransferFunction must be trivially copyable for memcpy");
    // Spot-check field offsets in skcms_TransferFunction to verify the unusual ordering (g, a, b, c, d, e, f)
    static_assert(offsetof(skcms_TransferFunction, g) == 0,
                  "skcms_TransferFunction::g must be at offset 0");
    static_assert(offsetof(skcms_TransferFunction, a) == sizeof(float),
                  "skcms_TransferFunction::a must be at offset sizeof(float)");
    memcpy(out_skcms, &rust_tf, sizeof(skcms_TransferFunction));
}

static void ToSkcmsCurve(const rust_icc::Curve& rust_curve, skcms_Curve* out_skcms) {
    // Note: Curve table data (table_16) is borrowed from Rust Vec via .data().
    // See ToSkcmsIccProfile documentation about lifetime requirements.
    if (rust_curve.table_entries == 0) {
        out_skcms->table_entries = 0;
        ToSkcmsTransferFunction(rust_curve.parametric, &out_skcms->parametric);
    } else {
        out_skcms->table_entries = rust_curve.table_entries;
        out_skcms->table_8 = nullptr;
        out_skcms->table_16 = rust_curve.table_data.data();
    }
}

static bool ToSkcmsA2B(const rust_icc::A2B& rust_a2b, skcms_A2B* out_skcms) {
    memset(out_skcms, 0, sizeof(skcms_A2B));

    // Input curves: If input_channels is non-zero, ensure we have enough curves
    if (rust_a2b.input_channels > 4) {
        return false;
    }
    out_skcms->input_channels = rust_a2b.input_channels;
    if (rust_a2b.input_channels > 0) {
        // Only validate curve count if input_channels is specified
        if (!rust_a2b.input_curves.empty() && rust_a2b.input_channels > rust_a2b.input_curves.size()) {
            return false;
        }
        for (size_t i = 0; i < rust_a2b.input_curves.size() && i < 4; i++) {
            ToSkcmsCurve(rust_a2b.input_curves[i], &out_skcms->input_curves[i]);
        }
    }

    // Grid data
    static_assert(std::is_same_v<decltype(out_skcms->grid_points), uint8_t[4]>,
                  "skcms_A2B::grid_points must be uint8_t[4]");
    static_assert(std::is_trivially_copyable_v<uint8_t>,
                  "uint8_t must be trivially copyable for memcpy");
    if (rust_a2b.grid_points.size() != 4) {
        return false;
    }
    memcpy(out_skcms->grid_points, rust_a2b.grid_points.data(), 4);
    if (!rust_a2b.grid_data.empty()) {
        if (!ValidateGridPoints(out_skcms->grid_points, out_skcms->input_channels)) {
            return false;
        }
        if (rust_a2b.is_16bit_grid) {
            out_skcms->grid_16 = rust_a2b.grid_data.data();
        } else {
            out_skcms->grid_8 = rust_a2b.grid_data.data();
        }
    }

    // Matrix curves and matrix
    // If matrix_channels is explicitly set, use it. Otherwise, infer from matrix_curves presence.
    uint32_t effective_matrix_channels = rust_a2b.matrix_channels;
    if (effective_matrix_channels == 0 && !rust_a2b.matrix_curves.empty()) {
        // Infer matrix_channels from the number of matrix curves provided
        effective_matrix_channels = std::min(static_cast<uint32_t>(rust_a2b.matrix_curves.size()), 3u);
    }

    if (effective_matrix_channels > rust_a2b.matrix_curves.size()) {
        return false;
    }
    if (effective_matrix_channels > 3) {
        return false;
    }
    out_skcms->matrix_channels = effective_matrix_channels;
    for (size_t i = 0; i < rust_a2b.matrix_curves.size() && i < 3; i++) {
        ToSkcmsCurve(rust_a2b.matrix_curves[i], &out_skcms->matrix_curves[i]);
    }

    // Copy 3x3 matrix portion (only if we have matrix curves)
    if (effective_matrix_channels > 0) {
        skcms_Matrix3x3 temp_matrix;
        ToSkcmsMatrix3x3(rust_a2b.matrix, &temp_matrix);
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 3; col++) {
                out_skcms->matrix.vals[row][col] = temp_matrix.vals[row][col];
            }
            // 4th column is the bias/offset
            out_skcms->matrix.vals[row][3] = rust_a2b.matrix_bias[row];
        }
    }

    // A2B output is always PCS (XYZ/Lab), which is 3-dimensional (crbug.com/506010945).
    if (rust_a2b.output_channels != 3) {
        return false;
    }
    out_skcms->output_channels = rust_a2b.output_channels;
    if (rust_a2b.output_channels > 0) {
        // Only validate curve count if output_channels is specified
        if (!rust_a2b.output_curves.empty() && rust_a2b.output_channels > rust_a2b.output_curves.size()) {
            return false;
        }
        for (size_t i = 0; i < rust_a2b.output_curves.size() && i < 4; i++) {
            ToSkcmsCurve(rust_a2b.output_curves[i], &out_skcms->output_curves[i]);
        }
    }
    return true;
}

// Helper to populate skcms_B2A from Rust B2A data
static bool ToSkcmsB2A(const rust_icc::B2A& rust_b2a, skcms_B2A* out_skcms) {
    memset(out_skcms, 0, sizeof(skcms_B2A));

    // Input curves
    if (rust_b2a.input_channels != 3) {
        return false;
    }
    if (rust_b2a.input_curves.size() < 3) {
        return false;
    }
    out_skcms->input_channels = rust_b2a.input_channels;
    for (size_t i = 0; i < 3; i++) {
        ToSkcmsCurve(rust_b2a.input_curves[i], &out_skcms->input_curves[i]);
    }

    // Matrix and matrix curves
    // If matrix_channels is explicitly set, use it. Otherwise, infer from matrix_curves presence.
    uint32_t effective_matrix_channels = rust_b2a.matrix_channels;
    if (effective_matrix_channels == 0 && !rust_b2a.matrix_curves.empty()) {
        // Infer matrix_channels from the number of matrix curves provided
        effective_matrix_channels = std::min(static_cast<uint32_t>(rust_b2a.matrix_curves.size()), 3u);
    }

    if (effective_matrix_channels > rust_b2a.matrix_curves.size()) {
        return false;
    }
    if (effective_matrix_channels > 3) {
        return false;
    }
    out_skcms->matrix_channels = effective_matrix_channels;

    // Copy matrix and matrix curves only if matrix stage is active
    if (effective_matrix_channels > 0) {
        skcms_Matrix3x3 temp_matrix;
        ToSkcmsMatrix3x3(rust_b2a.matrix, &temp_matrix);
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 3; col++) {
                out_skcms->matrix.vals[row][col] = temp_matrix.vals[row][col];
            }
            out_skcms->matrix.vals[row][3] = rust_b2a.matrix_bias[row];
        }

        for (size_t i = 0; i < rust_b2a.matrix_curves.size() && i < 3; i++) {
            ToSkcmsCurve(rust_b2a.matrix_curves[i], &out_skcms->matrix_curves[i]);
        }
    }

    // Grid data
    static_assert(std::is_same_v<decltype(out_skcms->grid_points), uint8_t[4]>,
                  "skcms_B2A::grid_points must be uint8_t[4]");
    static_assert(std::is_trivially_copyable_v<uint8_t>,
                  "uint8_t must be trivially copyable for memcpy");
    if (rust_b2a.grid_points.size() != 4) {
        return false;
    }
    memcpy(out_skcms->grid_points, rust_b2a.grid_points.data(), 4);
    if (!rust_b2a.grid_data.empty()) {
        if (!ValidateGridPoints(out_skcms->grid_points, rust_b2a.output_channels)) {
            return false;
        }
        if (rust_b2a.is_16bit_grid) {
            out_skcms->grid_16 = rust_b2a.grid_data.data();
        } else {
            out_skcms->grid_8 = rust_b2a.grid_data.data();
        }
    }

    // skcms requires 3 (RGB) or 4 (CMYK) output channels for B2A.
    if (rust_b2a.output_channels < 3 || rust_b2a.output_channels > 4) {
        return false;
    }
    if (rust_b2a.output_channels > rust_b2a.output_curves.size()) {
        return false;
    }
    out_skcms->output_channels = rust_b2a.output_channels;
    for (size_t i = 0; i < rust_b2a.output_channels; i++) {
        ToSkcmsCurve(rust_b2a.output_curves[i], &out_skcms->output_curves[i]);
    }
    return true;
}

bool ToSkcmsIccProfile(const IccProfile& rust_profile, skcms_ICCProfile* out_skcms) {
    memset(out_skcms, 0, sizeof(skcms_ICCProfile));

    // Copy color space information
    out_skcms->data_color_space = static_cast<uint32_t>(rust_profile.data_color_space);
    out_skcms->pcs = static_cast<uint32_t>(rust_profile.connection_space);

    // Copy toXYZD50 matrix if present
    out_skcms->has_toXYZD50 = rust_profile.has_to_xyzd50;
    if (rust_profile.has_to_xyzd50) {
        ToSkcmsMatrix3x3(rust_profile.to_xyzd50, &out_skcms->toXYZD50);
    }

    // Copy transfer curves if present preserving the original curve type
    // (parametric or table).
    out_skcms->has_trc = rust_profile.has_trc;
    if (rust_profile.has_trc) {
        ToSkcmsCurve(rust_profile.trc_r, &out_skcms->trc[0]);
        ToSkcmsCurve(rust_profile.trc_g, &out_skcms->trc[1]);
        ToSkcmsCurve(rust_profile.trc_b, &out_skcms->trc[2]);
    }

    // Copy CICP data if present
    out_skcms->has_CICP = rust_profile.has_cicp;
    if (rust_profile.has_cicp) {
        out_skcms->CICP.color_primaries = rust_profile.cicp.color_primaries;
        out_skcms->CICP.transfer_characteristics = rust_profile.cicp.transfer_characteristics;
        out_skcms->CICP.matrix_coefficients = rust_profile.cicp.matrix_coefficients;
        out_skcms->CICP.video_full_range_flag = rust_profile.cicp.video_full_range_flag;
    }

    // Populate A2B and B2A transforms if present
    out_skcms->has_A2B = rust_profile.has_a2b;
    if (rust_profile.has_a2b) {
        if (!ToSkcmsA2B(rust_profile.a2b, &out_skcms->A2B)) {
            return false;
        }
    }

    out_skcms->has_B2A = rust_profile.has_b2a;
    if (rust_profile.has_b2a) {
        if (!ToSkcmsB2A(rust_profile.b2a, &out_skcms->B2A)) {
            // Non-fatal: a2b_to_b2a() can produce invalid channel counts
            // for CMYK and curve-only B2A profiles. Profile is still usable via A2B.
            out_skcms->has_B2A = false;
        }
    }

    // To be usable as a source profile in skcms_Transform, a profile needs
    // either an A2B transform or both TRC curves and a toXYZD50 matrix.
    // This mirrors skcms_Parse's usable_as_src() validation.
    if (!out_skcms->has_A2B &&
        !(out_skcms->has_trc && out_skcms->has_toXYZD50)) {
        return false;
    }

    return true;
}

}  // namespace rust_icc
