package com.dsh105.holoapi.api;

import com.dsh105.commodus.IdentUtil;
import com.dsh105.holoapi.HoloAPI;
import com.dsh105.holoapi.config.Settings;
import com.dsh105.holoapi.image.AnimatedImageGenerator;
import com.dsh105.holoapi.image.AnimatedTextGenerator;
import com.dsh105.holoapi.image.Frame;
import com.dsh105.holoapi.util.TagIdGenerator;

import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;

import java.util.ArrayList;
import java.util.Map;

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

public class AnimatedHologramImpl extends HologramImpl implements AnimatedHologram {

    private BukkitTask displayTask;
    private boolean imageGenerated;
    private String animationKey;

    private ArrayList<Frame> frames = new ArrayList<>();
    private int index = 0;
    private Frame currentFrame;

    protected AnimatedHologramImpl(String saveId, String worldName, double x, double y, double z, AnimatedImageGenerator animatedImage) {
        super(TagIdGenerator.next(animatedImage.getLargestFrame().getImageGenerator().getLines().length * animatedImage.getFrames().size()),
                saveId, worldName, x, y, z, animatedImage.getLargestFrame().getImageGenerator().getLines());
        this.frames.addAll(animatedImage.getFrames());
        this.currentFrame = this.getCurrent();
        this.animate();
        this.imageGenerated = true;
        this.animationKey = animatedImage.getKey();
    }

    protected AnimatedHologramImpl(String saveId, String worldName, double x, double y, double z, AnimatedTextGenerator textGenerator) {
        super(TagIdGenerator.next(textGenerator.getLargestFrame().getLines().length * textGenerator.getFrames().size()),
                saveId, worldName, x, y, z, textGenerator.getLargestFrame().getLines());
        this.frames.addAll(textGenerator.getFrames());
        this.currentFrame = this.getCurrent();
        this.animate();
    }

    @Override
    public boolean isImageGenerated() {
        return this.imageGenerated;
    }

    @Override
    public String getAnimationKey() {
        return this.animationKey;
    }

    @Override
    public Frame getCurrent() {
        return this.getFrame(this.index);
    }

    @Override
    public Frame getNext() {
        return this.getFrame((this.index + 1) >= this.frames.size() ? 0 : this.index + 1);
    }

    private Frame next() {
        if (++this.index >= this.frames.size()) {
            this.index = 0;
        }
        return this.getFrame(this.index);
    }

    @Override
    public Frame getFrame(int index) {
        if (index >= this.frames.size()) {
            throw new IndexOutOfBoundsException("Frame " + index + "doesn't exist.");
        } else if(index < 0) {
            throw new IndexOutOfBoundsException("Index cannot be less than 0");
        }
        return this.frames.get(index);
    }

    @Override
    public ArrayList<Frame> getFrames() {
        ArrayList<Frame> list = new ArrayList<>();
        list.addAll(this.frames);
        return list;
    }

    @Override
    public void updateLine(int index, String content) {
        // Nothing can happen here. Lines are always changing
    }

    @Override
    public void animate() {
        if (this.isAnimating()) {
            this.cancelAnimation();
        }
        this.displayTask = new BukkitRunnable() {
            @Override
            public void run() {
                runAnimation();
            }
        }.runTaskTimer(HoloAPI.getCore(), 0L, currentFrame.getDelay());
    }

    private void runAnimation() {
        for (Map.Entry<String, Vector> entry : getPlayerViews().entrySet()) {
            final Player p = IdentUtil.getPlayerOf(entry.getKey());
            if (p != null) {
                currentFrame = next();
                updateAnimation(p, currentFrame.getLines());
            }
        }
    }

    @Override
    public boolean isAnimating() {
        return this.displayTask != null;
    }

    @Override
    public void cancelAnimation() {
        if (this.displayTask != null) {
            this.displayTask.cancel();
            this.displayTask = null;
        }
    }

    @Override
    public void refreshDisplay() {
        this.cancelAnimation();
        this.animate();
    }

    @Override
    public void updateAnimation(Player observer, String... lines) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#updateAnimation(...)");
        for (int index = 0; index < lines.length; index++) {
            this.updateNametag(observer, lines[index], index);
        }
    }

    @Override
    public void show(Player observer) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#show(Player)");
        this.showAnimation(observer, currentFrame.getLines());
    }

    @Override
    public void show(Player observer, Location location) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#show(Player, Observer)");
        checkNotNull(location, "The Location object is null in AnimatedHologramImpl#show(Player, Observer)");
        this.showAnimation(observer, location.toVector(), currentFrame.getLines());
    }

    @Override
    public void show(Player observer, double x, double y, double z) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#show(Player, double, double, double)");
        this.showAnimation(observer, x, y, z, currentFrame.getLines());
    }

    @Override
    public void showAnimation(Player observer, String... lines) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#showAnimation(Player, String...)");
        this.showAnimation(observer, this.getDefaultX(), this.getDefaultY(), this.getDefaultZ(), lines);
    }

    @Override
    public void showAnimation(Player observer, Vector v, String... lines) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#showAnimation(Player, Vector, String...)");
        checkNotNull(v, "The Vector object is null in AnimatedHologramImpl#showAnimation(Player, Vector, String...)");
        this.showAnimation(observer, v.getBlockX(), v.getBlockY(), v.getBlockZ(), lines);
    }

    private void showAnimation(Player observer, double x, double y, double z, String[] lines) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#showAnimation(Player, double, double, double, String[])");
        checkNotNull(lines, "The String[] object is null in AnimatedHologramImpl#showAnimation(Player, double, double, double, String[])");
        for (int index = 0; index < lines.length; index++) {
            this.generate(observer, lines[index], index, -index * Settings.VERTICAL_LINE_SPACING.getValue(), x, y, z);
        }
        this.playerToLocationMap.put(IdentUtil.getIdentificationForAsString(observer), new Vector(x, y, z));
    }

    @Override
    public void move(Player observer, Vector to) {
        checkNotNull(observer, "The Player object is null in AnimatedHologramImpl#move(Player, Vector)");
        checkNotNull(to, "The Vector object is null in AnimatedHologramImpl#move(Player, Vector)");
        this.cancelAnimation();
        super.move(observer, to);
        this.animate();
    }
}
