// Should do a tree like that:
//            AND
//         /      \
//        OR      OR
//       / \      / \
//    name name age age
//    ='A' ='B' >80 <20
//
// For {(name = 'Adrien' OR name = 'Bob') AND (age > 80 OR age < 20)}

const std = @import("std");
const s2t = @import("dtype").s2t;
const ZipponError = @import("error").ZipponError;
const DataType = @import("dtype").DataType;
const DateTime = @import("dtype").DateTime;
const UUID = @import("dtype").UUID;
const Data = @import("ZipponData").Data;

const log = std.log.scoped(.filter);

pub const ComparisonOperator = enum {
    equal,
    different,
    superior,
    superior_or_equal,
    inferior,
    inferior_or_equal,
    in,
    not_in,

    pub fn str(self: ComparisonOperator) []const u8 {
        return switch (self) {
            .equal => "=",
            .different => "!=",
            .superior => ">",
            .superior_or_equal => ">=",
            .inferior => "<",
            .inferior_or_equal => "<=",
            .in => "IN",
            .not_in => "!IN",
        };
    }
};

const LogicalOperator = enum {
    AND,
    OR,

    pub fn str(self: LogicalOperator) []const u8 {
        return switch (self) {
            .AND => "AND",
            .OR => "OR",
        };
    }
};

