const io = @import("./io.zig");

const scalars = @import("./scalars.zig");

const slices = @import("./slices.zig");

const std = @import("std");

pub fn Sequential(comptime Value: type) type {
	return struct {
		allocator: std.mem.Allocator,
		values: []Value = &.{},
		cap: usize = 0,

		const Self = @This();

		pub fn clear(self: *Self) void {
			self.values = self.values[0 .. 0];
		}

		pub fn deinit(self: *Self) void {
			if (self.cap == 0) {
				return;
			}

			self.allocator.free(self.values.ptr[0 .. self.cap]);

			self.* = undefined;
		}

		pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
			if (additional == 0) {
				return;
			}

			const grown_capacity = self.cap + additional;
			const buffer = try self.allocator.alloc(Value, grown_capacity);

			errdefer self.allocator.deallocate(buffer);

			if (self.cap != 0) {
				@memcpy(buffer[0 .. self.values.len], self.values);
				self.allocator.free(self.values.ptr[0 .. self.cap]);
			}

			self.values = @as([*]Value, @ptrCast(@alignCast(buffer)))[0 .. self.values.len];
			self.cap = grown_capacity;
		}

		pub fn is_empty(self: Self) bool {
			return self.values.len == 0;
		}

		pub fn get(self: Self) ?Value {
			if (self.get_ptr()) |value| {
				return value.*;
			}

			return null;
		}

		pub fn get_ptr(self: Self) ?*Value {
			if (self.values.len == 0) {
				return null;
			}

			return &self.values[self.values.len - 1];
		}

		pub fn len(self: Self) usize {
			return self.values.len;
		}

		pub fn pop(self: *Self) bool {
			if (self.values.len == 0) {
				return false;
			}

			self.values = self.values[0 .. self.values.len - 1];

			return true;
		}

		pub fn pop_many(self: *Self, n: usize) bool {
			const new_length = scalars.sub(self.values.len, n) orelse {
				return false;
			};

			self.values = self.values[0 .. new_length];

			return true;
		}

		pub fn push(self: *Self, value: Value) bool {
			if (self.values.len == self.cap) {
				return false;
			}

			const offset_index = self.values.len;

			self.values = self.values.ptr[0 .. self.values.len + 1];

			self.values[offset_index] = value;

			return true;
		}

		pub fn push_all(self: *Self, values: []const Value) bool {
			const new_length = self.values.len + values.len;

			if (new_length > self.cap) {
				return false;
			}

			const offset_index = self.values.len;

			self.values = self.values.ptr[0 .. new_length];

			for (0 .. values.len) |index| {
				self.values[offset_index + index] = values[index];
			}

			return true;
		}

		pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
			if (self.values.len == self.cap) {
				try self.grow(@max(1, self.cap));
			}

			const offset_index = self.values.len;

			self.values = self.values.ptr[0 .. self.values.len + 1];

			self.values[offset_index] = value;
		}

		pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void {
			const new_length = self.values.len + n;

			if (new_length > self.cap) {
				return false;
			}

			const offset_index = self.values.len;

			self.values = self.values.ptr[0 .. new_length];

			for (0 .. n) |index| {
				self.values[offset_index + index] = value;
			}

			return true;
		}

		pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
			if (self.cap == size) {
				return;
			}

			const values = try self.allocator.alloc(Value, size);

			for (0 .. @min(values.len, self.values.len)) |i| {
				values[i] = self.values[i];
			}

			if (values.len > self.values.len) {
				for (self.values.len .. values.len) |i| {
					values[i] = default_value;
				}
			}

			self.values = values[0 .. values.len];
			self.cap = values.len;
		}

		pub fn to_allocation(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error![]Value {
			defer {
				self.values = &.{};
				self.cap = 0;
			}

			const allocation = try self.allocator.realloc(self.values.ptr[0 .. self.cap], size);

			for (allocation[@min(self.values.len, size) .. size]) |*value| {
				value.* = default_value;
			}

			return allocation;
		}

		pub const writer = switch (Value) {
			io.Byte => struct {
				fn writer(self: *Self) io.Writer {
					return io.Writer.bind(Self, self, write);
				}

				fn write(self: *Self, buffer: []const io.Byte) io.Error!usize {
					self.push_all(buffer) catch return error.UnavailableResource;

					return buffer.len;
				}
			}.writer,

			else => @compileError("only `Stack(Byte)` has a `reader()` method"),
		};
	};
}

pub fn Parallel(comptime Value: type) type {
	const Slices = slices.Parallel(Value);
	const alignment = @alignOf(Value);

	return struct {
		allocator: std.mem.Allocator,
		values: Slices = .{},
		cap: usize = 0,

		pub const Field = std.meta.FieldEnum(Value);

		const Self = @This();

		pub fn clear(self: *Self) void {
			self.values = self.values.slice_all(0, 0);
		}

		pub fn deinit(self: *Self) void {
			var capacity_slice = self.values;

			capacity_slice.len = self.cap;

			slices.parallel_free(Value, self.allocator, capacity_slice);

			self.* = undefined;
		}

		pub fn get_ptr(self: Self, comptime field: Slices.Field) ?*align (alignment) Slices.Element(field) {
			if (self.len() == 0) {
				return null;
			}

			return &self.slices.field_slice(field)[self.len() - 1];
		}

		pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
			const grown_capacity = self.cap + additional;
			const buffer = try slices.parallel_alloc(Value, self.allocator, grown_capacity);

			if (self.cap != 0) {
				slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
				slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
			}

			self.cap = grown_capacity;
			self.values = buffer.slice_all(0, self.values.len).?;
		}

		pub fn len(self: Self) usize {
			return self.values.len;
		}

		pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
			if (self.len() == self.cap) {
				try self.grow(@max(1, self.cap));
			}

			const tail_index = self.values.len;

			self.values.len += 1;

			std.debug.assert(self.values.set_all(tail_index, value));
		}
	};
}