// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

/**
 * @file perceptual_hash.hpp
 * @brief 画像のパーセプチュアルハッシュ (OCV-30: img_hash)
 *
 * aHash (Average Hash), pHash (Perceptual Hash), dHash (Difference Hash)
 * 類似画像検出・重複検出に使用。
 */

#ifndef CALX_IMAGE_IMGPROC_PERCEPTUAL_HASH_HPP
#define CALX_IMAGE_IMGPROC_PERCEPTUAL_HASH_HPP

#include <image/Image.hpp>
#include <cstdint>
#include <cmath>
#include <algorithm>

namespace calx::image {

/// Average Hash (aHash): 8×8 に縮小 → 平均値で 2 値化 → 64bit
template<typename T>
uint64_t averageHash(const Image<T>& img) {
    // グレースケール + 8×8 リサイズ
    Image<T> gray = (img.numChannels() >= 3) ? img.toGray() : img;
    auto small = gray.resize(8, 8);

    // 平均値
    T sum = T(0);
    for (int y = 0; y < 8; ++y)
        for (int x = 0; x < 8; ++x)
            sum += small(y, x);
    T avg = sum / T(64);

    // ハッシュ生成
    uint64_t hash = 0;
    for (int y = 0; y < 8; ++y)
        for (int x = 0; x < 8; ++x) {
            hash <<= 1;
            if (small(y, x) > avg) hash |= 1;
        }
    return hash;
}

/// Difference Hash (dHash): 8×9 に縮小 → 水平隣接差分で 2 値化 → 64bit
template<typename T>
uint64_t differenceHash(const Image<T>& img) {
    Image<T> gray = (img.numChannels() >= 3) ? img.toGray() : img;
    auto small = gray.resize(8, 9);

    uint64_t hash = 0;
    for (int y = 0; y < 8; ++y)
        for (int x = 0; x < 8; ++x) {
            hash <<= 1;
            if (small(y, x) < small(y, x + 1)) hash |= 1;
        }
    return hash;
}

/// Perceptual Hash (pHash): 32×32 → DCT → 8×8 低周波 → 中央値で 2 値化
template<typename T>
uint64_t perceptualHash(const Image<T>& img) {
    Image<T> gray = (img.numChannels() >= 3) ? img.toGray() : img;
    auto small = gray.resize(32, 32);

    // 簡易 DCT (32×32 → 8×8 低周波成分)
    constexpr int N = 32, K = 8;
    constexpr double pi = 3.14159265358979323846;
    double dct[K][K];
    for (int u = 0; u < K; ++u)
        for (int v = 0; v < K; ++v) {
            double sum = 0;
            for (int y = 0; y < N; ++y)
                for (int x = 0; x < N; ++x)
                    sum += double(small(y, x))
                         * std::cos(pi * (2*x+1) * u / (2*N))
                         * std::cos(pi * (2*y+1) * v / (2*N));
            double cu = (u == 0) ? 1.0/std::sqrt(2.0) : 1.0;
            double cv = (v == 0) ? 1.0/std::sqrt(2.0) : 1.0;
            dct[u][v] = sum * cu * cv * 2.0 / N;
        }

    // DC 成分を除いた中央値
    double values[K*K - 1];
    int idx = 0;
    for (int u = 0; u < K; ++u)
        for (int v = 0; v < K; ++v)
            if (u != 0 || v != 0) values[idx++] = dct[u][v];
    std::sort(values, values + idx);
    double median = values[idx / 2];

    // ハッシュ生成
    uint64_t hash = 0;
    for (int u = 0; u < K; ++u)
        for (int v = 0; v < K; ++v) {
            hash <<= 1;
            if (dct[u][v] > median) hash |= 1;
        }
    return hash;
}

/// ハミング距離 (2 つのハッシュの類似度)
inline int hammingDistance(uint64_t h1, uint64_t h2) {
    uint64_t diff = h1 ^ h2;
    int count = 0;
    while (diff) { count += diff & 1; diff >>= 1; }
    return count;
}

} // namespace calx::image

#endif
