#include "pch.h"
#include "log.h"
#include "image.h"
#include "asset.h"

#include <stb/stb_image.h>

bool Image::load(std::string filename)
{
    if (Asset::is_asset(filename))
    {
        Asset file;
        if (!(file.open(filename.c_str()) && file.read_all()))
        {
            file.close();
            return false;
        }
        stbi_set_flip_vertically_on_load(false);
        uint8_t* buffer = stbi_load_from_memory(file.m_data, file.m_len, &width, &height, nullptr, 4);
        file.close();
        comp = 4;
        m_data = std::unique_ptr<uint8_t[]>(buffer);
        return true;
    }
    else
    {
        return load_file(filename);
    }
}

bool Image::load_file(std::string filename)
{
    stbi_set_flip_vertically_on_load(false);
    uint8_t* buffer = stbi_load(filename.c_str(), &width, &height, nullptr, 4);
    if (!buffer)
        return false;
    comp = 4;
    m_data = std::unique_ptr<uint8_t[]>(buffer);
    return true;
}

bool Image::save(const std::string& path)
{
    bool ret = stbi_write_png(path.c_str(), width, height, comp, data(), 0);
    if (!ret)
        LOG("failed Image::save %s", path.c_str());
    return ret;
}

void Image::flip()
{
    auto flipped = std::make_unique<uint8_t[]>(width*height*4);
    int line_size = width * 4;
    const uint8_t* src = m_data.get();
    uint8_t* dst = flipped.get() + line_size * (height - 1);
    for (int y = 0; y < height; y++)
    {
        std::copy(src, src+line_size, dst);
        src += line_size;
        dst -= line_size;
    }
    std::swap(m_data, flipped);
}

void Image::gayscale_alpha()
{
    int np = width * height;
    auto ptr = reinterpret_cast<glm::u8vec4*>(m_data.get());
    for (int i = 0; i < np; i++)
    {
        auto& c = ptr[i];
        c.g = c.b = c.r = 255 - c.a;
        c.a = 255;
    }
}

Image Image::resize(int w, int h) const
{
    Image ret;
    ret.create(w, h);
    auto temp = (glm::u8vec4*)ret.data();
    auto pixels = (glm::u8vec4*)data();
    float x_ratio = ((float)(width - 1)) / w;
    float y_ratio = ((float)(height - 1)) / h;
    int offset = 0;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            int x = (int)(x_ratio * j);
            int y = (int)(y_ratio * i);
            float x_diff = (x_ratio * j) - x;
            float y_diff = (y_ratio * i) - y;
            int index = y * width + x;

            // range is 0 to 255 thus bitwise AND with 0xff
            glm::vec4 A = pixels[index];
            glm::vec4 B = pixels[index + 1];
            glm::vec4 C = pixels[index + width];
            glm::vec4 D = pixels[index + width + 1];

            // Y = A(1-w)(1-h) + B(w)(1-h) + C(h)(1-w) + Dwh
            glm::vec4 gray = A*(1 - x_diff)*(1 - y_diff) + B * (x_diff)*(1 - y_diff) +
                C * (y_diff)*(1 - x_diff) + D * (x_diff*y_diff);
            temp[offset++] = glm::clamp(gray, glm::vec4(0), glm::vec4(255));
        }
    }
    return ret;
}

Image Image::resize_power2() const
{
    int w = pow(2, ceil(log(width) / log(2)));
    int h = pow(2, ceil(log(height) / log(2)));
    if (w == width && h == height)
    {
        Image i;
        i.create(width, height);
        std::copy(m_data.get(), m_data.get() + width * height * comp, i.m_data.get());
        return i;
    }
    return resize(w, h);
}

Image Image::resize_squared(const glm::u8vec4& bg) const
{
    Image ret;
    if (width == height)
    {
        ret.create(width, height);
        std::copy(m_data.get(), m_data.get() + width * height * comp, ret.m_data.get());
    }
    else
    {
        int pad_x = 0;
        int pad_y = 0;
        int size = 0;
        if (height > width)
        {
            size = height;
            pad_x = (size - width) / 2;
        }
        else
        {
            size = width;
            pad_y = (size - height) / 2;
        }
        ret.create(size, size);
        auto ptr_src = reinterpret_cast<glm::u8vec4*>(m_data.get());
        auto ptr_dst = reinterpret_cast<glm::u8vec4*>(ret.m_data.get());
        std::fill_n(ptr_dst, size * size, bg);
        for (int y = 0; y < height; y++)
            std::copy_n(ptr_src + y * width, width, ptr_dst + pad_x + (y + pad_y) * ret.width);
    }
    return ret;
}
