/*  Python-rgbcx Texture Compression Library
    Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
    Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
    and licenced under the public domain

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "interpolator.h"

#include <array>
#include <cassert>
#include <cstdint>
#include <stdexcept>

#include "util.h"

namespace rgbcx {

/*
Interpolator::Interpolator() {
    PrepSingleColorTables(_single_match5, _single_match5_half, 5);
    PrepSingleColorTables(_single_match5, _single_match5_half, 6);
}

void Interpolator::PrepSingleColorTables(const MatchListPtr &matchTable, const MatchListPtr &matchTableHalf, int len) {
    int size = 1 << len;

    assert((len == 5 && size == Size5) || (len == 6 && size == size6));

    const uint8_t *expand = (len == 5) ? &Expand5[0] : &Expand6[0];

    bool ideal = IsIdeal();
    bool use_e = useExpandedInMatch();

    for (int i = 0; i < match_count; i++) {
        int lowest_error = 256;
        int lowest_half_error = 256;

        for (int low = 0; low < size; low++) {
            const int low_e = expand[low];
            const int low_val = use_e ? low_e : low;

            for (int high = 0; high < size; high++) {
                const int high_e = expand[high];
                const int high_val = use_e ? high_e : high;

                int v = (len == 5) ? Interpolate5(high_val, low_val) : Interpolate6(high_val, low_val);
                int v_half = (len == 5) ? InterpolateHalf5(low_val, high_val) : InterpolateHalf6(low_val, high_val);

                int error = PrepSingleColorTableEntry(matchTable, v, i, low, high, low_e, high_e, lowest_error, false, ideal);
                int half_error = PrepSingleColorTableEntry(matchTableHalf, v, i, low, high, low_e, high_e, lowest_error, true, ideal);

                if (error < lowest_error) lowest_error = error;
                if (half_error < lowest_half_error) lowest_half_error = half_error;
            }
        }
    }
}
int Interpolator::PrepSingleColorTableEntry(const MatchListPtr &matchTable, int v, int i, int low, int high, int low_e, int high_e, int lowest_error, bool half,
                                            bool ideal) {
    int e = iabs(v - i);

    // We only need to factor in 3% error in BC1 ideal mode.
    if (ideal) e += (iabs(high_e - low_e) * 3) / 100;

    // Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation.
    if ((e < lowest_error) || (e == lowest_error && low == high)) {
        assert(e <= UINT8_MAX);

        auto &entry = (*matchTable)[i];
        entry.low = low;
        entry.high = high;
        entry.error = e;
    }

    return e;
}*/

// region Interpolator implementation
std::unique_ptr<Interpolator> Interpolator::MakeInterpolator(Interpolator::Type type) {
    switch (type) {
        case Type::Ideal:
            return std::make_unique<Interpolator>();
        case Type::IdealRound:
            return std::make_unique<InterpolatorRound>();
        case Type::Nvidia:
            return std::make_unique<InterpolatorNvidia>();
        case Type::AMD:
            return std::make_unique<InterpolatorAMD>();
        default:
            throw std::invalid_argument("Invalid interpolator type");
    }
}

uint8_t Interpolator::Interpolate5(uint8_t v0, uint8_t v1) const { return Interpolate8(scale5To8(v0), scale5To8(v1)); }
uint8_t Interpolator::Interpolate6(uint8_t v0, uint8_t v1) const { return Interpolate8(scale6To8(v0), scale6To8(v1)); }
uint8_t Interpolator::InterpolateHalf5(uint8_t v0, uint8_t v1) const { return InterpolateHalf8(scale5To8(v0), scale5To8(v1)); }
uint8_t Interpolator::InterpolateHalf6(uint8_t v0, uint8_t v1) const { return InterpolateHalf8(scale6To8(v0), scale6To8(v1)); }

std::array<Color, 4> Interpolator::InterpolateBC1(uint16_t low, uint16_t high) const {
    auto colors = std::array<Color, 4>();
    colors[0] = Color::Unpack565(low);
    colors[1] = Color::Unpack565(high);

    if (low > high) {
        // 4-color mode
        colors[2] = InterpolateColor24(colors[0], colors[1]);
        colors[3] = InterpolateColor24(colors[1], colors[0]);
    } else {
        // 3-color mode
        colors[2] = InterpolateHalfColor24(colors[0], colors[1]);
        colors[3] = Color(0, 0, 0, 0);  // transparent black
    }

    return colors;
}

