package org.fountainmc.api.entity.data;

import java.util.UUID;
import javax.annotation.Nonnegative;

import org.fountainmc.api.GameMode;
import org.fountainmc.api.NonnullByDefault;

import static com.google.common.base.Preconditions.*;

/**
 * The data of a player.
 */
@NonnullByDefault
public interface PlayerData extends LivingEntityData {

    /**
     * Get the name of the Player.
     *
     * @return name of the Player
     */
    String getName();

    /**
     * Get the Unique Identifier of the Player.
     *
     * @return UUID of the Player
     */
    UUID getUniqueId();

    /**
     * Get the game-mode the player is (or will be) in
     *
     * @return the game-mode
     */
    GameMode getGameMode();

    void setGameMode(GameMode gameMode);

    boolean canFly();

    boolean setCanFly(boolean canFly);

    @Nonnegative
    float getPercentageToNextExperienceLevel();

    void setPercentageToNextExperienceLevel(@Nonnegative float percentage);

    @Nonnegative
    int getExperienceLevel();

    void setExperienceLevel(@Nonnegative int experienceLevel);

    /**
     * Calculate and return the player's total experience
     * <p>The experience in each level is given in </p>
     *
     * @return the player's total experience
     */
    @Nonnegative
    default long getTotalExperience() {
        int level = getExperienceLevel();
        long total = Math.round(getServer().getExpAtLevel(level--) * getPercentageToNextExperienceLevel());
        for (; level >= 0; level--) {
            // Use addExact in case somehow stupid players ever get more than 2^64 exp
            total = Math.addExact(total, getServer().getExpAtLevel(level));
        }
        return total;
    }

    default void setTotalExperience(long total) {
        checkArgument(total >= 0, "Negative experience %s", total);
        int level = 0;
        while (true) {
            long expAtLevel = getServer().getExpAtLevel(level);
            if (expAtLevel <= total) {
                total -= expAtLevel;
                level++;
            } else {
                setPercentageToNextExperienceLevel((float) ((double) total / (double) expAtLevel));
                break;
            }
        }
        setExperienceLevel(level);
    }

    default void giveExp(@Nonnegative long amount) {
        checkArgument(amount >= 0, "Can't give negative exp %s! Use takeExp(long) if you want that!", amount);
        setTotalExperience(getTotalExperience() + amount);
    }

    default void takeExp(@Nonnegative long amount) {
        checkArgument(amount >= 0, "Can't give negative exp %s! Use takeExp(long) if you want that!", amount);
        long exp = getTotalExperience();
        checkState(exp >= amount, "Can't take away %s exp because the player only has %s exp.", amount, exp);
        setTotalExperience(exp - amount);
    }

    @Override
    default void copyDataFrom(EntityData data) {
        LivingEntityData.super.copyDataFrom(data);
        if (data instanceof PlayerData) {
            this.setGameMode(((PlayerData) data).getGameMode());
            this.setCanFly(((PlayerData) data).canFly());
            this.setPercentageToNextExperienceLevel(((PlayerData) data).getPercentageToNextExperienceLevel());
            this.setExperienceLevel(((PlayerData) data).getExperienceLevel());
        }
    }
}

@@ -1282,24 +1282,16 @@
 ive%0A    
-default 
 long get
@@ -1311,938 +1311,51 @@
 ce()
- %7B%0A        int level = getExperienceLevel();%0A        long total = Math.round(getServer().getExpAtLevel(level--) * getPercentageToNextExperienceLevel());%0A        for (; level %3E= 0; level--) %7B%0A            // Use addExact in case somehow stupid players ever get more than 2%5E64 exp%0A            total = Math.addExact(total, getServer().getExpAtLevel(level));%0A        %7D%0A        return total;%0A    %7D%0A%0A    default void setTotalExperience(long total) %7B%0A        checkArgument(total %3E= 0, %22Negative experience %25s%22, total);%0A        int level = 0;%0A        while (true) %7B%0A            long expAtLevel = getServer().getExpAtLevel(level);%0A            if (expAtLevel %3C= total) %7B%0A                total -= expAtLevel;%0A                level++;%0A            %7D else %7B%0A                setPercentageToNextExperienceLevel((float) ((double) total / (double) expAtLevel));%0A                break;%0A            %7D%0A        %7D%0A        setExperienceLevel(level);%0A    %7D
+;%0A%0A    void setTotalExperience(long total);
 %0A%0A  

Remove default exp implementations