package me.km.inventory;

import java.util.Arrays;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;

public class ModInventory extends Inventory {
    private static int modIds = 0;
    private final int modId;
    private final int allSlots;
    private final int rows;
    private final byte[] data;
    private final byte[] smallData;

    public ModInventory(byte[] data, int slots, int allSlots) {
        super(slots);
        this.modId = modIds++;
        this.allSlots = allSlots;
        this.rows = allSlots / 9 + (allSlots % 9 == 0 ? 0 : 1);
        this.data = data;
        this.smallData = deleteZeros(data);
    }

    private static int countZeros(String slots) {
        int i = 0;
        for(char c : slots.toCharArray()) {
            if(c == '0') {
                i++;
            }
        }
        return i;
    }

    private static byte[] deleteZeros(byte[] bytes) {
        int size = 0;
        for(byte b : bytes) {
            size += (b & 3) != 0 ? 1 : 0;
            size += (b & 12) != 0 ? 1 : 0;
            size += (b & 48) != 0 ? 1 : 0;
            size += (b & 192) != 0 ? 1 : 0;
        }
        size = (size >> 2) + ((size & 3) != 0 ? 1 : 0);

        byte[] b = new byte[size];
        byte current;
        int index = 0;
        int pos = 0;
        for(int i = 0; i < bytes.length; i++) {
            for(int j = 0; j < 8; j += 2) {
                current = (byte) ((bytes[i] >> j) & 3);
                if(current != 0) {
                    b[index] |= current << pos;
                    pos += 2;
                    if(pos == 8) {
                        pos = 0;
                        index++;
                    }
                }
            }
        }
        return b;
    }

    public ModInventory(String slots) {
        super(Math.min(54, slots.length() - countZeros(slots)));

        this.modId = modIds++;
        allSlots = Math.min(54, slots.length());
        this.rows = allSlots / 9 + (allSlots % 9 == 0 ? 0 : 1);

        this.data = new byte[14];

        int id;
        for(int i = 0; i < allSlots; i++) {
            id = Character.getNumericValue(slots.charAt(i));
            if(id > 3 || id < 0) {
                id = 0;
            }
            switch(i % 4) {
                case 0:
                    data[i / 4] += id;
                    break;
                case 1:
                    data[i / 4] += id << 2;
                    break;
                case 2:
                    data[i / 4] += id << 4;
                    break;
                case 3:
                    data[i / 4] += id << 6;
                    break;
            }
        }

        this.smallData = deleteZeros(data);
    }

    public ModInventory(int slots) {
        super(Math.max(9, Math.min(54, ((slots / 9) + ((slots % 9) == 0 ? 0 : 1)) * 9)));
        this.modId = modIds++;
        this.allSlots = super.getSizeInventory();
        this.rows = allSlots / 9;
        this.data = new byte[14];
        // default is 3 to every 2 bits
        Arrays.fill(this.data, (byte) 0xFF);
        this.smallData = data;
    }

    public byte[] getData() {
        return data;
    }

    public int getRows() {
        return rows;
    }

    public int getSlotStatus(int slot) {
        if(slot < 0 || slot >= this.getSizeInventory()) {
            return 1;
        }
        int index = Math.max(0, Math.min(smallData.length, slot / 4));
        switch(slot % 4) {
            case 0:
                return smallData[index] & 3;
            case 1:
                return (smallData[index] >> 2) & 3;
            case 2:
                return (smallData[index] >> 4) & 3;
            case 3:
                return (smallData[index] >> 6) & 3;
        }
        return 0;
    }

    public int getSlotStatus(int x, int y) {
        int slot = y * 9 + x;
        int index = Math.max(0, Math.min(13, slot / 4));
        switch(slot % 4) {
            case 0:
                return data[index] & 3;
            case 1:
                return (data[index] >> 2) & 3;
            case 2:
                return (data[index] >> 4) & 3;
            case 3:
                return (data[index] >> 6) & 3;
        }
        return 0;
    }

    public boolean isSlotValid(int x, int y) {
        return getSlotStatus(x, y) != 0;
    }

    public boolean shouldRenderOverlay(int x, int y) {
        int status = getSlotStatus(x, y);
        return status == 0 || status == 2;
    }

    public boolean shouldRenderDarker(int x, int y) {
        return getSlotStatus(x, y) == 3;
    }

    public int getModId() {
        return modId;
    }

    @Override
    public ItemStack addItem(ItemStack stack) {
        stack = stack.copy();
        this.moveToEqualItemStack(stack);
        if(stack.isEmpty()) {
            return ItemStack.EMPTY;
        } else {
            this.moveToEmptySlot(stack);
            return stack.isEmpty() ? ItemStack.EMPTY : stack;
        }
    }

    private void moveToEqualItemStack(ItemStack stack) {
        for(int i = 0; i < getSizeInventory(); i++) {
            // ignore special fields
            if(getSlotStatus(i) != 1) {
                continue;
            }
            ItemStack slotStack = getStackInSlot(i);
            if(ItemStack.areItemsEqual(slotStack, stack)) {
                this.mergeItemStacks(stack, slotStack);
                if(stack.isEmpty()) {
                    return;
                }
            }
        }

    }

    private void mergeItemStacks(ItemStack stack, ItemStack slotStack) {
        int maxSize = Math.min(getInventoryStackLimit(), slotStack.getMaxStackSize());
        int maxMoveable = Math.min(stack.getCount(), maxSize - slotStack.getCount());
        if(maxMoveable > 0) {
            slotStack.grow(maxMoveable);
            stack.shrink(maxMoveable);
            markDirty();
        }
    }

    private void moveToEmptySlot(ItemStack stack) {
        for(int i = 0; i < getSizeInventory(); i++) {
            // ignore special fields
            if(getSlotStatus(i) != 1) {
                continue;
            }
            ItemStack slotStack = getStackInSlot(i);
            if(slotStack.isEmpty()) {
                setInventorySlotContents(i, stack.copy());
                stack.setCount(0);
                return;
            }
        }
    }

    public int getAllSlots() {
        return allSlots;
    }
}
