package mindustry.entities.comp;

import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;

/** Component/entity for labels in world space. Useful for servers. Does not save in files - create only on world load. */
@EntityDef(value = {WorldLabelc.class}, serialize = false)
@Component(base = true, genInterface = false)
public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
    @Import int id;
    @Import float x, y;
    @Import boolean added;

    public static final byte
    flagBackground =        1 << 0,
    flagOutline =           1 << 1,
    flagAlignLeft =         1 << 2,
    flagAlignRight =        1 << 3,
    flagAutoscale =         1 << 4;

    public String text = "sample text";
    public float fontSize = 1f, z = Layer.playerName + 1;
    /** Flags are packed into a byte for sync efficiency; see the flag static values. */
    public byte flags = flagBackground | flagOutline;
    /** If not null, this label gets set to the parent position with x, y used as offsets. */
    public @Nullable Posc parent;
    /** Duration in seconds. Ignored if negative */
    public transient float duration = -1;
    public transient @Nullable Runnable expired;

    @Replace
    public float clipSize(){
        if(parent != null) return Float.MAX_VALUE;
        return text == null ? 0f : text.length() * 10f * fontSize;
    }

    @Override
    public void update(){
        if(duration >= 0){
            duration -= Time.delta / 60f;
            if(duration <= 0){
                hide();
                if(expired != null) expired.run();
            }
        }
    }

    @Override
    public void draw(){
        float x = this.x, y = this.y;
        if(parent != null){
            x += parent.x();
            y += parent.y();
        }
        drawAt(text, x, y, z, flags, fontSize, Align.center, (flags & flagAlignLeft) != 0 ? Align.left : (flags & flagAlignRight) != 0 ? Align.right : Align.center);
    }

    public static void drawAt(String text, float x, float y, float layer, int flags, float fontSize, int align, int lineAlign){
        if(text == null) return;
        Draw.z(layer);
        float z = Drawf.text();

        Font font = (flags & flagOutline) != 0 ? Fonts.outline : Fonts.def;
        GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);

        boolean ints = font.usesIntegerPositions();
        font.setUseIntegerPositions(false);
        // Numbers below are obtained by the method of guessing and comparing results to regular labels.
        font.getData().setScale(0.25f * fontSize / Scl.scl(1f) /
            ((flags & flagAutoscale) != 0 ? 0.2f * Vars.renderer.camerascale + 0.05f : 1f));
        layout.setText(font, text);

        int border = (flags & flagBackground) != 0 ? 1 : 0;

        if(Align.isBottom(align)){
            y += layout.height + border * 1.5f;
        }else if(Align.isTop(align)){
            y -= border * 1.5f;
        }else{
            y += layout.height / 2;
        }

        if(Align.isLeft(align)){
            x += layout.width / 2 + border;
        }else if(Align.isRight(align)){
            x -= layout.width / 2 + border;
        }

        if((flags & flagBackground) != 0){
            Draw.color(0f, 0f, 0f, 0.3f);
            Fill.rect(x, y - layout.height / 2, layout.width + 2, layout.height + 3);
            Draw.color();
        }

        float tx = Align.isLeft(lineAlign) ? -layout.width * 0.5f : Align.isRight(lineAlign) ? layout.width * 0.5f : 0;

        font.setColor(Color.white);
        font.draw(text, x + tx, y, 0, lineAlign, false);

        Draw.reset();
        Pools.free(layout);
        font.getData().setScale(1f);
        font.setColor(Color.white);
        font.setUseIntegerPositions(ints);

        Draw.z(z);
    }

    /** Makes this label visible only to the specific player. This must be called instead of add(). */
    public void show(Player player){
        if(added || player.con == null) return;
        player.con.localEntities.add(this);
        added = true;
    }

    /** Hides this player-specific label. If you used {@link #show(Player)} previously, you must call this method instead of {@link #hide()}! */
    public void hide(Player player){
        if(!added || player.con == null) return;
        player.con.localEntities.remove(this);
        Call.removeWorldLabel(player.con, id);
        added = false;
    }

    /** This MUST be called instead of remove()! */
    public void hide(){
        remove();
        Call.removeWorldLabel(id);
    }
}
