#pragma once

#include <cstdint>
#include <string>
#include <fstream>
#include "psf.h"

const char PADDING_ZEROES[3] = {0, 0, 0};

#pragma pack(push, 1)
struct BitmapFileHeader {
    char signature[2] = {'B', 'M'};
    uint32_t fileSize = 0;
    uint16_t reserved1 = 0;
    uint16_t reserved2 = 0;
    uint32_t imageDataOffset = 54;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct BITMAPINFOHEADER {
    uint32_t HeaderSize = 40;
    uint32_t BitmapWidth = 0;
    uint32_t BitmapHeight = 0;
    uint16_t ColorPlanes = 1;
    uint16_t BitsPerPixel = 24;
    uint32_t CompressionMethod = 0;
    uint32_t ImageSize = 0;
    int32_t HorizontalPixelPerMetre = 0;
    int32_t VerticalPixelPerMetre = 0;
    uint32_t NumberOfColors = 0;
    uint32_t NumberOfImportantColors = 0;
};
#pragma pack(pop)

#pragma pack(push, 1)
struct Pixel {
    uint8_t r = 255;
    uint8_t g = 0;
    uint8_t b = 255;
};
#pragma pack(pop)

class PixelArray {
    Pixel **array;
    uint32_t _width;
    uint32_t _height;
public:
    PixelArray(uint32_t width, uint32_t height);

    PixelArray(const PixelArray &);

    ~PixelArray();

    [[nodiscard]] const uint32_t &width() const;

    [[nodiscard]] const uint32_t &height() const;

//    Pixel operator()(const uint32_t &, const uint32_t &);
//    Pixel* operator()(const uint32_t &);
    Pixel &operator()(const uint32_t &, const uint32_t &);

    Pixel *&operator()(const uint32_t &);
};

class BMPImage {
    BitmapFileHeader fileHeader;
    BITMAPINFOHEADER infoHeader;
    PixelArray pixelArray;
public:
    BMPImage(const BitmapFileHeader &fileHeader, const BITMAPINFOHEADER &infoHeader, const PixelArray &pixelArray);

    BMPImage(const PixelArray &pixelArray);

    ~BMPImage();

    [[nodiscard]] const uint32_t &width() const;

    [[nodiscard]] const uint32_t &height() const;

    [[nodiscard]] PixelArray pixels();

    [[nodiscard]] PixelArray pixels_copy();

    [[nodiscard]] BitmapFileHeader fileHeader_copy();

    [[nodiscard]] BITMAPINFOHEADER infoHeader_copy();

    void save(const std::string &);

    BMPImage appendRight(BMPImage &);

    BMPImage overlay(BMPImage &, uint32_t, uint32_t);
};

BMPImage readBMPImage(const std::string &filename);

Pixel operator/(const Pixel &, const uint8_t &);

Pixel operator+(const Pixel &, const Pixel &);

Pixel operator*(const Pixel &, const int &);

Pixel operator-(const Pixel &, const uint8_t &);

Pixel operator-(const uint8_t &, const Pixel &);

Pixel operator-(const Pixel &, const Pixel &);

BMPImage grayscale(BMPImage &);

BMPImage invertColors(BMPImage &);

BMPImage upscale2x(BMPImage &);

BMPImage downscale2x(BMPImage &);

BMPImage upscale1_5x(BMPImage &);

BMPImage upscale1_5x_ver2(BMPImage &); // TODO: BAD

BMPImage upscale2x_ver2(BMPImage &); // TODO: BAD

BMPImage textImg(const std::u16string &, Font *font, uint8_t scale = 1, Pixel background_color = Pixel{0, 0, 0},
                 Pixel font_color = Pixel{255, 255, 255});

uint8_t ui8_clamp(int value, uint8_t min = 0, uint8_t max = 255);

BMPImage filter(BMPImage &img, int mask[9], uint8_t modifier = 9);