import { Pointer, ptr, read } from "bun:ffi";
import { freeMem } from "./lib";

export type u32 = number;
export type i32 = number;
export type f32 = number;

export type Vector2 = { x: f32, y: f32 };
export type Texture2D = { id: u32, width: i32, height: i32, mimaps: i32, format: i32 };
export type Image2D = { data: Pointer, width: i32, height: i32, mimaps: i32, format: i32 };
export type Texture = Texture2D;
export type Image = Image2D;
export type Rectangle = { x: f32, y: f32, width: f32, height: f32 };
export type Camera2D = { offset: Vector2, target: Vector2, rotation: f32, zoom: f32 };

const tempF32Array = new Float32Array(1);
const PTR_BYTE_SIZE = process.arch === "arm64" || process.arch === "x64" ? 8 : 4;

export function camera2DFromPointer(pointer: Pointer) {
    return {
        offset: { x: read.f32(pointer, 0), y: read.f32(pointer, 4) },
        target: { x: read.f32(pointer, 8), y: read.f32(pointer, 12) },
        rotation: read.f32(pointer, 16),
        zoom: read.f32(pointer, 20)
    };
}

export function camera2DToPointer(camera2D: Camera2D) {
    const buff = new ArrayBuffer(24);
    const f32Array = new Float32Array(buff, 0, 6);
    f32Array[0] = camera2D.offset.x;
    f32Array[1] = camera2D.offset.y;
    f32Array[2] = camera2D.target.x;
    f32Array[3] = camera2D.target.y;
    f32Array[4] = camera2D.rotation;
    f32Array[5] = camera2D.zoom;
    return ptr(buff);
}

export function rectangleToPointer(rect: Rectangle) {
    const buff = new ArrayBuffer(16);
    const f32Array = new Float32Array(buff, 0, 4);
    f32Array[0] = rect.x;
    f32Array[1] = rect.y;
    f32Array[2] = rect.width;
    f32Array[3] = rect.height;
    return ptr(buff);
}

export function rectangleFromPointer(pointer: Pointer): Rectangle {
    return {
        x: read.f32(pointer, 0),
        y: read.f32(pointer, 4),
        width: read.f32(pointer, 8),
        height: read.f32(pointer, 12)
    };
}

export function textureToPointer(texture: Texture2D) {
    const buff = new ArrayBuffer(20);
    new Uint32Array(buff, 0, 1)[0] = texture.id;
    const i32Array = new Int32Array(buff, 4, 4);
    i32Array[0] = texture.width;
    i32Array[1] = texture.height;
    i32Array[2] = texture.mimaps;
    i32Array[3] = texture.format;
    return ptr(buff);
}

export function imageToPointer(image: Image2D) {
    const buff = new ArrayBuffer(20 + PTR_BYTE_SIZE);
    new BigUint64Array(buff, 0, 1)[0] = BigInt(image.data);
    const i32Array = new Int32Array(buff, 8, 4);
    i32Array[0] = image.width;
    i32Array[1] = image.height;
    i32Array[2] = image.mimaps;
    i32Array[3] = image.format;
    return ptr(buff);
}

/**
 * This function does NOT free the memory
 */
export function imageFromPointer(pointer: Pointer): Image2D {
    const i: Image2D = {
        data: read.ptr(pointer, 0),
        width: read.i32(pointer, PTR_BYTE_SIZE),
        height: read.i32(pointer, 12),
        mimaps: read.i32(pointer, 16),
        format: read.i32(pointer, 20)
    };
    //freeMem(pointer);
    return i;
}

/**
 * This function does NOT free the memory
 */
export function textureFromPointer(pointer: Pointer): Texture {
    const t: Texture2D = {
        id: read.u32(pointer, 0),
        width: read.i32(pointer, 4),
        height: read.i32(pointer, 8),
        mimaps: read.i32(pointer, 12),
        format: read.i32(pointer, 16)
    };
    freeMem(pointer);
    return t;
}

