package com.ufund.api.ufundapi.persistence;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ufund.api.ufundapi.model.User;

@Component
public class UserFileDAO implements UserDAO {

    private final Map<String, User> users; // cache
    private final ObjectMapper objectMapper;
    private final String filename;

    public UserFileDAO(@Value("${users.file}") String filename, ObjectMapper objectMapper) throws IOException {
        this.filename = filename;
        this.objectMapper = objectMapper;
        users = new TreeMap<>();
        load(); // load the users from the file
    }

    /**
     * Load changes from the json file
     *
     * @throws IOException Any IO issue with the file
     */
    private void load() throws IOException {
        users.clear();

        User[] usersArray = objectMapper.readValue(new File(filename), User[].class);

        for (User user : usersArray) {
            users.put(user.getName(), user);
        }
    }

    /**
     * Saves the needs to json
     *
     * @return True if the save was successful, false otherwise
     * @throws IOException If there was an IO issue saving the file
     */
    private boolean save() throws IOException {
        User[] userArray = getUserArray();

        objectMapper.writeValue(new File(filename), userArray);
        return true;
    }

    /**
     * Return an array of the needs
     *
     * @return An array of all the needs
     */
    private User[] getUserArray() {
        return users.values().toArray(User[]::new);
    }

    @Override
    public User[] getUsers() throws IOException {
        synchronized (users) {
            return getUserArray();
        }
    }

    /**
     * Return the user with the String name name or null otherwise
     * 
     * @param name Name of desired user
     * 
     * @return Desired user, null otherwise
     * @throws IOException If there was an IO issue saving the file
     */
    @Override
    public User getUser(String name) throws IOException {
        synchronized (users) {
            return users.getOrDefault(name, null);
        }
    }

    /**
     * Create a User user
     * 
     * @param user User to create
     * 
     * @return Desired created user
     * @throws IOException If there was an IO issue saving the file
     */
    @Override
    public User createUser(User user) throws IOException {
        synchronized (users) {
            if (getUser(user.getName()) == null) {
                User newUser = new User(user);
                users.put(newUser.getName(), newUser);
                save();
                return newUser;
            } else {
                return null;
            }
        }
    }

    /**
     * Update a user that matches the supplied name
     * 
     * @param name    The name of the user
     * @param newUser New user data
     * 
     * @return Desired user, null otherwise
     * @throws IOException If there was an IO issue saving the file
     */
    @Override
    public User updateUser(User newUser, String name) throws IOException {
        synchronized (users) {
            if (users.containsKey(name)) {
                users.put(name, newUser);
                save();
                return newUser;
            } else {
                return null;
            }
        }
    }

    /**
     * Delete a user matching the name
     * 
     * @param name The name of the user
     * 
     * @return True if deleted, false otherwise
     * @throws IOException If there was an IO issue saving the file
     */
    @Override
    public boolean deleteUser(String name) throws IOException {
        synchronized (users) {
            if (users.containsKey(name)) {
                users.remove(name);
                return save();
            } else {
                return false;
            }
        }
    }

}