package cz.jzitnik.game.sprites.ui;

import java.util.Arrays;
import java.util.function.Function;

import org.jline.terminal.Terminal;

import cz.jzitnik.game.sprites.ui.Font.Custom.Width;
import cz.jzitnik.tui.ResourceLoader;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Font {
    private final String[] lines = ResourceLoader.loadResource("ui/font.ans").split("\n");
    private final int HEIGHT = 7;

    @AllArgsConstructor
    @Getter
    public class FontDTO {
        private String data;
        private int width;
        private int height;
    }

    @Getter
    @AllArgsConstructor
    public class CharDTO {
        private String character;
        private int width;
    }

    private String get(int startY, int startX, int height, int width, String color, String background) {
        StringBuilder stringBuilder = new StringBuilder();

        for (int y = startY; y < startY + height; y++) {
            String[] line = lines[y].split("\033");

            for (int x = startX; x < startX + width; x++) {
                stringBuilder.append("\033")
                        .append(line[x + 1].replaceAll("\\[(?!49m)[0-9;]+m", color).replaceAll("\\[49m", background));
            }
            stringBuilder.append("\n");
        }

        return stringBuilder.toString();
    }

    private CharDTO getDigit(char digit, String color, String background) {
        int num = Integer.valueOf(digit);
        int width = 12;
        int startY = HEIGHT;
        int left = 26 * width;
        int startX = left + (num - '1') * width;

        if (num == '0') {
            startX = (26 + 9) * width;
        }

        return new CharDTO(get(startY, startX, HEIGHT, width, color, background), width);
    }

    public CharDTO getChar(char character, String color, String background) {
        if (character == ' ') {
            return new CharDTO((("\033" + background + " ").repeat(12) + "\n").repeat(HEIGHT), 12);
        }

        if (Character.isDigit(character)) {
            return getDigit(character, color, background);
        }

        boolean upper = Character.isUpperCase(character);
        int width = upper ? 14 : 12;
        int startY = (upper ? 0 : 1) * HEIGHT;
        int startX = (upper ? character - 'A' : character - 'a') * width;

        return new CharDTO(get(startY, startX, HEIGHT, width, color, background), width);
    }

    public FontDTO getLine(String text, String color, String background) {
        char[] chars = text.toCharArray();

        String[][] letters = new String[chars.length][];
        int width = 0;

        for (int i = 0; i < chars.length; i++) {
            var charDto = getChar(chars[i], color, background);
            letters[i] = charDto.getCharacter().split("\n");
            width += charDto.getWidth();
        }

        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < HEIGHT; i++) {
            for (int j = 0; j < letters.length; j++) {
                stringBuilder.append(letters[j][i]);
            }

            stringBuilder.append("\n");
        }

        return new FontDTO(stringBuilder.toString(), width, HEIGHT);
    }

    public FontDTO scale(FontDTO font, int times) {
        if (times <= 1) {
            return font;
        }

        StringBuilder stringBuilder = new StringBuilder();
        var line = font.getData();
        var lines = line.split("\n");

        for (int lineNum = 0; lineNum < lines.length; lineNum++) {
            for (int i = 0; i < times; i++) {
                stringBuilder.append(scaleLine(lines[lineNum], times));
                stringBuilder.append("\n");
            }
        }

        return new FontDTO(stringBuilder.toString(), font.getWidth() * times, font.getHeight() * times);
    }

    private StringBuilder scaleLine(String line, int times) {
        StringBuilder result = new StringBuilder();

        for (char c : line.toCharArray()) {
            if (c == ' ') {
                result.append(" ".repeat(times));
            } else {
                result.append(c);
            }
        }

        return result;
    }

    public FontDTO getScaledLine(String text, Size size, int termWidth, String color, String background) {
        return scale(getLine(text, color, background), size.getFunc().apply(termWidth));
    }

    // This will probably need little more work
    public enum Size {
        SMALL((x) -> {
            if (x < 800) {
                return 1;
            }

            return 2;
        }),
        MEDIUM((x) -> {
            if (x < 800) {
                return 2;
            }

            return 3;
        }),
        LARGE((x) -> {
            if (x < 800) {
                return 3;
            }

            return 4;
        });

        @Getter
        private Function<Integer, Integer> func;

        Size(Function<Integer, Integer> func) {
            this.func = func;
        }
    }

    public enum Align {
        LEFT,
        RIGHT,
        CENTER
    }

    public record Background(String bg) {

    }

    public record Color(String color) {

    }

    public class Custom {
        public record Width(int width) {

        }
    }

    private FontDTO applyAlignment(FontDTO data, Align alignment, int termWidth, String background) {
        return switch (alignment) {
            case LEFT -> data;
            case CENTER -> {
                var lines = data.getData().split("\n");
                int nowWidth = data.getWidth();

                StringBuilder buffer = new StringBuilder();

                var leftPad = (termWidth - nowWidth) / 2;
                var rightPad = termWidth - nowWidth - leftPad;

                for (int i = 0; i < lines.length; i++) {
                    buffer.append("\033").append(background);
                    buffer.append(" ".repeat(leftPad)).append(lines[i]);
                    buffer.append("\033").append(background);
                    buffer.append(" ".repeat(rightPad));
                    buffer.append("\033[0m\n");
                }

                yield new FontDTO(buffer.toString(), termWidth, data.getHeight());
            }
            case RIGHT -> {
                var lines = data.getData().split("\n");
                int nowWidth = data.getWidth();

                StringBuilder buffer = new StringBuilder();

                var leftPad = (int) Math.round((termWidth - nowWidth) / 2.0);

                for (int i = 0; i < lines.length; i++) {
                    buffer.append("\033").append(background);
                    buffer.append(" ".repeat(leftPad)).append(lines[i]);
                    buffer.append("\n");
                }

                yield new FontDTO(buffer.toString(), termWidth, data.getHeight());
            }
        };
    }

    public FontDTO line(Terminal terminal, String text, Object... attributes) {
        int termWidth = terminal.getWidth();

        Size fontSize = Size.MEDIUM;
        Align alignment = Align.LEFT;

        String color = "[47m";
        String background = "[49m";
        int width = termWidth;

        for (Object attribute : attributes) {
            if (Size.class.isAssignableFrom(attribute.getClass())) {
                fontSize = (Size) attribute;
                continue;
            }

            if (Align.class.isAssignableFrom(attribute.getClass())) {
                alignment = (Align) attribute;
                continue;
            }

            if (Color.class.isAssignableFrom(attribute.getClass())) {
                color = ((Color) attribute).color();
                continue;
            }

            if (Background.class.isAssignableFrom(attribute.getClass())) {
                background = ((Background) attribute).bg();
                continue;
            }

            if (Custom.Width.class.isAssignableFrom(attribute.getClass())) {
                width = ((Width) attribute).width();
                continue;
            }

            log.error("Invalid attribute class {}. Skipping", attribute.getClass().getSimpleName());
        }

        return applyAlignment(getScaledLine(text, fontSize, termWidth, color, background), alignment, width,
                background);
    }
}
