package me.blackness.black.pane;

import java.util.Arrays;
import java.util.Objects;

import org.bukkit.event.inventory.InventoryInteractEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;

import me.blackness.black.Element;
import me.blackness.black.Pane;
import me.blackness.observer.Target;

/*
       .                                                    .
    .$"                                    $o.      $o.  _o"
   .o$$o.    .o$o.    .o$o.    .o$o.   .o$$$$$  .o$$$$$ $$P `4$$$$P'   .o$o.
  .$$| $$$  $$' $$$  $$' $$$  $$' $$$ $$$| $$$ $$$| $$$ ($o $$$: $$$  $$' $$$
  """  """ """  """ """  """ """  """ """  """ """  """  "  """  """ """  """
.oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo.
  ooo_ ooo ooo. ... ooo. ... ooo.  .. `4ooo.  .`4ooo.   ooo.ooo. ooo ooo.  ..
  $$$"$$$$ $$$| ... $$$| ... $$$$$$ ..    "$$o     "$$o $$$|$$$| $$$ $$$|   .
  $$$| $$$ $$$|     $$$|     $$$|     $$$: $$$ $$$: $$$ $$$|$$$| $$$ $$$|
  $$$| $$$ $$$| $o. $$$| $o. $$$| $o. $$$| $$$ $$$| $$$ $$$|$$$| $$$ $$$| $.
  $$$| $$$ $$$| $$$ $$$| $$$ $$$| $$$ $$$| $$$ $$$| $$$ $$$|$$$| $$$ $$$| $o.
  $$$| $$$ $$$| $$$ $$$| $$$ $$$| $$$ $$$| $$$ $$$| $$$ $$$|$$$| $$$ $$$| $$$
  $$$| $$$  $$. $$$  $$. $$$  $$. $$$ $$$| $$$ $$$| $$$ $$$|$$$| $$$  $$. $$$
  $$$: $P'  `4$$$Ü'__`4$$$Ü'  `4$$$Ü' $$$$$P'  $$$$$P'  $$$|$$$: $P' __`4$$$Ü'
 _ _______/∖______/  ∖______/∖______________/|________ "$P' _______/  ∖_____ _
                                                        i"  personinblack
                                                        |
 */

/**
 * a pane which takes some other panes and makes an animation out of them.
 *
 * @author personinblack
 * @see Pane
 * @see BasicPane
 * @since 1.0.3
 */
public final class LivePane implements Pane {
    private final Plugin plugin;
    private final int period;
    private final Pane[] frames;

    /**
     * ctor.
     *
     * @param plugin plugin for being used on registering bukkit tasks
     * @param period delay between every frame
     * @param frames frames to display in order
     */
    public LivePane(final Plugin plugin, final int period, final Pane... frames) {
        this.plugin = Objects.requireNonNull(plugin);
        this.period = Objects.requireNonNull(period);
        this.frames = Objects.requireNonNull(frames.clone());
    }

    /**
     * {@inheritDoc}
     *
     * @see #fill(int, Element)
     */
    @Override
    public void fill(final Element element) {
        for (final Pane frame : frames) {
            frame.fill(element);
        }
    }

    /**
     * fills the specified pane with specified elements.
     *
     * @param frame the frame number of the pane to fill
     * @param element the element to fill the pane with
     * @see Element
     */
    public void fill(final int frame, final Element element) {
        frames[frame].fill(element);
    }

    /**
     * {@inheritDoc}
     *
     * @see #fill(int, Element...)
     */
    @Override
    public void fill(final Element... elements) {
        for (final Pane frame : frames) {
            frame.fill(elements);
        }
    }

    /**
     * fills the specified pane with specified elements. this method will reuse the elements,
     * if amount of elements given is not enough to fill the pane.
     *
     * @param frame the frame number of the pane to fill
     * @param elements the elements to fill the pane with
     * @see Element
     */
    public void fill(final int frame, final Element... elements) {
        frames[frame].fill(elements);
    }

    @Override
    public void clear() {
        for (final Pane frame : frames) {
            frame.clear();
        }
    }

    @Override
    public boolean add(final Element element) {
        for (final Pane frame : frames) {
            if (frame.add(element)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Element[] add(final Element... elements) {
        Element[] leftOvers = elements;
        for (final Pane frame : frames) {
            leftOvers = frame.add(leftOvers);
            if (leftOvers.length == 0) {
                break;
            }
        }

        return leftOvers;
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated because you have to specify which frame
     * @see #insert(int, Element, int, int, boolean)
     */
    @Override @Deprecated
    public void insert(final Element element, final int locX, final int locY,
            final boolean shift) throws IllegalArgumentException {

        // this method is useless because you have to specify the frame to insert an element
    }

    /**
     * inserts an element to the specified slot of the specified frame.
     *
     * @param frame the frame which will get the specified element
     * @param element the element to add
     * @param locX x location of the slot
     * @param locY y location of the slot
     * @param shift either shift the element that already exist at the specified location or
     *     replace it with this one
     * @throws IllegalArgumentException if the specified slot is not in the range of the pane
     * @see Element
     */
    public void insert(final int frame, final Element element, final int locX, final int locY,
            final boolean shift) throws IllegalArgumentException {

        frames[frame].insert(element, locX, locY, shift);
    }

    /**
     * {@inheritDoc}
     *
     * @see #replaceAll(int, Element...)
     */
    @Override
    public void replaceAll(final Element... elements) {
        for (final Pane frame : frames) {
            frame.replaceAll(elements);
        }
    }

    /**
     * replaces all the elements of the said frame with the given ones. this method will reuse
     * the elements, if the amount of elements given is smaller then the amount of existing ones.
     *
     * @param frame the frame number of the pane to replace all
     * @param elements elements to fill the pane with
     * @see Element
     */
    public void replaceAll(final int frame, final Element... elements) {
        frames[frame].replaceAll(elements);
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated because you have to specify which frame
     * @see #remove(int, int, int)
     */
    @Override @Deprecated
    public void remove(final int locX, final int locY) throws IllegalArgumentException {
        // this method is useless because you have to specify the frame to remove an element
    }

    /**
     * removes the element at the specified slot from the specified frame.
     *
     * @param frame the frame which will get the specified slot of it cleared
     * @param locX x location of the slot
     * @param locY y location of the slot
     * @throws IllegalArgumentException if the specified slot is not in the range of the pane
     */
    public void remove(final int frame, final int locX, final int locY)
                throws IllegalArgumentException {

        frames[frame].remove(locX, locY);
    }

    @Override
    public void subscribe(final Target<Object> target) {
        Arrays.stream(frames).forEach(frame -> frame.subscribe(target));
    }

    @Override
    public boolean contains(final ItemStack icon) {
        for (final Pane frame : frames) {
            if (frame.contains(icon)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void accept(final InventoryInteractEvent event) {
        for (final Pane frame : frames) {
            frame.accept(event);
        }
    }

    @Override
    public void displayOn(final Inventory inventory) {
        frames[0].displayOn(inventory);

        new BukkitRunnable() {
            private int iterator;

            @Override
            public void run() {
                if (inventory.getViewers().isEmpty()) {
                    this.cancel();
                } else {
                    nextFrame().displayOn(inventory);
                }
            }

            private Pane nextFrame() {
                iterator = iterator + 1 < frames.length
                    ? iterator + 1
                    : 0;

                return frames[iterator];
            }
        }.runTaskTimer(plugin, 1, period);
    }
}