/*
 * Project: UHC
 * Class: gg.uhc.uhc.inventory.IconInventory
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Graham Howden <graham_howden1 at yahoo.co.uk>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package gg.uhc.uhc.inventory;

import gg.uhc.uhc.ShutdownRegistry;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public class IconInventory implements IconUpdateListener, Listener, ShutdownRegistry.ShutdownListener {

    protected static final int INVENTORY_WIDTH = 9;

    protected final List<IconStack> icons = Lists.newArrayList();
    protected final Set<Inventory> openedInventories = Sets.newHashSet();

    protected final String title;

    public IconInventory(String title) {
        this.title = title;
        ShutdownRegistry.addListener(this);
    }

    public void showTo(HumanEntity entity) {
        // how many icons do we want to show
        final int iconCount = icons.size();

        // how many slots are required, in increments of 9, min 9, max 54
        final int slotsRequired = Math.max(
                9,
                Math.min(
                        54,
                        INVENTORY_WIDTH * ((iconCount + INVENTORY_WIDTH - 1) / INVENTORY_WIDTH)
                )
        );

        // Create and render inventory
        final Inventory inventory = Bukkit.createInventory(null, slotsRequired, title);
        final ItemStack[] contents = new ItemStack[slotsRequired];
        for (int i = 0; i < icons.size(); i++) {
            contents[i] = icons.get(i);
        }

        inventory.setContents(contents);

        // Show to player
        entity.openInventory(inventory);
        openedInventories.add(inventory);
    }

    public void registerNewIcon(IconStack icon) {
        icon.registerUpdateHandler(this);
        add(icon);
    }

    protected int add(IconStack icon) {
        // check where to insert and
        // add item to list in the correct location
        final int index = indexToInsert(icon);
        icons.add(index, icon);
        reopenForCurrentViewers();

        return index;
    }

    protected int indexToInsert(IconStack icon) {
        int insertionIndex = Collections.binarySearch(icons, icon);

        // weight wasn't found so it returns (-(insertion index) -1)
        if (insertionIndex < 0) {
            insertionIndex = -(insertionIndex + 1);
        }

        return insertionIndex;
    }

    protected Collection<HumanEntity> getCurrentViewers() {
        final List<HumanEntity> viewers = Lists.newArrayList();
        for (final Inventory openedInventory : openedInventories) {
            viewers.addAll(openedInventory.getViewers());
        }

        return viewers;
    }

    protected void reopenForCurrentViewers() {
        for (final HumanEntity viewer : getCurrentViewers()) {
            viewer.closeInventory();
            showTo(viewer);
        }
    }

    @Override
    public void onUpdate(IconStack icon) {
        final int index = icons.indexOf(icon);

        // this isn't one of our icons
        if (index < 0) return;

        // refresh the item
        for (final Inventory openedInventory : openedInventories) {
            openedInventory.setItem(index, icon);
        }
    }

    @Override
    public void onWeightUpdate(IconStack icon) {
        final int index = icons.indexOf(icon);

        // this isn't one of our icons
        if (index < 0) return;

        // remove from the list
        icons.remove(index);

        reopenForCurrentViewers();

        // readd to the list
        final int newIndex = add(icon);
    }

    @EventHandler(ignoreCancelled = true)
    public void on(InventoryClickEvent event) {
        final Inventory inventory = event.getInventory();
        if (!inventory.getTitle().equals(title)) return;

        event.setCancelled(true);

        final int slot = event.getRawSlot();

        if (slot < 0 || slot >= inventory.getSize() || slot >= icons.size()) return;

        // call event
        final IconStack icon = icons.get(slot);

        if (icon != null) {
            icon.onClick((Player) event.getWhoClicked());
        }
    }

    @EventHandler
    public void on(InventoryDragEvent event) {
        final Inventory inventory = event.getInventory();
        if (!inventory.getTitle().equals(title)) return;

        event.setCancelled(true);
    }

    @EventHandler
    public void on(InventoryCloseEvent event) {
        openedInventories.remove(event.getInventory());
    }

    @Override
    public void onPluginShutdown() {
        for (final HumanEntity viewer : getCurrentViewers()) {
            viewer.closeInventory();
        }

        openedInventories.clear();
    }
}