/*
 * This file is part of GriefDefender, licensed under the MIT License (MIT).
 *
 * Copyright (c) bloodmc
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.griefdefender.cache;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.griefdefender.GriefDefenderPlugin;
import com.griefdefender.api.Tristate;
import com.griefdefender.permission.GDPermissionGroup;
import com.griefdefender.permission.GDPermissionHolder;
import com.griefdefender.permission.GDPermissionUser;
import com.griefdefender.util.PermissionUtil;

import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;

import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class PermissionHolderCache {

    private static PermissionHolderCache instance;
    private final Cache<UUID, GDPermissionUser> userCache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES)
            .build();
    private final Cache<String, GDPermissionGroup> groupCache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES)
            .build();
    private final ConcurrentHashMap<GDPermissionHolder, Cache<Integer, Tristate>> permissionCache = new ConcurrentHashMap<>();

    public GDPermissionUser getOrCreateUser(OfflinePlayer user) {
        if (user == null) {
            return null;
        }

        return this.getOrCreateUser(user.getUniqueId());
    }

    public GDPermissionUser getOrCreateUser(UUID uuid) {
        if (uuid == null) {
            return null;
        }
        if (uuid.equals(GriefDefenderPlugin.PUBLIC_UUID)) {
            return GriefDefenderPlugin.PUBLIC_USER;
        }
        if (uuid.equals(GriefDefenderPlugin.WORLD_USER_UUID)) {
            return GriefDefenderPlugin.WORLD_USER;
        }

        GDPermissionUser holder = this.userCache.getIfPresent(uuid);
        if (holder != null) {
            return holder;
        }

        holder = new GDPermissionUser(uuid);
        this.userCache.put(uuid, holder);
        return holder;
    }

    public GDPermissionUser getOrCreateUser(String username) {
        if (username == null || username.length() > 16) {
            return null;
        }

        final UUID uuid = PermissionUtil.getInstance().lookupUserUniqueId(username);
        if (uuid != null) {
            return this.getOrCreateUser(uuid);
        }
        // Avoid checking Bukkit.getOfflinePlayer(name) as it will create a UUID for anything passed

        return null;
    }

    public GDPermissionGroup getOrCreateGroup(String groupName) {
        if (groupName == null) {
            return null;
        }
        GDPermissionGroup holder = this.groupCache.getIfPresent(groupName);
        if (holder != null) {
            return holder;
        }

        holder = new GDPermissionGroup(groupName);
        this.groupCache.put(groupName, holder);
        return holder;
    }

    public GDPermissionHolder getOrCreateHolder(String identifier) {
        if (identifier == null) {
            return null;
        }
        UUID uuid = null;
        try {
            uuid = UUID.fromString(identifier);
        } catch (IllegalArgumentException e) {
            return this.getOrCreateGroup(identifier);
        }

        return this.getOrCreateUser(uuid);
    }

    public Cache<Integer, Tristate> getOrCreatePermissionCache(GDPermissionHolder holder) {
        Cache<Integer, Tristate> cache = this.permissionCache.get(holder);
        if (cache == null) {
            cache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build();
            this.permissionCache.put(holder, cache);
        }
        return cache;
    }

    public void invalidateAllPermissionCache() {
        for (Cache<Integer, Tristate> cache : this.permissionCache.values()) {
            cache.invalidateAll();
        }
    }

    static {
        instance = new PermissionHolderCache();
    }

    public static PermissionHolderCache getInstance() {
        return instance;
    }
}