package com.lunar.sprite;

import com.lunar.tile.Tile;
import com.lunar.utilities.Logger;
import com.lunar.world.dir.Direction;
import com.sun.istack.internal.Nullable;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SpriteManager {

    private final List<SpriteSheet> SPRITE_SHEETS = new ArrayList<>();
    private SpriteSheet current;

    public SpriteManager(String path, String name) {
        BufferedImage image = load(path);
        if (image == null) {
            Logger.logCritical("Attempted to load a null spritesheet.");
            return;
        }
        SpriteSheet sheet = new SpriteSheet(name, image);
        this.current = sheet;
        SPRITE_SHEETS.add(sheet);
    }

    public SpriteManager(BufferedImage sheet, String name) {
        SpriteSheet sh = new SpriteSheet(name, sheet);
        this.current = sh;
        SPRITE_SHEETS.add(sh);
    }

    public SpriteManager(SpriteSheet sheet) {
        this.current = sheet;
        SPRITE_SHEETS.add(sheet);
    }

    public SpriteManager(SpriteSheet[] sheets) {
        List<SpriteSheet> list = Arrays.asList(sheets);
        SPRITE_SHEETS.addAll(list);
    }

    public SpriteManager(List<SpriteSheet> sheets) {
        SPRITE_SHEETS.addAll(sheets);
        sheets.clear();
    }

    /**
     * Set the current spritesheet to use.
     *
     * @param current the spritesheet.
     */
    public void setCurrent(SpriteSheet current) {
        this.current = current;
    }

    /**
     * Get a part of the image.
     *
     * @param image  the image
     * @param x      the X coordinate.
     * @param y      the Y coordinate.
     * @param width  the width
     * @param height the height
     * @return the sub section of the image.
     */
    public static BufferedImage getSectionAt(BufferedImage image, int x, int y, int width, int height) {
        return image.getSubimage(x, y, width, height);
    }

    /**
     * Load a image from the file path.
     *
     * @param path the path to the file.
     * @return the image that was loaded.
     */
    @Nullable
    public static BufferedImage load(String path) {
        try {
            return ImageIO.read(new File(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param name the name of the spritesheet.
     * @return the spritesheet with the corresponding name.
     */
    @Nullable
    public SpriteSheet getSheet(String name) {
        return SPRITE_SHEETS.stream().filter(sheet -> sheet.getName().equals(name)).findAny().orElse(null);
    }

    /**
     * Get a part of the image.
     *
     * @param x      the X coordinate.
     * @param y      the Y coordinate.
     * @param width  the width
     * @param height the height
     * @return the sub section of the image.
     */
    @Nullable
    public BufferedImage getSectionAt(int x, int y, int width, int height) {
        if (current == null) {
            Logger.logWarning("Attempted to get a section from a null sheet.");
            return null;
        }
        return current.getTexture().getSubimage(x, y, width, height);
    }


    /**
     * Get a part of the image.
     *
     * @param name   the name of the spritesheet.
     * @param x      the X coordinate.
     * @param y      the Y coordinate.
     * @param width  the width
     * @param height the height
     * @return the sub section of the image.
     */
    public BufferedImage getSectionAt(String name, int x, int y, int width, int height) {
        BufferedImage i = getSheet(name).getTexture();
        return i.getSubimage(x, y, width, height);
    }

    /**
     * Get multiple images from an image.
     *
     * @param name        the name of the spritesheet.
     * @param x           the X coordinate.
     * @param y           the Y coordinate.
     * @param width       the width
     * @param height      the height
     * @param direction   the direction
     * @param spriteCount the amount of sprites to get.
     * @param xOffset     the offset to add/subtract since spacing between sprites can vary.
     * @param yOffset     the offset to add/subtract since spacing between sprites can vary.
     * @return an array of sprites.
     */
    public BufferedImage[] getMultipleSprites(String name, int x, int y, int width, int height, Direction direction,
                                              int spriteCount, int xOffset, int yOffset) {

        BufferedImage image = getSheet(name).getTexture();
        BufferedImage[] frames = new BufferedImage[spriteCount];

        int newWidth, newHeight;
        newWidth = width + xOffset;
        newHeight = height + yOffset;

        for (int frameCount = 0; frameCount < spriteCount; frameCount++) {
            frames[frameCount] = getSectionAt(image, x, y, width, height);
            x = direction == Direction.RIGHT ? x + newWidth : direction == Direction.LEFT ? x - newWidth : x;
            y = direction == Direction.DOWN ? y + newHeight : direction == Direction.UP ? y - newHeight : y;
        }

        return frames;
    }


    /**
     * Get multiple images from an image.
     *
     * @param x           the X coordinate.
     * @param y           the Y coordinate.
     * @param width       the width
     * @param height      the height
     * @param direction   the direction
     * @param spriteCount the amount of sprites to get.
     * @param xOffset     the offset to add/subtract since spacing between sprites can vary.
     * @param yOffset     the offset to add/subtract since spacing between sprites can vary.
     * @return an array of sprites.
     */
    public BufferedImage[] getMultipleSprites(int x, int y, int width, int height, Direction direction,
                                              int spriteCount, int xOffset, int yOffset) {
        BufferedImage[] frames = new BufferedImage[spriteCount];

        int newWidth, newHeight;
        newWidth = width + xOffset;
        newHeight = height + yOffset;

        for (int frameCount = 0; frameCount < spriteCount; frameCount++) {
            frames[frameCount] = getSectionAt(x, y, width, height);
            x = direction == Direction.RIGHT ? x + newWidth : direction == Direction.LEFT ? x - newWidth : x;
            y = direction == Direction.DOWN ? y + newHeight : direction == Direction.UP ? y - newHeight : y;
        }
        return frames;
    }

    /**
     * Get multiple tiles from an image.
     *
     * @param x             the X coordinate.
     * @param y             the Y coordinate.
     * @param width         the width
     * @param height        the height
     * @param direction     the direction
     * @param spriteCount   the amount of sprites to get.
     * @param areTilesSolid if the tiles are solid.
     * @param xOffset       the offset to add/subtract since spacing between sprites can vary.
     * @param yOffset       the offset to add/subtract since spacing between sprites can vary..
     * @param tileIds       the array of Ids to use, this can be null.
     * @return a list of tiles created from the textures.
     */
    public List<Tile> getTilesFromSprites(int x, int y, int width, int height, Direction direction, int spriteCount, boolean
            areTilesSolid, int xOffset, int yOffset, int[] tileIds) {
        // get the sprites.
        BufferedImage[] images = getMultipleSprites(x, y, width, height, direction, spriteCount, xOffset, yOffset);
        if (tileIds == null) {
            tileIds = new int[images.length];
        }
        List<Tile> tiles = new ArrayList<>();

        for (int i = 0; i < images.length; i++) {
            // loop through all the textures and create them.
            BufferedImage texture = images[i];
            Tile tile = new Tile(texture, tileIds[i], new Tile.TileProperties(true, areTilesSolid));
            tiles.add(tile);
        }
        return tiles;
    }

    /**
     * Get multiple tiles from an image.
     *
     * @param name          the name of the spritesheet.
     * @param x             the X coordinate.
     * @param y             the Y coordinate.
     * @param width         the width
     * @param height        the height
     * @param direction     the direction
     * @param spriteCount   the amount of sprites to get.
     * @param areTilesSolid if the tiles are solid.
     * @param xOffset       the offset to add/subtract since spacing between sprites can vary.
     * @param yOffset       the offset to add/subtract since spacing between sprites can vary.
     * @param tileIds       the array of Ids to use, this can be null.
     * @return a list of tiles created from the textures.
     */
    public List<Tile> getTilesFromSprites(String name, int x, int y, int width, int height, Direction direction, int spriteCount, boolean
            areTilesSolid, int xOffset, int yOffset, int[] tileIds) {
        // get the sprites.
        BufferedImage[] images = getMultipleSprites(name, x, y, width, height, direction, spriteCount, xOffset, yOffset);
        if (tileIds == null) {
            tileIds = new int[images.length];
        }
        List<Tile> tiles = new ArrayList<>();

        for (int i = 0; i < images.length; i++) {
            // loop through all the textures and create them.
            BufferedImage texture = images[i];
            Tile tile = new Tile(texture, tileIds[i], new Tile.TileProperties(true, areTilesSolid));
            tiles.add(tile);
        }
        return tiles;
    }

    /**
     * Get multiple tiles from an image.
     *
     * @param name          the name of the spritesheet.
     * @param x             the X coordinate.
     * @param y             the Y coordinate.
     * @param width         the width
     * @param height        the height
     * @param direction     the direction
     * @param spriteCount   the amount of sprites to get.
     * @param areTilesSolid if the tiles are solid.
     * @param xOffset       the offset to add/subtract since spacing between sprites can vary.
     * @param yOffset       the offset to add/subtract since spacing between sprites can vary.
     * @return a list of tiles created from the textures.
     */
    public List<Tile> getTilesFromSprites(String name, int x, int y, int width, int height, Direction direction, int spriteCount, boolean
            areTilesSolid, int xOffset, int yOffset) {
        return getTilesFromSprites(name, x, y, width, height, direction, spriteCount, areTilesSolid, xOffset, yOffset, null);
    }

}