export function vec2DToArray(vec: Vector2) {
    return Float32Array.of(vec.x, vec.y);
}

export function roundVector(vec: Vector2): Vector2 {
    return { x: Math.round(vec.x), y: Math.round(vec.y) };
}

export function newVector(val: number) {
    return { x: Math.round(val), y: Math.round(val) };
}

export function copyVector(vec: Vector2): Vector2 {
    return { x: vec.x, y: vec.y };
}

export function clampVector(min: Vector2, max: Vector2, vec: Vector2): Vector2 {
    return { x: clamp(min.x, max.x, vec.x), y: clamp(min.y, max.y, vec.y) };
}

export function clamp(min: number, max: number, num: number) {
    return Math.min(Math.max(num, min), max);
}

export function getColor(r: number, g: number, b: number, a = 255) : u32 {
    return (clamp(0, 255, r) & 0xff) | ((clamp(0, 255, g) & 0xff) << 8) | ((clamp(0, 255, b) & 0xff) << 16) | ((clamp(0, 255, a) & 0xff) << 24);
}

export function toCString(str: string) {
    return new TextEncoder().encode(str + "\0");
}

export function isInteger(num: number) {
    if(num > 2**32-1 || num < -(2**32)/2) return false;
    if(Math.round(num) !== num) return false;
    return true;
}

export function isUnsignedInteger(num: number) {
    if(num < 0 || num > 2**32-1) return false;
    if(Math.round(num) !== num) return false;
    return true;
}

export function isFloat(num: number) {
    tempF32Array[0] = num;
    if(tempF32Array[0] === Infinity || tempF32Array[0] === -Infinity) return false;
    return true;
}

export function throwIfNotI32(variable: number, variableName: string) {
    if(!isInteger(variable)) throw new Error(variableName + " isn't a i32");
}

export function throwIfNotU32(variable: number, variableName: string) {
    if(!isUnsignedInteger(variable)) throw new Error(variableName + " isn't a u32");
}

export function throwIfNotF32(variable: number, variableName: string) {
    if(!isFloat(variable)) throw new Error(variableName + " isn't a f32");
}

export const Colors = {
    GRAY: getColor(130, 130, 130, 255),
    DARK_GRAY: getColor(80, 80, 80, 255),
    YELLOW: getColor(253, 249, 0, 255),
    GOLD: getColor(255, 203, 0, 255),
    ORANGE: getColor(255, 161, 0, 255),
    PINK: getColor(255, 109, 194, 255),
    RED: getColor(230, 41, 55, 255),
    MAROON: getColor(190, 33, 55, 255),
    GREEN: getColor(0, 228, 48, 255),
    LIME: getColor(0, 158, 47, 255),
    DARK_GREEN: getColor(0, 117, 44, 255),
    SKY_BLUE: getColor(102, 191, 255, 255),
    BLUE: getColor(0, 121, 241, 255),
    DARK_BLUE: getColor(0, 82, 172, 255),
    PURPLE: getColor(200, 122, 255, 255),
    VIOLET: getColor(135, 60, 190, 255),
    DARK_PURPLE: getColor(112, 31, 126, 255),
    BEIGE: getColor(211, 176, 131, 255),
    BROWN: getColor(127, 106, 79, 255),
    DARK_BROWN: getColor(76, 63, 47, 255),
    WHITE: getColor(255, 255, 255, 255),
    BLACK: getColor(0, 0, 0, 255),
    BLANK: getColor(0, 0, 0, 0),
    MAGENTA: getColor(255, 0, 255, 255),
    RAY_WHITE: getColor(245, 245, 245, 255),
} as const;

export type Color = keyof typeof Colors | u32;

export const enum MouseButton {
    LEFT,
    RIGHT,
    MIDDLE,
    SIDE,
    EXTRA,
    FORWARD,
    BACK
}

