InMemoryHexaRepo.java

package com.guinetik.hexafun.hexa;

import java.nio.file.DirectoryStream.Filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.guinetik.hexafun.fun.Result;

/**
 * Generic in-memory implementation of the HexaRepo interface.
 * Uses a HashMap to store entities with string IDs.
 * 
 * @param <T> The entity type this repository manages
 */
public class InMemoryHexaRepo<T> implements HexaRepo<T> {

    private final Map<String, T> storage = new HashMap<>();
    private final Function<T, String> idExtractor;
    private final Function<T, T> idGenerator;

    /**
     * Creates a new InMemoryHexaRepo with custom ID extraction and generation.
     * 
     * @param idExtractor Function to extract ID from an entity
     * @param idGenerator Function to generate a new ID for an entity that doesn't have one
     */
    public InMemoryHexaRepo(Function<T, String> idExtractor, Function<T, T> idGenerator) {
        this.idExtractor = idExtractor;
        this.idGenerator = idGenerator;
    }

    /**
     * Creates a new InMemoryHexaRepo with default UUID generation.
     * Note: This constructor assumes the entity has an ID field named "id" 
     * and is accessible via reflection or a setter.
     * 
     * @param idExtractor Function to extract ID from an entity
     */
    public InMemoryHexaRepo(Function<T, String> idExtractor) {
        this.idExtractor = idExtractor;
        this.idGenerator = entity -> {
            // This is a placeholder. In a real implementation, 
            // you would need to set the ID on the entity.
            // This requires knowing the entity structure.
            return entity;
        };
    }
    
    /**
     * Creates a new InMemoryHexaRepo with default behavior.
     * The idExtractor returns the ID from storage based on the entity.
     * The idGenerator simply returns the entity as is.
     */
    public InMemoryHexaRepo() {
        this.idExtractor = entity -> {
            for (Map.Entry<String, T> entry : storage.entrySet()) {
                if (entry.getValue().equals(entity)) {
                    return entry.getKey();
                }
            }
            return String.valueOf(storage.size() + 1);
        };
        this.idGenerator = entity -> entity;
    }

    //-------------------------------------------------------------------------
    // Create operations
    //-------------------------------------------------------------------------

    @Override
    public Result<T> save(T entity) {
        try {
            String id = idExtractor.apply(entity);
            
            // If no ID, generate one
            if (id == null || id.isEmpty()) {
                entity = idGenerator.apply(entity);
                id = idExtractor.apply(entity);
            }
            
            storage.put(id, entity);
            return Result.ok(entity);
        } catch (Exception e) {
            return Result.fail("Failed to save entity: " + e.getMessage());
        }
    }

    @Override
    public Result<List<T>> saveAll(List<T> entities) {
        try {
            List<T> savedEntities = new ArrayList<>();
            for (T entity : entities) {
                Result<T> result = save(entity);
                if (result.isFailure()) {
                    return Result.fail("Failed to save entities: " + result.error());
                }
                savedEntities.add(result.get());
            }
            return Result.ok(savedEntities);
        } catch (Exception e) {
            return Result.fail("Failed to save entities: " + e.getMessage());
        }
    }

    //-------------------------------------------------------------------------
    // Read operations
    //-------------------------------------------------------------------------

    @Override
    public Result<T> findById(String id) {
        if (id == null || id.isEmpty()) {
            return Result.fail("ID cannot be null or empty");
        }
        
        T entity = storage.get(id);
        if (entity == null) {
            return Result.fail("Entity not found with ID: " + id);
        }
        
        return Result.ok(entity);
    }

    @Override
    public Result<List<T>> findAll() {
        return Result.ok(new ArrayList<>(storage.values()));
    }

    @Override
    public Result<List<T>> findBy(Filter<T> filter) {
        try {
            // Since DirectoryStream.Filter only has an accept method,
            // we need to create our own filtering logic
            List<T> result = storage.values().stream()
                .filter(entity -> {
                    try {
                        return filter.accept(entity);
                    } catch (Exception e) {
                        return false;
                    }
                })
                .collect(Collectors.toList());
            
            return Result.ok(result);
        } catch (Exception e) {
            return Result.fail("Failed to filter entities: " + e.getMessage());
        }
    }

    @Override
    public Result<List<T>> findAll(int offset, int limit) {
        if (offset < 0) {
            return Result.fail("Offset cannot be negative");
        }
        if (limit < 0) {
            return Result.fail("Limit cannot be negative");
        }
        
        List<T> allEntities = new ArrayList<>(storage.values());
        int fromIndex = Math.min(offset, allEntities.size());
        int toIndex = Math.min(offset + limit, allEntities.size());
        
        return Result.ok(allEntities.subList(fromIndex, toIndex));
    }

    @Override
    public Result<Long> count() {
        return Result.ok((long) storage.size());
    }

    //-------------------------------------------------------------------------
    // Update operations
    //-------------------------------------------------------------------------

    @Override
    public Result<T> update(String id, T entity) {
        if (id == null || id.isEmpty()) {
            return Result.fail("ID cannot be null or empty");
        }
        
        if (!storage.containsKey(id)) {
            return Result.fail("Cannot update: Entity not found with ID: " + id);
        }
        
        try {
            // Ensure the entity has the correct ID
            String entityId = idExtractor.apply(entity);
            if (entityId == null || !entityId.equals(id)) {
                return Result.fail("Entity ID doesn't match the provided ID");
            }
            
            storage.put(id, entity);
            return Result.ok(entity);
        } catch (Exception e) {
            return Result.fail("Failed to update entity: " + e.getMessage());
        }
    }

    //-------------------------------------------------------------------------
    // Delete operations
    //-------------------------------------------------------------------------

    @Override
    public Result<Boolean> deleteById(String id) {
        if (id == null || id.isEmpty()) {
            return Result.fail("ID cannot be null or empty");
        }
        
        if (!storage.containsKey(id)) {
            return Result.ok(false); // Entity doesn't exist, so technically delete succeeded
        }
        
        storage.remove(id);
        return Result.ok(true);
    }

    @Override
    public Result<Void> deleteAllById(List<String> ids) {
        if (ids == null) {
            return Result.fail("IDs list cannot be null");
        }
        
        for (String id : ids) {
            if (id != null && !id.isEmpty()) {
                storage.remove(id);
            }
        }
        
        return Result.ok(null);
    }

    @Override
    public Result<Void> clear() {
        storage.clear();
        return Result.ok(null);
    }
}