#ifndef UTILS_H_
#define UTILS_H_

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <limits>
#include <iostream>
#include <random>
#include <vector>
#include "matrix_storage.h"
#include "../gemmlowp/public/gemmlowp.h" 




template <typename tScalar, gemmlowp::MapOrder tOrder>
std::ostream& operator<<(std::ostream& s,
                                                 const gemmlowp::MatrixMap<tScalar, tOrder>& m) {
    for (int i = 0; i < m.rows(); i++) {
        for (int j = 0; j < m.cols(); j++) {
            if (j) {
                s << '\t';
            }
            if (std::numeric_limits<tScalar>::is_integer) {
                s << static_cast<int>(m(i, j));
            }
            else {
                s << static_cast<float>(m(i,j));
            }
        }
        s << '\n';
    }
    return s;
}

template <typename tScalar, typename tScalarPack, gemmlowp::MapOrder tOrder>
std::ostream& operator<<(std::ostream& s, 
        const PackMatrixWithStorage<tScalar, tScalarPack, tOrder>& m) {
    return s << m.ConstMap();
}

// flost to int
template <typename tScalarFloat, typename tScalarInt>
tScalarInt Quantize(tScalarFloat input, tScalarFloat scale, tScalarInt shift, int bit_count) {
    assert(!std::numeric_limits<tScalarFloat>::is_integer);
    assert(std::numeric_limits<tScalarInt>::is_integer);

    auto out_max = std::numeric_limits<tScalarInt>::max();
    auto out_min = std::numeric_limits<tScalarInt>::min();
    auto out_max_float = static_cast<tScalarFloat>(out_max >> (sizeof(tScalarInt) * 8 - bit_count));
    auto out_min_float = static_cast<tScalarFloat>(out_min >> (sizeof(tScalarInt) * 8 - bit_count));
    
    tScalarFloat val = input / scale + shift;
    val = std::max(out_min_float, std::min(out_max_float, val));
    return static_cast<tScalarInt>(std::round(val));
}

// int to float
template <typename tScalarFloat, typename tScalarInt>
tScalarFloat DeQuantize(tScalarInt input, tScalarFloat scale, tScalarInt shift) {
    assert(!std::numeric_limits<tScalarFloat>::is_integer);
    assert(std::numeric_limits<tScalarInt>::is_integer);

    return static_cast<tScalarFloat>(scale * (input - shift));
}
#endif
