/*
 *  nextpnr -- Next Generation Place and Route
 *
 *  Copyright (C) 2023  gatecat <gatecat@ds0.me>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#ifndef STATIC_UTIL_H
#define STATIC_UTIL_H

#include <fstream>
#include "log.h"
#include "nextpnr_assertions.h"
#include "nextpnr_namespaces.h"

NEXTPNR_NAMESPACE_BEGIN

namespace StaticUtil {

enum class Axis
{
    X,
    Y
};

template <typename T> struct RealPairTempl
{
    RealPairTempl() : x(0), y(0) {};
    RealPairTempl(T x, T y) : x(x), y(y) {};
    explicit RealPairTempl(Loc l, T bias = 0.0f) : x(l.x + bias), y(l.y + bias) {};
    T x, y;
    RealPairTempl &operator+=(const RealPairTempl &other)
    {
        x += other.x;
        y += other.y;
        return *this;
    }
    RealPairTempl &operator/=(T factor)
    {
        x /= factor;
        y /= factor;
        return *this;
    }
    template <typename Tf> friend RealPairTempl<Tf> operator+(RealPairTempl<Tf> a, RealPairTempl<Tf> b);
    template <typename Tf> friend RealPairTempl<Tf> operator-(RealPairTempl<Tf> a, RealPairTempl<Tf> b);
    RealPairTempl operator*(T factor) const { return RealPairTempl(x * factor, y * factor); }
    RealPairTempl operator/(T factor) const { return RealPairTempl(x / factor, y / factor); }
    // to simplify axis-generic code
    inline T &at(Axis axis) { return (axis == Axis::Y) ? y : x; }
    inline const T &at(Axis axis) const { return (axis == Axis::Y) ? y : x; }
};
template <typename T> inline RealPairTempl<T> operator+(RealPairTempl<T> a, RealPairTempl<T> b)
{
    return RealPairTempl(a.x + b.x, a.y + b.y);
}
template <typename T> inline RealPairTempl<T> operator-(RealPairTempl<T> a, RealPairTempl<T> b)
{
    return RealPairTempl(a.x - b.x, a.y - b.y);
}

using RealPair = RealPairTempl<float>;
using DoublePair = RealPairTempl<double>;

// array2d; but as ourafft wants it
struct FFTArray
{
    FFTArray(int width = 0, int height = 0) : m_width(width), m_height(height)
    {
        alloc();
        fill(0);
    }

    void fill(float value)
    {
        for (int x = 0; x < m_width; x++)
            for (int y = 0; y < m_height; y++)
                m_data[x][y] = value;
    }

    void reset(int width, int height, float value = 0)
    {
        if (width != m_height || height != m_height) {
            destroy();
            m_width = width;
            m_height = height;
            alloc();
        }
        fill(value);
    }

    float &at(int x, int y)
    {
        NPNR_ASSERT(x >= 0 && x < m_width && y >= 0 && y < m_height);
        return m_data[x][y];
    }
    float at(int x, int y) const
    {
        NPNR_ASSERT(x >= 0 && x < m_width && y >= 0 && y < m_height);
        return m_data[x][y];
    }
    float **data() { return m_data; }

    void write_csv(const std::string &filename) const
    {
        std::ofstream out(filename);
        if (!out.is_open()) {
            log_error("Failed to open CSV file for writing '%s': %s.\n", filename.c_str(),
                      std::error_code(errno, std::generic_category()).message().c_str());
        }
        NPNR_ASSERT(out);
        for (int y = 0; y < m_height; y++) {
            for (int x = 0; x < m_width; x++) {
                out << at(x, y) << ",";
            }
            out << std::endl;
        }
    }

    ~FFTArray() { destroy(); }

  private:
    int m_width, m_height;
    float **m_data = nullptr;
    void alloc()
    {
        if (m_width == 0)
            return;
        m_data = new float *[m_width];
        for (int x = 0; x < m_width; x++)
            m_data[x] = (m_height > 0) ? (new float[m_height]) : nullptr;
    }
    void destroy()
    {
        for (int x = 0; x < m_width; x++)
            delete[] m_data[x];
        delete[] m_data;
    }
};

}; // namespace StaticUtil

NEXTPNR_NAMESPACE_END

#endif