uint8_t Interpolator::Interpolate8(uint8_t v0, uint8_t v1) const {
    return (v0 * 2 + v1) / 3;
}

uint8_t Interpolator::InterpolateHalf8(uint8_t v0, uint8_t v1) const {
    return (v0 + v1) / 2;
}
// endregion

// region InterpolatorRound implementation
uint8_t InterpolatorRound::Interpolate5(uint8_t v0, uint8_t v1) const { return Interpolate8(scale5To8(v0), scale5To8(v1)); }
uint8_t InterpolatorRound::Interpolate6(uint8_t v0, uint8_t v1) const { return Interpolate8(scale6To8(v0), scale6To8(v1)); }

uint8_t InterpolatorRound::Interpolate8(uint8_t v0, uint8_t v1) const {
    return (v0 * 2 + v1 + 1) / 3;
}
// endregion

// region InterpolatorNvidia implementation
uint8_t InterpolatorNvidia::Interpolate5(uint8_t v0, uint8_t v1) const {
    assert(v0 < 32 && v1 < 32);
    return ((2 * v0 + v1) * 22) / 8U;
}

uint8_t InterpolatorNvidia::Interpolate6(uint8_t v0, uint8_t v1) const {
    assert(v0 < 64 && v1 < 64);
    const int gdiff = v1 - v0;
    return static_cast<uint8_t>((256 * v0 + (gdiff / 4) + 128 + gdiff * 80) >> 8);
}

uint8_t InterpolatorNvidia::InterpolateHalf5(uint8_t v0, uint8_t v1) const {
    assert(v0 < 32 && v1 < 32);
    return ((v0 + v1) * 33) / 8U;
}

uint8_t InterpolatorNvidia::InterpolateHalf6(uint8_t v0, uint8_t v1) const {
    assert(v0 < 64 && v1 < 64);
    const int gdiff = v1 - v0;
    return static_cast<uint8_t>((256 * v0 + gdiff / 4 + 128 + gdiff * 128) >> 8);
}

std::array<Color, 4> InterpolatorNvidia::InterpolateBC1(uint16_t low, uint16_t high) const {
    // Nvidia is special and interpolation cant be done with 8-bit values, so we need to override the default behavior
    auto colors = std::array<Color, 4>();
    auto low565 = Color::Unpack565Unscaled(low);
    auto high565 = Color::Unpack565Unscaled(high);
    colors[0] = low565.ScaleFrom565();
    colors[1] = high565.ScaleFrom565();

    if (low > high) {
        // 4-color mode
        colors[2] = InterpolateColor565(low565, high565);
        colors[3] = InterpolateColor565(high565, low565);
    } else {
        // 3-color mode
        colors[2] = InterpolateHalfColor565(low565, high565);
        colors[3] = Color(0, 0, 0, 0);  // transparent black
    }

    return colors;
}
// endregion

// region InterpolatorAMD implementation
uint8_t InterpolatorAMD::Interpolate5(uint8_t v0, uint8_t v1) const { return Interpolate8(scale5To8(v0), scale5To8(v1)); }
uint8_t InterpolatorAMD::Interpolate6(uint8_t v0, uint8_t v1) const { return Interpolate8(scale6To8(v0), scale6To8(v1)); }
uint8_t InterpolatorAMD::InterpolateHalf5(uint8_t v0, uint8_t v1) const { return InterpolateHalf8(scale5To8(v0), scale5To8(v1)); }
uint8_t InterpolatorAMD::InterpolateHalf6(uint8_t v0, uint8_t v1) const { return InterpolateHalf8(scale6To8(v0), scale6To8(v1)); }

uint8_t InterpolatorAMD::Interpolate8(uint8_t v0, uint8_t v1) const {
    return (v0 * 43 + v1 * 21 + 32) >> 6;
}

uint8_t InterpolatorAMD::InterpolateHalf8(uint8_t v0, uint8_t v1) const {
    return (v0 + v1 + 1) >> 1;
}
// endregion
}  // namespace rgbcx
