/*
 * This file is part of Sponge, licensed under the MIT License (MIT).
 *
 * Copyright (c) SpongePowered <https://www.spongepowered.org>
 * 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 org.spongepowered.common.mixin.core.world.level.block.entity;

import com.mojang.authlib.GameProfile;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.profile.GameProfileManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.common.SpongeCommon;
import org.spongepowered.common.bridge.world.level.block.entity.SkullBlockEntityBridge;
import org.spongepowered.common.profile.SpongeGameProfile;

import java.util.concurrent.CompletableFuture;
import net.minecraft.util.StringUtil;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.SkullBlockEntity;
import net.minecraft.world.level.block.entity.TickableBlockEntity;

@Mixin(SkullBlockEntity.class)
public abstract class SkullBlockEntityMixin extends BlockEntity implements TickableBlockEntity, SkullBlockEntityBridge {

    @Shadow private GameProfile owner;

    private @Nullable CompletableFuture<?> impl$currentProfileFuture;

    public SkullBlockEntityMixin(final BlockEntityType<?> type) {
        super(type);
    }

    private void cancelResolveFuture() {
        if (this.impl$currentProfileFuture != null) {
            this.impl$currentProfileFuture.cancel(true);
            this.impl$currentProfileFuture = null;
        }
    }

    @Override
    public void bridge$setUnresolvedPlayerProfile(final @Nullable GameProfile owner) {
        this.cancelResolveFuture();
        this.owner = owner;
        this.setChanged();
    }

    /**
     * @reason Don't block the main thread while attempting to lookup a game profile.
     */
    @Redirect(method = "updateOwnerProfile()V", at = @At(value = "INVOKE",
            target = "Lnet/minecraft/world/level/block/entity/SkullBlockEntity;updateGameprofile(Lcom/mojang/authlib/GameProfile;)Lcom/mojang/authlib/GameProfile;"))
    private GameProfile onUpdateProfile(final GameProfile input) {
        this.cancelResolveFuture();
        if (input == null) {
            return null;
        }
        if (input.isComplete() && input.getProperties().containsKey("textures")) {
            return input;
        }
        final GameProfileManager manager = Sponge.server().gameProfileManager();
        CompletableFuture<org.spongepowered.api.profile.GameProfile> future = null;
        if (input.getId() != null) {
            future = manager.profile(input.getId());
        } else if (!StringUtil.isNullOrEmpty(input.getName())) {
            future = manager.profile(input.getName());
        }
        if (future == null) {
            return input;
        }
        future.thenAcceptAsync(profile -> {
            this.owner = SpongeGameProfile.toMcProfile(profile);
            this.setChanged();
        }, SpongeCommon.server());
        this.impl$currentProfileFuture = future;
        return input;
    }

    @Override
    public void setRemoved() {
        super.setRemoved();
        this.cancelResolveFuture();
    }
}

@@ -1347,24 +1347,71 @@
 ameProfile;%0A
+import net.minecraft.server.level.ServerLevel;%0A
 import org.c
@@ -1454,24 +1454,24 @@
 l.Nullable;%0A
-
 import org.s
@@ -2467,16 +2467,129 @@
 owner;%0A%0A
+    @Shadow public abstract void shadow$setOwner(@org.jetbrains.annotations.Nullable final GameProfile param0);%0A%0A
     priv
@@ -3119,21 +3119,30 @@
 his.
-owner = 
+shadow$setOwner(
 owner
+)
 ;%0A  
@@ -3148,34 +3148,47 @@
        this.
-setChanged
+impl$markDirtyAndUpdate
 ();%0A    %7D%0A%0A 
@@ -4396,18 +4396,31 @@
 his.
-setChanged
+impl$markDirtyAndUpdate
 ();%0A
@@ -4641,16 +4641,258 @@
 ture();%0A
-
     %7D%0A
+%0A    private void impl$markDirtyAndUpdate() %7B%0A        this.setChanged();%0A        if (this.level != null && !this.level.isClientSide) %7B%0A            ((ServerLevel) this.level).getChunkSource().blockChanged(this.getBlockPos());%0A        %7D%0A    %7D%0A%0A
 %7D%0A

fix skull block entity not updating