package image

import "core:mem"
import "core:bytes"

Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
Destroy_Proc :: #type proc(img: ^Image)

@(private)
_internal_loaders: [Which_File_Type]Loader_Proc
_internal_destroyers: [Which_File_Type]Destroy_Proc

register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
	assert(loader != nil)
	assert(destroyer != nil)
	assert(_internal_loaders[kind] == nil)
	_internal_loaders[kind] = loader

	assert(_internal_destroyers[kind] == nil)
	_internal_destroyers[kind] = destroyer
}

load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
	loader := _internal_loaders[which(data)]
	if loader == nil {
		return nil, .Unsupported_Format
	}
	return loader(data, options, allocator)
}


destroy :: proc(img: ^Image, allocator := context.allocator) {
	if img == nil {
		return
	}
	context.allocator = allocator
	destroyer := _internal_destroyers[img.which]
	if destroyer != nil {
		destroyer(img)
	} else {
		assert(img.metadata == nil)
		bytes.buffer_destroy(&img.pixels)
		free(img)
	}
}

Which_File_Type :: enum {
	Unknown,

	BMP,
	DjVu, // AT&T DjVu file format
	EXR,
	FLIF,
	GIF,
	HDR, // Radiance RGBE HDR
	ICNS, // Apple Icon Image
	JPEG,
	JPEG_2000,
	JPEG_XL,
	NetPBM, // NetPBM family
	PIC, // Softimage PIC
	PNG, // Portable Network Graphics
	PSD, // Photoshop PSD
	QOI, // Quite Okay Image
	SGI_RGB, // Silicon Graphics Image RGB file format
	Sun_Rast, // Sun Raster Graphic
	TGA, // Targa Truevision
	TIFF, // Tagged Image File Format
	WebP,
	XBM, // X BitMap
}

which_bytes :: proc(data: []byte) -> Which_File_Type {
	test_tga :: proc(s: string) -> bool {
		get8 :: #force_inline proc(s: ^string) -> u8 {
			v := s[0]
			s^ = s[1:]
			return v
		}
		get16le :: #force_inline  proc(s: ^string) -> u16 {
			v := u16(s[0]) | u16(s[1])<<16
			s^ = s[2:]
			return v
		}
		s := s
		s = s[1:] // skip offset

		color_type := get8(&s)
		if color_type > 1 {
			return false
		}
		image_type := get8(&s) // image type
		if color_type == 1 { // Colormap (Paletted) Image
			if image_type != 1 && image_type != 9 { // color type requires 1 or 9
				return false
			}
			s = s[4:] // skip index of first colormap
			bpcme := get8(&s) // check bits per colormap entry
			if bpcme != 8 && bpcme != 15 && bpcme != 16 && bpcme != 24 && bpcme != 32 {
				return false
			}
			s = s[4:] // skip image origin (x, y)
		} else { // Normal image without colormap
			if image_type != 2 && image_type != 3 && image_type != 10 && image_type != 11 {
				return false
			}
			s = s[9:] // skip colormap specification
		}
		if get16le(&s) < 1 || get16le(&s) < 1 { // test width and height
			return false
		}
		bpp := get8(&s) // bits per pixel
		if color_type == 1 && bpp != 8 && bpp != 16 {
			return false
		}
		if bpp != 8 && bpp != 15 && bpp != 16 && bpp != 24 && bpp != 32 {
			return false
		}
		return true
	}

	header: [128]byte
	copy(header[:], data)
	s := string(header[:])

	switch {
	case s[:2] == "BM":
		return .BMP
	case s[:8] == "AT&TFORM":
		switch s[12:16] {
		case "DJVU", "DJVM":
			return .DjVu
		}
	case s[:4] == "\x76\x2f\x31\x01":
		return .EXR
	case s[:6] == "GIF87a", s[:6] == "GIF89a":
		return .GIF
	case s[6:10] == "JFIF", s[6:10] == "Exif":
		return .JPEG
	case s[:3] == "\xff\xd8\xff":
		switch s[4] {
		case 0xdb, 0xee, 0xe1, 0xe0:
			return .JPEG
		}
		switch {
		case s[:12] == "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01":
			return .JPEG
		}
	case s[:4] == "\xff\x4f\xff\x51", s[:12] == "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a":
		return .JPEG_2000
	case s[:12] == "\x00\x00\x00\x0c\x4a\x58\x4c\x20\x0d\x0a\x87\x0a":
		return .JPEG_XL
	case s[0] == 'P':
		switch s[2] {
		case '\t', '\n', '\r':
			switch s[1] {
			case '1', '4': // PBM
				return .NetPBM
			case '2', '5': // PGM
				return .NetPBM
			case '3', '6': // PPM
				return .NetPBM
			case '7':      // PAM
				return .NetPBM
			case 'F', 'f': // PFM
				return .NetPBM
			}
		}
	case s[:8] == "\x89PNG\r\n\x1a\n":
		return .PNG
	case s[:4] == "qoif":
		return .QOI
	case s[:2] == "\x01\xda":
		return .SGI_RGB
	case s[:4] == "\x59\xA6\x6A\x95":
		return .Sun_Rast
	case s[:4] == "MM\x2a\x00", s[:4] == "II\x00\x2A":
		return .TIFF
	case s[:4] == "RIFF" && s[8:12] == "WEBP":
		return .WebP
	case s[:8] == "#define ":
		return .XBM

	case s[:11] == "#?RADIANCE\n", s[:7] == "#?RGBE\n":
		return .HDR
	case s[:4] == "\x38\x42\x50\x53":
		return .PSD
	case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT":
		return .PIC
	case s[:4] == "\x69\x63\x6e\x73":
		return .ICNS
	case s[:4] == "\x46\x4c\x49\x46":
		return .FLIF
	case:
		// More complex formats
		if test_tga(s) {
			return .TGA
		}


	}
	return .Unknown
}
