/*
 * 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 <cstring>
#include <type_traits>

namespace rust_icc {

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
    out_skcms->input_channels = rust_a2b.input_channels;
    if (rust_a2b.input_channels > 0) {
        if (rust_a2b.input_channels < 1 || rust_a2b.input_channels > 4) {
            return false;
        }
        // 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 (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];
        }
    }

    // Output curves: If output_channels is non-zero, ensure we have enough curves
    out_skcms->output_channels = rust_a2b.output_channels;
    if (rust_a2b.output_channels > 0) {
        if (rust_a2b.output_channels > 4) {
            return false;
        }
        // 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 (rust_b2a.is_16bit_grid) {
            out_skcms->grid_16 = rust_b2a.grid_data.data();
        } else {
            out_skcms->grid_8 = rust_b2a.grid_data.data();
        }
    }

    // Output curves
    if (rust_b2a.output_channels < 1 || 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)) {
            return false;
        }
    }

    // A profile needs either a matrix, transfer curves, or A2B/B2A to be useful
    if (!out_skcms->has_toXYZD50 && !out_skcms->has_trc &&
        !out_skcms->has_A2B && !out_skcms->has_B2A) {
        return false;
    }

    return true;
}

}  // namespace rust_icc
