const std = @import("std");
const Api = @import("artifacts-api");
const Position = Api.Position;

const Action = @import("./action.zig").Action;
const ActionResult = @import("./action.zig").ActionResult;
const CodeId = Api.CodeId;
const ItemQuantity = Api.ItemQuantity;

const bank_position = Position{ .x = 4, .y = 1 }; // TODO: Figure this out dynamically

const task_master_position = Position{ .x = 1, .y = 2 }; // TODO: Figure this out dynamically

pub const UntilCondition = union(enum) {
    xp: u64,
    item: Api.ItemQuantity,
    quantity: u64,

    fn isComplete(self: UntilCondition, progress: u64) bool {
        return switch (self) {
            .xp => |xp| progress >= xp,
            .item => |item| progress >= item.quantity,
            .quantity => |quantity| progress >= quantity,
        };
    }
};

pub const Task = union(enum) {
    fight: struct {
        at: Position,
        until: UntilCondition,
        progress: u64 = 0,
    },
    gather: struct {
        at: Position,
        until: UntilCondition,
        progress: u64 = 0,
    },
    craft: struct {
        at: Position,
        target: Api.ItemQuantity,
        progress: u64 = 0,
    },
    accept_task: struct {
        done: bool = false
    },

    pub fn isComplete(self: Task) bool {
        return switch (self) {
            .fight => |args| args.until.isComplete(args.progress),
            .gather => |args| args.until.isComplete(args.progress),
            .craft => |args| args.progress >= args.target.quantity,
            .accept_task => |args| args.done
        };
    }

    pub fn onActionCompleted(self: *Task, result: ActionResult) void {
        switch (self.*) {
            .fight => |*args| {
                if (result.get(.fight)) |r| {
                    const fight_result: Api.Server.FightResult = r;

                    switch (args.until) {
                        .xp => {
                            args.progress += fight_result.fight.xp;
                        },
                        .item => {
                            const drops = fight_result.fight.drops;
                            args.progress += drops.getQuantity(args.until.item.id);
                        },
                        .quantity => {
                            args.progress += 1;
                        }
                    }
                }
            },
            .gather => |*args| {
                if (result.get(.gather)) |r| {
                    const gather_result: Api.Server.GatherResult = r;

                    switch (args.until) {
                        .xp => {
                            args.progress += gather_result.details.xp;
                        },
                        .item => {
                            const items = gather_result.details.items;
                            args.progress += items.getQuantity(args.until.item.id);
                        },
                        .quantity => {
                            args.progress += 1;
                        }
                    }
                }
            },
            .craft => |*args| {
                if (result.get(.craft_item)) |r| {
                    const craft_result: Api.Server.CraftResult = r;
                    const items = craft_result.details.items;

                    args.progress += items.getQuantity(args.target.id);
                }
            },
            .accept_task => |*args| {
                if (result.get(.accept_task)) |_| {
                    args.done = true;
                }
            }
        }
    }

    pub fn queueActions(self: Task, api: *Api.Server, name: []const u8, action_queue: *std.ArrayList(Action)) !void {
        const ctx = TaskContext{
            .api = api,
            .name = name,
            .action_queue = action_queue
        };

        switch (self) {
            .fight => |args| {
                try ctx.fightRoutine(args.at);
            },
            .gather => |args| {
                try ctx.gatherRoutine(args.at);
            },
            .craft => |args| {
                try ctx.craftRoutine(args.at, args.target.id, args.target.quantity);
            },
            .accept_task => {
                if (try ctx.moveIfNeeded(task_master_position)) {
                    return;
                }

                try ctx.action_queue.append(.{ .accept_task = {} });
            }
        }
    }
};

