package tc.oc.commons.bukkit.chat;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nullable;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.util.IndexedFunction;
import tc.oc.commons.core.util.Numbers;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

public class Paginator<T> {
    public static final int DEFAULT_PER_PAGE = 14;

    private int perPage = DEFAULT_PER_PAGE;
    private @Nullable BaseComponent title;
    private IndexedFunction<? super T, ? extends BaseComponent> formatter =
        (t, i) -> new Component(String.valueOf(t));

    public Paginator() {
        this(DEFAULT_PER_PAGE);
    }

    public Paginator(int perPage) {
        checkArgument(perPage > 0);
        this.perPage = perPage;
    }

    public Paginator<T> perPage(int perPage) {
        this.perPage = perPage;
        return this;
    }

    public Paginator<T> title(@Nullable BaseComponent title) {
        this.title = title;
        return this;
    }

    public Paginator<T> entries(IndexedFunction<? super T, ? extends BaseComponent> formatter) {
        this.formatter = checkNotNull(formatter);
        return this;
    }

    public void display(CommandSender sender, Collection<? extends T> results, int page) {
        display(BukkitAudiences.getAudience(sender), results, page);
    }

    public void display(Audience audience, Collection<? extends T> results, int page) {
        if(results.isEmpty()) {
            audience.sendMessage(new WarningComponent("command.error.emptyResult"));
            return;
        }

        final int pages = Numbers.divideRoundingUp(results.size(), perPage);

        if(page < 1 || page > pages) {
            audience.sendMessage(new WarningComponent("command.error.invalidPage", String.valueOf(page), String.valueOf(pages)));
            return;
        }

        final int start = perPage * (page - 1);
        final int end = Math.min(perPage * page, results.size());

        audience.sendMessage(header(page, pages));

        if(results instanceof List) {
            List<? extends T> list = (List<? extends T>) results;
            for (int index = start; index < end; index++) {
                audience.sendMessages(multiEntry(list.get(index), index));
            }
        } else {
            final Iterator<? extends T> iterator = results.iterator();
            for(int index = Iterators.advance(iterator, start); index < end; index++) {
                audience.sendMessages(multiEntry(iterator.next(), index));
            }
        }
    }

    public BaseComponent header(int page, int pages) {
        final Component c = new Component(ChatColor.GRAY);
        final BaseComponent title = title();
        if(title != null) {
            c.extra(title, ChatColor.BLUE);
        }
        c.extra(" (")
         .extra(new TranslatableComponent("pageHeader",
                                          new Component(String.valueOf(page), ChatColor.WHITE),
                                          new Component(String.valueOf(pages), ChatColor.WHITE)
         ))
         .extra(")");
        return new HeaderComponent(c);
    }

    protected @Nullable BaseComponent title() {
        return title;
    }

    protected BaseComponent entry(T entry, int index) {
        return formatter.apply(entry, index);
    }

    protected List<? extends BaseComponent> multiEntry(T entry, int index) {
        return ImmutableList.of(entry(entry, index));
    }
}
