const std = @import("std");
const Property = @import("Property.zig").Property;
const PropertyData = @import("Property.zig").Data;
const Object = @This();

pub const ShapeKind = enum {
    line,
    ellipse,
    broken,
};

const default_common_data = [_]PropertyData{
    .{ .position = .{ .x = 0, .y = 0 } },
    .{ .angle = 0 },
    .{ .scale = .{ .scale_x = 1, .scale_y = 1 } },
    .{ .visible = true },
    .{ .opacity = 1.0 },
    .{ .locked = false },
    .{ .stroke_rgba = 0x000000FF }, // чёрный, полная непрозрачность
    .{ .thickness = 2.0 },
};

pub const defaultCommonProperties: [default_common_data.len]Property = blk: {
    var result: [default_common_data.len]Property = undefined;
    for (default_common_data, &result) |d, *p| {
        p.* = .{ .data = d };
    }
    break :blk result;
};

id: u64,
shape: ShapeKind,
properties: std.ArrayList(Property),
children: std.ArrayList(Object),

pub fn getProperty(self: Object, comptime tag: std.meta.Tag(PropertyData)) ?@FieldType(PropertyData, @tagName(tag)) {
    for (self.properties.items) |*prop| {
        if (std.meta.activeTag(prop.data) == tag) return @field(prop.data, @tagName(tag));
    }
    return null;
}

/// Забирает владение Property
pub fn setProperty(self: *Object, allocator: std.mem.Allocator, prop: Property) !void {
    for (self.properties.items, 0..) |*p, i| {
        if (std.meta.activeTag(p.data) == std.meta.activeTag(prop.data)) {
            if (p.data == .points) allocator.free(p.data.points);
            self.properties.items[i] = prop;
            return;
        }
    }
    return error.PropertyNotFound;
}

pub fn addChild(self: *Object, allocator: std.mem.Allocator, template: Object, next_id: *u64) !void {
    const obj = try template.clone(allocator, next_id);
    try self.children.append(allocator, obj);
}

pub fn clone(self: Object, allocator: std.mem.Allocator, next_id: *u64) !Object {
    var properties_list = std.ArrayList(Property).empty;
    errdefer properties_list.deinit(allocator);
    for (self.properties.items) |prop| {
        try properties_list.append(allocator, try prop.clone(allocator));
    }

    var children_list = std.ArrayList(Object).empty;
    errdefer children_list.deinit(allocator);
    for (self.children.items) |child| {
        try children_list.append(allocator, try child.clone(allocator, next_id));
    }

    return .{
        .id = allocId(next_id),
        .shape = self.shape,
        .properties = properties_list,
        .children = children_list,
    };
}

fn allocId(next_id: *u64) u64 {
    const id = next_id.*;
    next_id.* += 1;
    return id;
}

pub fn deinit(self: *Object, allocator: std.mem.Allocator) void {
    for (self.children.items) |*child| child.deinit(allocator);
    self.children.deinit(allocator);
    for (self.properties.items) |*prop| prop.deinit(allocator);
    self.properties.deinit(allocator);
    self.* = undefined;
}