const TaskContext = struct {
    api: *Api.Server,
    name: []const u8,
    action_queue: *std.ArrayList(Action),

    fn getCharacter(self: TaskContext) Api.Character {
        return self.api.store.getCharacter(self.name).?;
    }

    fn moveIfNeeded(self: TaskContext, pos: Position) !bool {
        const character = self.getCharacter();

        if (character.position.eql(pos)) {
            return false;
        }

        try self.action_queue.append(.{ .move = pos });

        return true;
    }

    pub fn depositItemsToBank(self: TaskContext) !bool {
        var character = self.getCharacter();
        const action_queue = self.action_queue;

        // Deposit items and gold to bank if full
        if (character.getItemCount() == 0) {
            return false;
        }

        if (!character.position.eql(bank_position)) {
            try action_queue.append(.{ .move = bank_position });
        }

        for (character.inventory.slice()) |slot| {
            try action_queue.append(.{
                .deposit_item = .{ .id = slot.id, .quantity = slot.quantity }
            });
        }

        return true;
    }

    fn depositIfFull(self: TaskContext) !bool {
        const character = self.getCharacter();
        if (character.getItemCount() < character.inventory_max_items) {
            return false;
        }

        _ = try depositItemsToBank(self);

        if (character.gold > 0) {
            try self.action_queue.append(.{ .deposit_gold = @intCast(character.gold) });
        }

        return true;
    }

    fn fightRoutine(self: TaskContext, enemy_position: Position) !void {
        if (try self.depositIfFull()) {
            return;
        }

        if (try self.moveIfNeeded(enemy_position)) {
            return;
        }

        try self.action_queue.append(.{ .fight = {} });
    }

    fn gatherRoutine(self: TaskContext, resource_position: Position) !void {
        if (try self.depositIfFull()) {
            return;
        }

        if (try self.moveIfNeeded(resource_position)) {
            return;
        }

        try self.action_queue.append(.{ .gather = {} });
    }

    fn withdrawFromBank(self: TaskContext, items: []const ItemQuantity) !bool {
        const character = self.getCharacter();

        var has_all_items = true;
        for (items) |item_quantity| {
            const inventory_quantity = character.inventory.getQuantity(item_quantity.id);
            if(inventory_quantity < item_quantity.quantity) {
                has_all_items = false;
                break;
            }
        }
        if (has_all_items) return false;

        if (try self.moveIfNeeded(bank_position)) {
            return true;
        }

        for (items) |item_quantity| {
            const inventory_quantity = character.inventory.getQuantity(item_quantity.id);
            if(inventory_quantity < item_quantity.quantity) {
                try self.action_queue.append(.{ .withdraw_item = .{
                    .id = item_quantity.id,
                    .quantity = item_quantity.quantity - inventory_quantity,
                }});
            }
        }

        return true;
    }

    fn craftItem(self: TaskContext, workstation: Position, id: CodeId, quantity: u64) !bool {
        var character = self.getCharacter();

        const inventory_quantity = character.inventory.getQuantity(id);
        if (inventory_quantity >= quantity) {
            return false;
        }

        if (try self.moveIfNeeded(workstation)) {
            return true;
        }

        try self.action_queue.append(.{ .craft_item = .{
            .id = id,
            .quantity = quantity - inventory_quantity
        }});

        return true;
    }

    fn craftRoutine(self: TaskContext, workstation: Position, id: CodeId, quantity: u64) !void {
        var character = self.getCharacter();
        const inventory_quantity = character.inventory.getQuantity(id);
        if (inventory_quantity >= quantity) {
            if (try self.depositItemsToBank()) {
                return;
            }
        }

        const code = self.api.store.getCode(id) orelse return error.InvalidItemId;
        const target_item = try self.api.getItem(code) orelse return error.ItemNotFound;
        if (target_item.craft == null) {
            return error.NotCraftable;
        }

        const recipe = target_item.craft.?;

        var needed_items = recipe.items;
        for (needed_items.slice()) |*needed_item| {
            needed_item.quantity *= quantity;
        }

        if (try self.withdrawFromBank(needed_items.slice())) {
            return;
        }

        if (try self.craftItem(workstation, id, quantity)) {
            return;
        }
    }
};