pub const ConditionValue = union(enum) {
    int: i32,
    float: f64,
    str: []const u8,
    bool_: bool,
    self: UUID,
    unix: u64,
    int_array: []const i32,
    str_array: []const []const u8,
    float_array: []const f64,
    bool_array: []const bool,
    unix_array: []const u64,
    link: *std.AutoHashMap(UUID, void),
    link_array: *std.AutoHashMap(UUID, void),

    pub fn initInt(value: []const u8) ConditionValue {
        return ConditionValue{ .int = s2t.parseInt(value) };
    }

    pub fn initFloat(value: []const u8) ConditionValue {
        return ConditionValue{ .float = s2t.parseFloat(value) };
    }

    pub fn initStr(value: []const u8) ConditionValue {
        return ConditionValue{ .str = value };
    }

    pub fn initSelf(value: UUID) ConditionValue {
        return ConditionValue{ .self = value };
    }

    pub fn initBool(value: []const u8) ConditionValue {
        return ConditionValue{ .bool_ = s2t.parseBool(value) };
    }

    pub fn initDate(value: []const u8) ConditionValue {
        return ConditionValue{ .unix = s2t.parseDate(value).toUnix() };
    }

    pub fn initTime(value: []const u8) ConditionValue {
        return ConditionValue{ .unix = s2t.parseTime(value).toUnix() };
    }

    pub fn initDateTime(value: []const u8) ConditionValue {
        return ConditionValue{ .unix = s2t.parseDatetime(value).toUnix() };
    }

    // Array
    pub fn initArrayInt(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .int_array = s2t.parseArrayInt(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initArrayFloat(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .float_array = s2t.parseArrayFloat(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initArrayStr(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .str_array = s2t.parseArrayStr(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initArrayBool(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .bool_array = s2t.parseArrayBool(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initArrayDate(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .unix_array = s2t.parseArrayDateUnix(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initArrayTime(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .unix_array = s2t.parseArrayTimeUnix(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initArrayDateTime(allocator: std.mem.Allocator, value: []const u8) ZipponError!ConditionValue {
        return ConditionValue{ .unix_array = s2t.parseArrayDatetimeUnix(allocator, value) catch return ZipponError.ParsingValueError };
    }

    pub fn initLink(value: *std.AutoHashMap(UUID, void)) ConditionValue {
        return ConditionValue{ .link = value };
    }

    pub fn initArrayLink(value: *std.AutoHashMap(UUID, void)) ConditionValue {
        return ConditionValue{ .link_array = value };
    }
};

pub const Condition = struct {
    value: ConditionValue = undefined,
    operation: ComparisonOperator = undefined,
    data_type: DataType = undefined,
    data_index: usize = undefined, // Index in the file
};

const FilterNode = union(enum) {
    condition: Condition,
    logical: struct {
        operator: LogicalOperator,
        left: *FilterNode,
        right: *FilterNode,
    },
    empty: bool,
};

pub const Filter = struct {
    allocator: std.mem.Allocator,
    root: *FilterNode,

    pub fn init(allocator: std.mem.Allocator) ZipponError!Filter {
        const node = allocator.create(FilterNode) catch return ZipponError.MemoryError;
        node.* = FilterNode{ .empty = true };
        return .{ .allocator = allocator, .root = node };
    }

    pub fn deinit(self: *Filter) void {
        switch (self.root.*) {
            .logical => self.freeNode(self.root),
            else => {},
        }
        self.allocator.destroy(self.root);
    }

    fn freeNode(self: *Filter, node: *FilterNode) void {
        switch (node.*) {
            .logical => |logical| {
                self.freeNode(logical.left);
                self.freeNode(logical.right);
                self.allocator.destroy(logical.left);
                self.allocator.destroy(logical.right);
            },
            else => {},
        }
    }

    pub fn addCondition(self: *Filter, condition: Condition) ZipponError!void {
        const node = self.allocator.create(FilterNode) catch return ZipponError.MemoryError;
        node.* = FilterNode{ .condition = condition };
        switch (self.root.*) {
            .empty => {
                self.allocator.destroy(self.root);
                self.root = node;
            },
            .logical => {
                var current = self.root;
                var founded = false;
                while (!founded) switch (current.logical.right.*) {
                    .empty => founded = true,
                    .logical => {
                        current = current.logical.right;
                        founded = false;
                    },
                    .condition => unreachable,
                };
                self.allocator.destroy(current.logical.right);
                current.logical.right = node;
            },
            .condition => unreachable,
        }
    }

    pub fn addLogicalOperator(self: *Filter, operator: LogicalOperator) ZipponError!void {
        const empty_node = self.allocator.create(FilterNode) catch return ZipponError.MemoryError;
        empty_node.* = FilterNode{ .empty = true };

        const node = self.allocator.create(FilterNode) catch return ZipponError.MemoryError;
        node.* = FilterNode{ .logical = .{ .operator = operator, .left = self.root, .right = empty_node } };
        self.root = node;
    }

    pub fn addSubFilter(self: *Filter, sub_filter: *Filter) void {
        switch (self.root.*) {
            .empty => {
                self.allocator.destroy(self.root);
                self.root = sub_filter.root;
            },
            .logical => {
                var current = self.root;
                var founded = false;
                while (!founded) switch (current.logical.right.*) {
                    .empty => founded = true,
                    .logical => {
                        current = current.logical.right;
                        founded = false;
                    },
                    .condition => unreachable,
                };
                self.allocator.destroy(current.logical.right);
                current.logical.right = sub_filter.root;
            },
            .condition => unreachable,
        }
    }

    // TODO: Use []Data and make it work
    pub fn evaluate(self: Filter, row: []Data) bool {
        return self.evaluateNode(self.root, row);
    }

    fn evaluateNode(self: Filter, node: *FilterNode, row: []Data) bool {
        return switch (node.*) {
            .condition => |cond| Filter.evaluateCondition(cond, row[cond.data_index]),
            .logical => |logical| switch (logical.operator) {
                .AND => self.evaluateNode(logical.left, row) and self.evaluateNode(logical.right, row),
                .OR => self.evaluateNode(logical.left, row) or self.evaluateNode(logical.right, row),
            },
            .empty => true,
        };
    }

    fn evaluateCondition(condition: Condition, row_value: Data) bool {
        return switch (condition.operation) {
            .equal => switch (condition.data_type) {
                .int => row_value.Int == condition.value.int,
                .float => row_value.Float == condition.value.float,
                .str => std.mem.eql(u8, row_value.Str, condition.value.str),
                .bool => row_value.Bool == condition.value.bool_,
                .date, .time, .datetime => row_value.Unix == condition.value.unix,
                else => unreachable,
            },

            .different => switch (condition.data_type) {
                .int => row_value.Int != condition.value.int,
                .float => row_value.Float != condition.value.float,
                .str => !std.mem.eql(u8, row_value.Str, condition.value.str),
                .bool => row_value.Bool != condition.value.bool_,
                .date, .time, .datetime => row_value.Unix != condition.value.unix,
                else => unreachable,
            },

            .superior_or_equal => switch (condition.data_type) {
                .int => row_value.Int >= condition.value.int,
                .float => row_value.Float >= condition.value.float,
                .date, .time, .datetime => row_value.Unix >= condition.value.unix,
                else => unreachable,
            },

            .superior => switch (condition.data_type) {
                .int => row_value.Int > condition.value.int,
                .float => row_value.Float > condition.value.float,
                .date, .time, .datetime => row_value.Unix > condition.value.unix,
                else => unreachable,
            },

            .inferior_or_equal => switch (condition.data_type) {
                .int => row_value.Int <= condition.value.int,
                .float => row_value.Float <= condition.value.float,
                .date, .time, .datetime => row_value.Unix <= condition.value.unix,
                else => unreachable,
            },

            .inferior => switch (condition.data_type) {
                .int => row_value.Int < condition.value.int,
                .float => row_value.Float < condition.value.float,
                .date, .time, .datetime => row_value.Unix < condition.value.unix,
                else => unreachable,
            },

            .in => switch (condition.data_type) {
                .link => condition.value.link.contains(UUID{ .bytes = row_value.UUID }),
                else => unreachable,
            },

            .not_in => switch (condition.data_type) {
                .link => !condition.value.link.contains(UUID{ .bytes = row_value.UUID }),
                else => unreachable,
            },
        };
    }

    pub fn debugPrint(self: Filter) void {
        self.printNode(self.root.*);
        std.debug.print("\n", .{});
    }

    fn printNode(self: Filter, node: FilterNode) void {
        switch (node) {
            .logical => |logical| {
                std.debug.print(" ( ", .{});
                self.printNode(logical.left.*);
                std.debug.print(" {s} ", .{logical.operator.str()});
                self.printNode(logical.right.*);
                std.debug.print(" ) ", .{});
            },
            .condition => |condition| std.debug.print("{d} {s} {any} |{any}|", .{
                condition.data_index,
                condition.operation.str(),
                condition.value,
                condition.data_type,
            }),
            .empty => std.debug.print("Empty", .{}),
        }
    }
};

test "Evaluate" {
    const allocator = std.testing.allocator;

    var data = [_]Data{
        Data.initInt(1),
        Data.initFloat(3.14159),
        Data.initInt(-5),
        Data.initStr("Hello world"),
        Data.initBool(true),
    };

    var filter = try Filter.init(allocator);
    defer filter.deinit();

    try filter.addCondition(Condition{ .value = ConditionValue.initInt("1"), .data_index = 0, .operation = .equal, .data_type = .int });

    filter.debugPrint();

    _ = filter.evaluate(&data);
}

test "ConditionValue: link" {
    const allocator = std.testing.allocator;

    // Create a hash map for storing UUIDs
    var hash_map = std.AutoHashMap(UUID, void).init(allocator);
    defer hash_map.deinit();

    // Create a UUID to add to the hash map
    const uuid1 = try UUID.parse("123e4567-e89b-12d3-a456-426614174000");
    const uuid2 = try UUID.parse("223e4567-e89b-12d3-a456-426614174000");

    // Add UUIDs to the hash map
    try hash_map.put(uuid1, {});
    try hash_map.put(uuid2, {});

    // Create a ConditionValue with the link
    var value = ConditionValue.initLink(&hash_map);

    // Check that the hash map contains the correct number of UUIDs
    try std.testing.expectEqual(@as(usize, 2), value.link.count());

    // Check that specific UUIDs are in the hash map
    try std.testing.expect(value.link.contains(uuid1));
    try std.testing.expect(value.link.contains(uuid2));
}