export const enum Keys {
    KEY_NULL = 0,
    KEY_APOSTROPHE = 39,
    KEY_COMMA = 44,
    KEY_MINUS = 45,
    KEY_PERIOD = 46,
    KEY_SLASH = 47,
    KEY_ZERO = 48,
    KEY_ONE = 49,
    KEY_TWO = 50,
    KEY_THREE = 51,
    KEY_FOUR = 52,
    KEY_FIVE = 53,
    KEY_SIX = 54,
    KEY_SEVEN = 55,
    KEY_EIGHT = 56,
    KEY_NINE = 57,
    KEY_SEMICOLON = 59,
    KEY_EQUAL = 61,
    KEY_A = 65,
    KEY_B = 66,
    KEY_C = 67,
    KEY_D = 68,
    KEY_E = 69,
    KEY_F = 70,
    KEY_G = 71,
    KEY_H = 72,
    KEY_I = 73,
    KEY_J = 74,
    KEY_K = 75,
    KEY_L = 76,
    KEY_M = 77,
    KEY_N = 78,
    KEY_O = 79,
    KEY_P = 80,
    KEY_Q = 81,
    KEY_R = 82,
    KEY_S = 83,
    KEY_T = 84,
    KEY_U = 85,
    KEY_V = 86,
    KEY_W = 87,
    KEY_X = 88,
    KEY_Y = 89,
    KEY_Z = 90,
    KEY_LEFT_BRACKET = 91,
    KEY_BACKSLASH = 92,
    KEY_RIGHT_BRACKET = 93,
    KEY_GRAVE = 96,
    KEY_SPACE = 32,
    KEY_ESCAPE = 256,
    KEY_ENTER = 257,
    KEY_TAB = 258,
    KEY_BACKSPACE = 259,
    KEY_INSERT = 260,
    KEY_DELETE = 261,
    KEY_RIGHT = 262,
    KEY_LEFT = 263,
    KEY_DOWN = 264,
    KEY_UP = 265,
    KEY_PAGE_UP = 266,
    KEY_PAGE_DOWN = 267,
    KEY_HOME = 268,
    KEY_END = 269,
    KEY_CAPS_LOCK = 280,
    KEY_SCROLL_LOCK = 281,
    KEY_NUM_LOCK = 282,
    KEY_PRINT_SCREEN = 283,
    KEY_PAUSE = 284,
    KEY_F1 = 290,
    KEY_F2 = 291,
    KEY_F3 = 292,
    KEY_F4 = 293,
    KEY_F5 = 294,
    KEY_F6 = 295,
    KEY_F7 = 296,
    KEY_F8 = 297,
    KEY_F9 = 298,
    KEY_F10 = 299,
    KEY_F11 = 300,
    KEY_F12 = 301,
    KEY_LEFT_SHIFT = 340,
    KEY_LEFT_CONTROL = 341,
    KEY_LEFT_ALT = 342,
    KEY_LEFT_SUPER = 343,
    KEY_RIGHT_SHIFT = 344,
    KEY_RIGHT_CONTROL = 345,
    KEY_RIGHT_ALT = 346,
    KEY_RIGHT_SUPER = 347,
    KEY_KB_MENU = 348,
    KEY_KP_0 = 320,
    KEY_KP_1 = 321,
    KEY_KP_2 = 322,
    KEY_KP_3 = 323,
    KEY_KP_4 = 324,
    KEY_KP_5 = 325,
    KEY_KP_6 = 326,
    KEY_KP_7 = 327,
    KEY_KP_8 = 328,
    KEY_KP_9 = 329,
    KEY_KP_DECIMAL = 330,
    KEY_KP_DIVIDE = 331,
    KEY_KP_MULTIPLY = 332,
    KEY_KP_SUBTRACT = 333,
    KEY_KP_ADD = 334,
    KEY_KP_ENTER = 335,
    KEY_KP_EQUAL = 336,
    KEY_BACK = 4,
    KEY_MENU = 82,
    KEY_VOLUME_UP = 24,
    KEY_VOLUME_DOWN = 25
}