package org.allaymc.server.player;

import lombok.experimental.UtilityClass;
import org.allaymc.api.player.Skin;
import org.cloudburstmc.protocol.bedrock.data.skin.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * SkinConvertor is a utility class to convert between the API's Skin object and the protocol's SerializedSkin object.
 */
@UtilityClass
public final class SkinConvertor {
    /**
     * Converts an API Skin object into a protocol SerializedSkin object for network transmission.
     *
     * @param skin the API Skin object to convert
     * @return a new SerializedSkin instance containing the data from the Skin object
     */
    public static SerializedSkin toSerializedSkin(Skin skin) {
        // Convert ImageData for skin and cape
        ImageData serializedSkinData = ImageData.of(
                skin.skinData().width(), skin.skinData().height(), skin.skinData().data().clone());

        ImageData serializedCapeData = ImageData.of(
                skin.capeData().width(), skin.capeData().height(), skin.capeData().data().clone());

        // Convert list of animations
        List<AnimationData> serializedAnimations = skin.animations().stream()
                .map(SkinConvertor::convertAnimationToSerialized)
                .collect(Collectors.toList());

        // Convert list of persona pieces
        List<PersonaPieceData> serializedPersonaPieces = skin.personaPieces().stream()
                .map(piece -> new PersonaPieceData(
                        piece.pieceId(),
                        piece.pieceType(),
                        piece.packId(),
                        piece.isDefault(),
                        piece.productId()
                ))
                .collect(Collectors.toList());

        // Convert list of persona piece tint colors
        List<PersonaPieceTintData> serializedTintColors = skin.pieceTintColors().stream()
                .map(tint -> new PersonaPieceTintData(
                        tint.pieceType(),
                        new ArrayList<>(tint.colors())
                ))
                .collect(Collectors.toList());

        // Use the SerializedSkin builder to construct the final object
        return SerializedSkin.builder()
                .skinId(skin.skinId())
                .playFabId(skin.playFabId())
                .skinResourcePatch(skin.skinResourcePatch())
                .skinData(serializedSkinData)
                .animations(serializedAnimations)
                .capeData(serializedCapeData)
                .geometryData(skin.skinGeometry())
                .geometryDataEngineVersion(skin.geometryDataEngineVersion())
                .animationData(skin.animationData())
                .premium(skin.premiumSkin())
                .persona(skin.personaSkin())
                .capeOnClassic(skin.personaCapeOnClassicSkin())
                .primaryUser(skin.primaryUser())
                .capeId(skin.capeId())
                .fullSkinId(skin.fullId())
                .armSize(skin.armSize())
                .skinColor(skin.skinColor())
                .personaPieces(serializedPersonaPieces)
                .tintColors(serializedTintColors)
                .overridingPlayerAppearance(skin.overrideAppearance())
                .build();
    }

    /**
     * Converts a protocol SerializedSkin object into an API Skin object.
     *
     * @param serializedSkin the protocol SerializedSkin object to convert
     * @return a new API Skin instance containing the data from the SerializedSkin object
     */
    public static Skin fromSerializedSkin(SerializedSkin serializedSkin) {
        // Convert ImageData for skin and cape
        Skin.ImageData skinData = new Skin.ImageData(
                serializedSkin.getSkinData().getWidth(),
                serializedSkin.getSkinData().getHeight(),
                serializedSkin.getSkinData().getImage().clone()
        );

        Skin.ImageData capeData = new Skin.ImageData(
                serializedSkin.getCapeData().getWidth(),
                serializedSkin.getCapeData().getHeight(),
                serializedSkin.getCapeData().getImage().clone()
        );

        // Convert list of animations
        List<Skin.AnimationData> animations = serializedSkin.getAnimations().stream()
                .map(SkinConvertor::convertAnimationFromSerialized)
                .collect(Collectors.toList());

        // Convert list of persona pieces
        List<Skin.PersonaPieces> personaPieces = serializedSkin.getPersonaPieces().stream()
                .map(piece -> new Skin.PersonaPieces(
                        piece.id(),
                        piece.type(),
                        piece.packId(),
                        piece.isDefault(),
                        piece.productId()
                ))
                .collect(Collectors.toList());

        // Convert list of persona piece tint colors
        List<Skin.PersonaPieceTintColor> tintColors = serializedSkin.getTintColors().stream()
                .map(tint -> new Skin.PersonaPieceTintColor(
                        tint.type(),
                        new ArrayList<>(tint.colors())
                ))
                .collect(Collectors.toList());

        // Construct the API Skin record
        return new Skin(
                serializedSkin.getSkinId(),
                serializedSkin.getPlayFabId(),
                serializedSkin.getSkinResourcePatch(),
                skinData,
                animations,
                capeData,
                serializedSkin.getGeometryData(),
                serializedSkin.getAnimationData(),
                serializedSkin.getGeometryDataEngineVersion(),
                serializedSkin.isPremium(),
                serializedSkin.isPersona(),
                serializedSkin.isCapeOnClassic(),
                serializedSkin.isPrimaryUser(),
                serializedSkin.getCapeId(),
                serializedSkin.getFullSkinId(),
                serializedSkin.getSkinColor(),
                serializedSkin.getArmSize(),
                personaPieces,
                tintColors,
                serializedSkin.isOverridingPlayerAppearance()
        );
    }

    private static AnimationData convertAnimationToSerialized(Skin.AnimationData data) {
        return new AnimationData(
                ImageData.of(
                        data.imageData().width(),
                        data.imageData().height(),
                        data.imageData().data().clone()
                ),
                convertAnimationType(data.animationType()),
                data.frameCount(),
                convertExpressionType(data.expressionType())
        );
    }

    private static Skin.AnimationData convertAnimationFromSerialized(AnimationData data) {
        return new Skin.AnimationData(
                new Skin.ImageData(
                        data.image().getWidth(),
                        data.image().getHeight(),
                        data.image().getImage().clone()
                ),
                convertAnimationType(data.textureType()),
                data.frames(),
                convertExpressionType(data.expressionType())
        );
    }

    private static AnimatedTextureType convertAnimationType(Skin.AnimationType type) {
        return switch (type) {
            case FACE -> AnimatedTextureType.FACE;
            case BODY_32X32 -> AnimatedTextureType.BODY_32X32;
            case BODY_128X128 -> AnimatedTextureType.BODY_128X128;
            default -> AnimatedTextureType.NONE;
        };
    }

    private static Skin.AnimationType convertAnimationType(AnimatedTextureType type) {
        return switch (type) {
            case FACE -> Skin.AnimationType.FACE;
            case BODY_32X32 -> Skin.AnimationType.BODY_32X32;
            case BODY_128X128 -> Skin.AnimationType.BODY_128X128;
            default -> Skin.AnimationType.NONE;
        };
    }

    private static AnimationExpressionType convertExpressionType(Skin.ExpressionType type) {
        return switch (type) {
            case LINEAR -> AnimationExpressionType.LINEAR;
            case BLINKING -> AnimationExpressionType.BLINKING;
        };
    }

    private static Skin.ExpressionType convertExpressionType(AnimationExpressionType type) {
        return switch (type) {
            case LINEAR -> Skin.ExpressionType.LINEAR;
            case BLINKING -> Skin.ExpressionType.BLINKING;
        };
    }
}
