package main

import "core:encoding/cbor"
import "core:fmt"
import "core:image/png"
import "core:math/rand"
import "core:os"
import "core:strings"
import "core:time"
import rl "vendor:raylib"

MAKE_CONVERTER :: #config(MAKE_CONVERTER, true)

Pixel :: struct {
    id:            int,
    pos:           rl.Vector2,
    already_moved: bool,
    color:         rl.Color,
}

State :: struct {
    seed:   u64,
    colors: map[int]rl.Color,
}
state: State

SIZE :: 800
SCALE :: 20
DIVIDED :: SIZE / SCALE
pixels: [dynamic]Pixel

get_pixel :: proc(pos: rl.Vector2) -> Maybe(^Pixel) {
    for &pixel in pixels {
        if pixel.pos == pos {
            return &pixel
        }
    }

    return nil
}

is_finished :: proc() -> bool {
    return len(pixels) >= DIVIDED * DIVIDED
}

main :: proc() {
    rl.SetTraceLogLevel(.ERROR)

    when MAKE_CONVERTER {
        if len(os.args) < 3 {
            fmt.eprintln("Please provide input and output filenames!")
            return
        }

        state.seed = u64(time.time_to_unix(time.now()))
        rand.reset(state.seed)
        rl.SetConfigFlags({.WINDOW_HIDDEN})
        fmt.println("Beginning conversion...")
    } else {
        if len(os.args) < 2 {
            fmt.eprintln("Please provide a sand image!")
            return
        }

        bytes, err := os.read_entire_file_or_err(os.args[1])
        if err != nil {
            fmt.eprintln("Failed to read sand image:", err)
            return
        }
        unmarshal_err := cbor.unmarshal(string(bytes), &state)
        if unmarshal_err != nil {
            fmt.eprintln("Failed to unmarshal sand image:", unmarshal_err)
        }
        rand.reset(state.seed)
    }

    rl.InitWindow(SIZE, SIZE, strings.clone_to_cstring(os.args[1]))
    when MAKE_CONVERTER {
        img := rl.LoadImage(strings.clone_to_cstring(os.args[1]))
        rl.ImageResize(&img, DIVIDED, DIVIDED)
    } else {
        rl.SetTargetFPS(120)
    }

    for !rl.WindowShouldClose() {
        for &pixel in pixels {
            pixel.already_moved = false
        }

        if !is_finished() {
            pos := rl.Vector2{f32(rand.int_max(DIVIDED)), 0}
            if _, found := get_pixel(pos).?; !found {
                color: rl.Color
                id := len(pixels)
                when !MAKE_CONVERTER {
                    color = state.colors[id]
                }
                append(&pixels, Pixel{id = id, pos = pos, color = color})
            }
        }

        when !MAKE_CONVERTER {
            rl.BeginDrawing()
            rl.ClearBackground(rl.BLACK)
        }
        for &pixel in pixels {
            when !MAKE_CONVERTER {
                rl.DrawRectangleV(pixel.pos * SCALE, {SCALE, SCALE}, pixel.color)
            }

            if pixel.already_moved do continue
            if pixel.pos.y >= DIVIDED - 1 do continue

            down := pixel.pos + {0, 1}
            left := pixel.pos + {-1, 1}
            right := pixel.pos + {1, 1}
            if _, found := get_pixel(down).?; !found {
                pixel.pos = down
                pixel.already_moved = true
            } else if _, found := get_pixel(left).?; !found {
                if pixel.pos.x > 0 {
                    pixel.pos = left
                    pixel.already_moved = true
                }
            }

            if _, found := get_pixel(right).?; !found && !pixel.already_moved {
                pixel.already_moved = true
                if pixel.pos.x >= DIVIDED - 1 do continue
                pixel.pos = right
            }

            pixel.already_moved = true
        }
        when !MAKE_CONVERTER {
            rl.EndDrawing()
        }

        when MAKE_CONVERTER {
            if is_finished() {
                for x in 0 ..< DIVIDED {
                    for y in 0 ..< DIVIDED {
                        pixel := get_pixel(rl.Vector2{f32(x), f32(y)}).?
                        color := rl.GetImageColor(img, i32(x), i32(y))
                        pixel.color = color
                        state.colors[pixel.id] = color
                    }
                }

                bytes, err := cbor.marshal(state)
                if err != nil {
                    fmt.eprintln("Failed to marshal sand image:", err)
                    return
                }
                write_err := os.write_entire_file_or_err(os.args[2], bytes)
                if write_err != nil {
                    fmt.eprintln("Failed to write sand image:", write_err)
                    return
                }

                fmt.println("Conversion complete")
                rl.CloseWindow()
            }
        }
    }
}