package ru.dbotthepony.kstarbound.defs.dungeon;

import java.util.BitSet;

// TODO: actually make it vectorized, some unsafe API maybe?
// currently it is quite slow, but achieves desired memory efficiency.
public final class VectorizedBitSet {
	public final int bits;
	public final int width;
	public final int height;
	private final BitSet data;

	public VectorizedBitSet(int bits, int width, int height) {
		if (bits <= 0 || bits >= 13)
			throw new IllegalArgumentException("Too many or no bits: " + bits + ". Maximum 12 supported");

		this.bits = bits;
		this.width = width;
		this.height = height;

		this.data = new BitSet(bits * width * height);
	}

	private static int bit(boolean value, int order) {
		return (value ? 1 : 0) << order;
	}

	private void bit(int index, int value, int order) {
		if (((value >>> order) & 1) == 0) {
			data.clear(index);
		} else {
			data.set(index);
		}
	}

	public void set(int x, int y, int value) {
		int bitIndex = (x + y * width) * bits;

		switch (this.bits) {
			case 0:

			case 1:
				bit(bitIndex, value, 0);
				break;

			case 2:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				break;

			case 3:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				break;

			case 4:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				break;

			case 5:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				break;

			case 6:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				break;

			case 7:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				bit(bitIndex + 6, value, 6);
				break;

			case 8:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				bit(bitIndex + 6, value, 6);
				bit(bitIndex + 7, value, 7);
				break;

			case 9:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				bit(bitIndex + 6, value, 6);
				bit(bitIndex + 7, value, 7);
				bit(bitIndex + 8, value, 8);
				break;

			case 10:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				bit(bitIndex + 6, value, 6);
				bit(bitIndex + 7, value, 7);
				bit(bitIndex + 8, value, 8);
				bit(bitIndex + 9, value, 9);
				break;

			case 11:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				bit(bitIndex + 6, value, 6);
				bit(bitIndex + 7, value, 7);
				bit(bitIndex + 8, value, 8);
				bit(bitIndex + 9, value, 9);
				bit(bitIndex + 10, value, 10);
				break;

			case 12:
				bit(bitIndex, value, 0);
				bit(bitIndex + 1, value, 1);
				bit(bitIndex + 2, value, 2);
				bit(bitIndex + 3, value, 3);
				bit(bitIndex + 4, value, 4);
				bit(bitIndex + 5, value, 5);
				bit(bitIndex + 6, value, 6);
				bit(bitIndex + 7, value, 7);
				bit(bitIndex + 8, value, 8);
				bit(bitIndex + 9, value, 9);
				bit(bitIndex + 10, value, 10);
				bit(bitIndex + 11, value, 11);
				break;

		}
	}

	public int get(int x, int y) {
		int bitIndex = (x + y * width) * bits;
		BitSet data = this.data;

		return switch (this.bits) {
			case 0, 1 -> bit(data.get(bitIndex), 0);
			case 2 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1);
			case 3 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2);
			case 4 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3);
			case 5 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4);
			case 6 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5);
			case 7 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5) |
				bit(data.get(bitIndex + 6), 6);
			case 8 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5) |
				bit(data.get(bitIndex + 6), 6) |
				bit(data.get(bitIndex + 7), 7);
			case 9 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5) |
				bit(data.get(bitIndex + 6), 6) |
				bit(data.get(bitIndex + 7), 7) |
				bit(data.get(bitIndex + 8), 8);
			case 10 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5) |
				bit(data.get(bitIndex + 6), 6) |
				bit(data.get(bitIndex + 7), 7) |
				bit(data.get(bitIndex + 8), 8) |
				bit(data.get(bitIndex + 9), 9);
			case 11 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5) |
				bit(data.get(bitIndex + 6), 6) |
				bit(data.get(bitIndex + 7), 7) |
				bit(data.get(bitIndex + 8), 8) |
				bit(data.get(bitIndex + 9), 9) |
				bit(data.get(bitIndex + 10), 10);
			case 12 -> bit(data.get(bitIndex), 0) |
				bit(data.get(bitIndex + 1), 1) |
				bit(data.get(bitIndex + 2), 2) |
				bit(data.get(bitIndex + 3), 3) |
				bit(data.get(bitIndex + 4), 4) |
				bit(data.get(bitIndex + 5), 5) |
				bit(data.get(bitIndex + 6), 6) |
				bit(data.get(bitIndex + 7), 7) |
				bit(data.get(bitIndex + 8), 8) |
				bit(data.get(bitIndex + 9), 9) |
				bit(data.get(bitIndex + 10), 10) |
				bit(data.get(bitIndex + 11), 11);
			default -> 0;
		};
	}
}
