#pragma once
#include <vector>
#include <array>
#include "fileUtils.hpp"
#include "block.hpp"
#include "blockRegistry.hpp"
#include "blockPos.hpp"

using std::vector;

class World {
public:
    /**
     * Create a World object using the blocks defined in BlockRegistry.
     * 
     * @param worldBlockRegistry The BlockRegistry to use.
     */
    World(BlockRegistry worldBlockRegistry) {
        this->blockRegistry = worldBlockRegistry;
    }
    
    /**
     * Load the world from the given text file.
     * - The character '|' is the player's starting position.
     * - The characters in the file are mapped to the corresponding blocks in the block registry.
     * - All other characters are kept as purely visual decoration blocks.
     * 
     * @param fileLocation The location of the file to load.
     */
    void loadFromFile(string fileLocation) {
        field = {};
        vector<string> file = readFileAsVector(fileLocation);
        
        for (unsigned int y = 0; y < file.size(); y++) {
            for (unsigned int x = 0; x < file.at(y).size(); x++) {
                setBlockAt(BlockPos(x, y), blockRegistry.getByEncoding(file.at(y).at(x)));
                if (file.at(y).at(x) == 'S') startPos = BlockPos(x+3, y);
                if (x > maxX) maxX = x;
            }
            if (y > maxY) maxY = y;
        }
    }
    /**
     * Sets the block at the given position in the world.
     * 
     * In case the position is outside the current bounds of the world, the world will be automatically be expanded.
     * If the position is negative, an error will be logged.
     * 
     * @param pos The position to set the block at.
     * @param block The block to set at that position.
     */
    void setBlockAt(BlockPos pos, Block block) {
        if (pos.isNegative()) cout << "Tried to set block at negative position: (x: " << pos.getX() << ", y:" << pos.getY() << ")" << endl;
        while (field.size() <= pos.getUnsignedY()) field.push_back({});
        while (field[pos.getUnsignedY()].size() <= pos.getUnsignedX()) field[pos.getUnsignedY()].push_back(blockRegistry.AIR);

        field[pos.getUnsignedY()][pos.getUnsignedX()] = block;
        if (block.getSettings().hasGravity() && containsPos(pos.add(0, 1)) && getBlockAt(pos.add(0, 1)) == blockRegistry.AIR) {
            setBlockAt(pos.add(0, 1), block);
            setBlockAt(pos, blockRegistry.AIR);
        }
    }

    /**
     * Get the block at the given position in the world.
     * 
     * In position is outside the current bounds of the world, the AIR block will be returned.
     * 
     * @param pos The position to get the block at.
     * @return The block at that position.
     */
    Block& getBlockAt(BlockPos pos) {
        if (pos.getUnsignedY() < field.size() && pos.getUnsignedX() < field[pos.getUnsignedY()].size()) {
            return field[pos.getUnsignedY()][pos.getUnsignedX()];
        }
        //cout << "Out of bounds: " << pos.getX() << ", " << pos.getY() << endl;
        return blockRegistry.AIR;
    }

    /**
     * Checks whether the given position is within the bounds of the world, or not.
     * 
     * @param pos The position to check.
     * @return True if the position is non-negative and within the current bounds of the world, false otherwise.
     */
    bool containsPos(BlockPos pos) {
        return !pos.isNegative() && pos.getUnsignedY() < field.size();
    }
    
    /**
     * @return The current state of the world as a 2D vector of blocks.
     */
    vector<vector<Block>> getFieldState() {
        return field;
    }

    /**
     * Get the block registry for the world.
     * 
     * @return The block registry containing all registered blocks.
     */
    BlockRegistry getBlockRegistry() {
        return blockRegistry;
    }
    
    /**
     * Get the biggest X (horizontal) value in the world.
     * 
     * @return The maximum X value.
     */
    unsigned int getMaxX() {
        return maxX;
    }

    /**
     * Get the biggest Y (vertical) value in the world.
     * 
     * @return The maximum Y value.
     */
    unsigned int getMaxY() {
        return maxY;
    }
    
    /**
     * Get the starting position of the player in the world.
     * 
     * @return The BlockPos representing the starting position in the current level.
     */

    BlockPos getStartPos() {
        return startPos;
    }
private:
    BlockRegistry blockRegistry;
    vector<vector<Block>> field;
    unsigned int maxX = 0;
    unsigned int maxY = 0;
    BlockPos startPos = BlockPos(0, 0);
};
