//! SQLite Connection Pool
//!
//! Provides a simple thread-safe connection pool for managing
//! multiple database connections.

const std = @import("std");
const Database = @import("database.zig").Database;

/// A simple connection pool for SQLite databases.
///
/// Manages a pool of database connections that can be acquired and released
/// for concurrent access. All connections share the same database file.
///
/// Example:
/// ```zig
/// var pool = try ConnectionPool.init(allocator, "mydb.sqlite", 4);
/// defer pool.deinit();
///
/// var conn = try pool.acquire();
/// defer pool.release(conn);
///
/// try conn.exec("SELECT ...");
/// ```
pub const ConnectionPool = struct {
    allocator: std.mem.Allocator,
    path: []u8,
    connections: []?Database,
    in_use: []bool,
    mutex: std.Thread.Mutex,
    max_size: usize,

    const Self = @This();

    /// Creates a new connection pool.
    ///
    /// Parameters:
    /// - `allocator`: Allocator for pool management
    /// - `path`: Database file path
    /// - `max_size`: Maximum number of connections in the pool
    pub fn init(allocator: std.mem.Allocator, path: [:0]const u8, max_size: usize) !Self {
        const path_copy = try allocator.dupe(u8, path);
        errdefer allocator.free(path_copy);

        const connections = try allocator.alloc(?Database, max_size);
        errdefer allocator.free(connections);
        @memset(connections, null);

        const in_use = try allocator.alloc(bool, max_size);
        errdefer allocator.free(in_use);
        @memset(in_use, false);

        return .{
            .allocator = allocator,
            .path = path_copy,
            .connections = connections,
            .in_use = in_use,
            .mutex = .{},
            .max_size = max_size,
        };
    }

    /// Destroys the connection pool, closing all connections.
    pub fn deinit(self: *Self) void {
        for (self.connections) |*conn_opt| {
            if (conn_opt.*) |*conn| {
                conn.close();
            }
        }
        self.allocator.free(self.connections);
        self.allocator.free(self.in_use);
        self.allocator.free(self.path);
    }

    /// Acquires a connection from the pool.
    ///
    /// Returns an existing idle connection or creates a new one if the pool
    /// isn't full. Returns error if all connections are in use and pool is full.
    pub fn acquire(self: *Self) !*Database {
        self.mutex.lock();
        defer self.mutex.unlock();

        // Look for an existing idle connection
        for (self.connections, 0..) |*conn_opt, i| {
            if (conn_opt.* != null and !self.in_use[i]) {
                self.in_use[i] = true;
                return &conn_opt.*.?;
            }
        }

        // Look for an empty slot to create a new connection
        for (self.connections, 0..) |*conn_opt, i| {
            if (conn_opt.* == null) {
                // Create null-terminated path
                const path_z = self.allocator.dupeZ(u8, self.path) catch return error.OutOfMemory;
                defer self.allocator.free(path_z);

                conn_opt.* = Database.open(path_z) catch |e| {
                    return e;
                };
                self.in_use[i] = true;
                return &conn_opt.*.?;
            }
        }

        return error.Busy; // Pool exhausted
    }

    /// Releases a connection back to the pool.
    pub fn release(self: *Self, conn: *Database) void {
        self.mutex.lock();
        defer self.mutex.unlock();

        for (self.connections, 0..) |*conn_opt, i| {
            if (conn_opt.*) |*stored_conn| {
                if (stored_conn == conn) {
                    self.in_use[i] = false;
                    return;
                }
            }
        }
    }

    /// Returns the number of connections currently in use.
    pub fn inUseCount(self: *Self) usize {
        self.mutex.lock();
        defer self.mutex.unlock();

        var count: usize = 0;
        for (self.in_use) |used| {
            if (used) count += 1;
        }
        return count;
    }

    /// Returns the total number of open connections (idle + in use).
    pub fn openCount(self: *Self) usize {
        self.mutex.lock();
        defer self.mutex.unlock();

        var count: usize = 0;
        for (self.connections) |conn_opt| {
            if (conn_opt != null) count += 1;
        }
        return count;
    }

    /// Returns the maximum pool size.
    pub fn capacity(self: *Self) usize {
        return self.max_size;
    }
};
