#include "bmpimage.h"

BMPImage::BMPImage(const BitmapFileHeader &fileHeader, const BITMAPINFOHEADER &infoHeader, Pixel **pixelArray) {
    this->fileHeader = fileHeader;
    this->infoHeader = infoHeader;
    this->pixelArray = pixelArray;
}

const uint32_t &BMPImage::width() const { return this->infoHeader.BitmapWidth; }

const uint32_t &BMPImage::height() const { return this->infoHeader.BitmapHeight; }

void BMPImage::write(const std::string &filename) {
    {
        std::ofstream ofs(filename, std::ios_base::binary);
        ofs.write((char *) &this->fileHeader, sizeof(this->fileHeader));
        ofs.write((char *) &this->infoHeader, sizeof(this->infoHeader));
        uint32_t byteByRow = this->infoHeader.BitmapWidth * 3;
        uint8_t padding = 4 - this->infoHeader.BitmapWidth % 4;
        for (int i = 0; i < this->infoHeader.BitmapHeight; ++i) {
            ofs.write((char *) pixelArray[i], byteByRow);
            if (padding != 4) ofs.write(PADDING_ZEROES, padding); // Write padding
        }
    }
}

 Pixel **BMPImage::pixels(){
    return this->pixelArray;
}

Pixel **BMPImage::pixels_copy() {
    Pixel **newPixelArray;
    newPixelArray = new Pixel *[this->infoHeader.BitmapHeight];
    for (int i = 0; i < this->infoHeader.BitmapHeight; ++i) {
        newPixelArray[i] = new Pixel[this->infoHeader.BitmapWidth];
        std::copy(this->pixelArray[i], this->pixelArray[i] + this->infoHeader.BitmapWidth, newPixelArray[i]);
    }
    return newPixelArray;
}

BitmapFileHeader BMPImage::fileHeader_copy() {
    return this->fileHeader;
}

BITMAPINFOHEADER BMPImage::infoHeader_copy() {
    return this->infoHeader;
}

BMPImage::~BMPImage() {
    for (int i = 0; i < this->infoHeader.BitmapHeight; ++i) {
        delete[] this->pixelArray[i];
    }
    delete[] this->pixelArray;
}

BMPImage readBMPImage(const std::string &filename) {
    BitmapFileHeader bitmapFileHeader;
    BITMAPINFOHEADER bitmapInfoHeader;
    Pixel **pixelArray;
    uint32_t DIB_Header_Size;
    {
        std::ifstream ifs(filename, std::ios_base::binary);
        if (!ifs.good()) {
            throw std::runtime_error("File read error");
        }
        ifs.seekg(0, std::ios::beg);
        ifs.read((char *) &bitmapFileHeader, sizeof(bitmapFileHeader));
        ifs.read((char *) &DIB_Header_Size, sizeof(DIB_Header_Size));
    }
    if (DIB_Header_Size != 40) {
        throw std::runtime_error("Invalid header");
    }
    {
        std::ifstream ifs(filename, std::ios_base::binary);
        if (!ifs.good()) {
            throw std::runtime_error("File read error");
        }
        ifs.seekg(14, std::ios::beg);
        ifs.read((char *) &bitmapInfoHeader, sizeof(bitmapInfoHeader));
    }
    pixelArray = new Pixel *[bitmapInfoHeader.BitmapHeight];
    {
        std::ifstream ifs(filename, std::ios_base::binary);
        if (!ifs.good()) {
            throw std::runtime_error("File read error");
        }
        ifs.seekg(bitmapFileHeader.imageDataOffset, std::ios::beg);
        uint32_t byteByRow = bitmapInfoHeader.BitmapWidth * 3;
        uint8_t padding = 4 - bitmapInfoHeader.BitmapWidth % 4;
        for (int i = 0; i < bitmapInfoHeader.BitmapHeight; ++i) {
            pixelArray[i] = new Pixel[bitmapInfoHeader.BitmapWidth];
            ifs.read((char *) pixelArray[i], byteByRow);
            if (padding != 4) ifs.seekg(padding, std::ios_base::cur); // Skip padding
        }
    }
    return {bitmapFileHeader, bitmapInfoHeader, pixelArray};
}