const std = @import("std");
const r = @import("raylib.zig");

const Funge = @This();

const allocator = std.heap.page_allocator;

pub const Direction = enum(u32) {
    South,
    West,
    East,
    North,
    pub fn spritePosY(self: @This(), offset: f32) f32 {
        return @as(f32, @floatFromInt(@intFromEnum(self))) * offset;
    }
};

pub const Opcode = enum(u32) {
    SpawnSouth,
    SpawnWest,
    SpawnEast,
    SpawnNorth,
    ExitClosed,
    ExitOpen,
    GoSouth,
    GoWest,
    GoEast,
    GoNorth,
    Add,
    Sub,
    Mul,
    Div,
    Mod,
    GreaterThan,
    Not,
    Jump,
    Ok,
    Stop,
    Empty,
    Discard,
    Digit0,
    Digit1,
    Digit2,
    Digit3,
    Digit4,
    Digit5,
    Digit6,
    Digit7,
    Digit8,
    Digit9,
    UTurn,
    Write,
    Dup,
    Swap,
    Horizontal,
    Vertical,
    Random,
    CondStop,
    _,
};

pub const Action = enum {
    Stop,
    Move,
    Teleport,
    End,
};

pub const Result = struct {
    action: Action,
    teleport: r.Vector2,
};

pub const FungeError = error{
    Underflow,
};

pub fn init() Funge {
    return Funge{
        .stack = std.ArrayList(u8).init(allocator),
        .display = std.ArrayList(c_int).init(allocator),
        .direction = .East,
        .isWin = false,
        //.winCondition = undefined,
        .rng = std.rand.DefaultPrng.init(0),
    };
}

stack: std.ArrayList(u8),
display: std.ArrayList(c_int),
direction: Direction,
isWin: bool,
//winCondition: *fn () bool,
rng: std.rand.DefaultPrng,

pub fn deinit(self: *@This()) void {
    self.stack.deinit();
    self.display.deinit();
}

pub fn checkArgs(self: *@This(), num: u32) !void {
    if (self.stack.items.len < num) return FungeError.Underflow;
}

pub fn step(self: *@This(), opcode: Opcode) !Result {
    var res = Result{ .action = .Move, .teleport = .{ .x = 0, .y = 0 } };
    // if (self.winCondition != undefined) {
    //     if (self.winCondition()) self.isWin = true;
    // }
    switch (opcode) {
        .ExitClosed => {
            // if (self.winCondition != undefined) {
            //     if (self.winCondition()) res.action = .End;
            // }
            if (self.isWin) res.action = .End;
        },
        .ExitOpen => res.action = .End,
        .SpawnEast, .GoEast => self.direction = .East,
        .SpawnSouth, .GoSouth => self.direction = .South,
        .SpawnWest, .GoWest => self.direction = .West,
        .SpawnNorth, .GoNorth => self.direction = .North,
        .Add => {
            try self.checkArgs(2);
            const a = self.stack.pop();
            const b = self.stack.pop();
            try self.stack.append(a +% b);
        },
        .Sub => {
            try self.checkArgs(2);
            const a = self.stack.pop();
            const b = self.stack.pop();
            try self.stack.append(b -% a);
        },
        .Mul => {
            try self.checkArgs(2);
            const a = self.stack.pop();
            const b = self.stack.pop();
            try self.stack.append(a *% b);
        },
        .Div => {
            try self.checkArgs(2);
            const a = self.stack.pop();
            const b = self.stack.pop();
            try self.stack.append(try std.math.divTrunc(u8, b, a));
        },
        .Mod => {
            try self.checkArgs(2);
            const a = self.stack.pop();
            const b = self.stack.pop();
            try self.stack.append(try std.math.mod(u8, b, a));
        },
        .GreaterThan => {
            try self.checkArgs(2);
            const a = self.stack.pop();
            const b = self.stack.pop();
            try self.stack.append(if (b > a) 1 else 0);
        },
        .Not => {
            try self.checkArgs(1);
            const a = self.stack.pop();
            try self.stack.append(if (a == 0) 1 else 0);
        },
        .Jump => {
            res.action = .Teleport;
            res.teleport = r.Vector2{
                .x = switch (self.direction) {
                    .East => 2,
                    .North, .South => 0,
                    .West => -2,
                },
                .y = switch (self.direction) {
                    .South => 2,
                    .West, .East => 0,
                    .North => -2,
                },
            };
        },
        .Ok => self.isWin = true,
        .Stop => res.action = .Stop,
        .Empty => try self.stack.resize(0),
        .Discard => {
            try self.checkArgs(1);
            _ = self.stack.pop();
        },
        .Digit0 => try self.stack.append(0),
        .Digit1 => try self.stack.append(1),
        .Digit2 => try self.stack.append(2),
        .Digit3 => try self.stack.append(3),
        .Digit4 => try self.stack.append(4),
        .Digit5 => try self.stack.append(5),
        .Digit6 => try self.stack.append(6),
        .Digit7 => try self.stack.append(7),
        .Digit8 => try self.stack.append(8),
        .Digit9 => try self.stack.append(9),
        .UTurn => self.direction = switch (self.direction) {
            .West => .East,
            .East => .West,
            .North => .South,
            .South => .North,
        },
        .Write => {
            try self.checkArgs(1);
            const c = self.stack.pop();
            //if (c > 0)
            try self.display.append(c);
        },
        .Dup => {
            try self.checkArgs(1);
            const c = self.stack.getLast();
            try self.stack.append(c);
        },
        .Horizontal => {
            try self.checkArgs(1);
            const c = self.stack.pop();
            self.direction = if (c == 0) .East else .West;
        },
        .Vertical => {
            try self.checkArgs(1);
            const c = self.stack.pop();
            self.direction = if (c == 0) .South else .North;
        },
        .Random => self.direction = self.rng.random().enumValue(Direction),
        .CondStop => {
            try self.checkArgs(1);
            const c = self.stack.pop();
            if (c == 0) res.action = .Stop;
        },
        else => {},
    }
    return res;
}
