#include <graphics.h>
#include <memory.h>
#include <stdint.h>
#include <stdlib.h>

static inline int32_t max(int32_t a, int32_t b) {
    return a > b ? a : b;
}

static inline int32_t min(int32_t a, int32_t b) {
    return a < b ? a : b;
}

image_data_t image_create(uint16_t width, uint16_t height) {
    image_data_t data;
    data.width = width;
    data.height = height;
    size_t length = width * height * sizeof(image_color_t);
    data.pixels = (image_color_t*)malloc(length);

    memset(data.pixels, 255, length);

    return data;
}

void image_free(image_data_t image) {
    if (image.pixels) {
        free(image.pixels);
    }
}

static inline void set_point(image_data_t image, int16_t x, int16_t y, image_color_t color) {
    image.pixels[x + y * image.width] = color;
}

void image_draw_point(image_data_t image, int16_t x, int16_t y, image_color_t color) {
    if (x >= image.width || y >= image.height || x < 0 || y < 0) {
        return;
    }
    set_point(image, x, y, color);
}

void image_draw_hline(image_data_t image, int16_t x1, int16_t x2, int16_t y, image_color_t color) {
    if (y < 0 || y >= image.height) {
        return;
    }
    if (x1 > x2) {
        int16_t temp = x1;
        x1 = x2;
        x2 = temp;
    }
    if (x1 > image.width || x2 < 0) {
        return;
    }
    x1 = (int16_t)max(0, x1);
    x2 = (int16_t)min(x2, image.width);

    do {
        set_point(image, x1++, y, color);
    } while (x1 < x2);
}

void image_draw_vline(image_data_t image, int16_t x, int16_t y1, int16_t y2, image_color_t color) {
    if (x < 0 || x >= image.width) {
        return;
    }
    if (y1 > y2) {
        int16_t temp = y1;
        y1 = y2;
        y2 = temp;
    }
    if (y1 > image.width || y2 < 0) {
        return;
    }
    y1 = (int16_t)max(0, y1);
    y2 = (int16_t)min(y2, image.height);

    do {
        set_point(image, x, y1++, color);
    } while (y1 < y2);
}

void image_draw_line(image_data_t image, int16_t x1, int16_t y1, int16_t x2, int16_t y2, image_color_t color) {
    int dx = abs(x2 - x1);
    int dy = abs(y2 - y1);
    int sx = (x1 < x2) ? 1 : -1;
    int sy = (y1 < y2) ? 1 : -1;
    int err = dx - dy;
    int e2;

    while (1) {
        if ((x1 >= image.width && x2 >= image.width) || (y1 >= image.height && y2 >= image.height)) {
            break;
        }
        if ((x1 < 0 && x2 < 0) || (y1 < 0 && y2 < 0)) {
            break;
        }
        image_draw_point(image, x1, y1, color);
        if (x1 == x2 && y1 == y2) {
            break;
        }
        e2 = 2 * err;
        if (e2 > -dy) {
            err -= dy;
            x1 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y1 += sy;
        }
    }
}

void image_draw_rect(image_data_t image, int16_t x, int16_t y, int16_t w, int16_t h, image_color_t color) {
    if (w <= 0 || h <= 0) {
        return;
    }

    w = (int16_t)min(image.width - x, w);
    h = (int16_t)min(image.height - y, h);

    image_draw_hline(image, x, x + w - 1, y, color);
    image_draw_hline(image, x, x + w, y + h - 1, color);

    image_draw_vline(image, x, y, y + h - 1, color);
    image_draw_vline(image, x + w - 1, y, y + h - 1, color);
}

void image_fill_rect(image_data_t image, int16_t x, int16_t y, int16_t w, int16_t h, image_color_t color) {
    if (w <= 0 || h <= 0) {
        return;
    }
    x = (int16_t)max(0, x);
    y = (int16_t)max(0, y);
    w = (int16_t)min(image.width - x, w);
    h = (int16_t)min(image.height - y, h);

    while (h--) {
        image_draw_hline(image, x, x + w, y++, color);
    };
}
