// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/ownable.sol";

struct Raider {
    uint16 level;
}

struct Army {
    Raider moloch_denier;
    Raider apprentice;
    Raider annointed;
    Raider champion;
}

struct Player {
    uint256 total_minted;
    uint256 created_at;
    uint256 last_raided_at;
}

contract RaidGeld is ERC20, Ownable {
    uint8 constant DECIMALS = 4;
    uint256 public constant BUY_IN_AMOUNT = 0.0005 ether;
    uint256 public constant INITIAL_GELD = 50 * 10 ** DECIMALS;
    uint256 public constant RAID_WAIT = 15 seconds;

    mapping(address => Player) private players;
    mapping(address => Army) private armies;

    // Modifier for functions that should only be available to registered players
    modifier onlyPlayer() {
        require(players[msg.sender].created_at != 0, "Not an initiated player");
        _;
    }

    constructor() ERC20("Raid Geld", "GELD") Ownable(msg.sender) {}

    // This effectively registers the user
    function register() external payable {
        require(players[msg.sender].created_at == 0, "Whoops, player already exists :)");
        require(msg.value == BUY_IN_AMOUNT, "Incorrect buy in amount");

        // Mint some starting tokens to the player
        _mint(msg.sender, INITIAL_GELD);

        // Set initial states
        players[msg.sender] = Player({total_minted: INITIAL_GELD, created_at: block.timestamp, last_raided_at: 0});
        armies[msg.sender] = Army({
            moloch_denier: Raider({level: 0}),
            apprentice: Raider({level: 0}),
            annointed: Raider({level: 0}),
            champion: Raider({level: 0})
        });
    }

    // Override for default number of decimals
    function decimals() public view virtual override returns (uint8) {
        return 4;
    }

    // Allows the owner to withdraw
    function withdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    // Manual minting for itchy fingers
    function raid() external onlyPlayer {
        require(block.timestamp >= players[msg.sender].last_raided_at + RAID_WAIT, "Tried minting too soon");
        // TODO: Make real calculation based on army
        uint256 new_geld = 50 * 10 ** decimals();

        // TODO: Pink noise, make it so sometimes its better than expected

        _mint(msg.sender, new_geld);
        players[msg.sender].last_raided_at = block.timestamp;
        players[msg.sender].total_minted = new_geld;
    }

    // Function to get Player struct
    function getPlayer(address addr) public view returns (Player memory) {
        return players[addr];
    }

    // Function to get Army struct
    function getArmy(address addr) public view returns (Army memory) {
        return armies[addr];
    }

    // Quick fn to check if user is registered
    function isRegistered(address addr) public view returns (bool) {
        return players[addr].created_at != 0;
    }

    // Add a unit to your army
    function addUnit(uint8 unit, uint16 n_units) external onlyPlayer {
        require(unit <= 3, "Unknown unit");

        // TODO: Calculate actual price
        uint256 price = 1 * 10 ** decimals();
        uint256 cost = price * n_units;
        
        require(balanceOf(msg.sender) > cost, "Not enough GELD to add that many units");
        _burn(msg.sender, cost);

        Army storage army = armies[msg.sender];
        // Increase level
        if (unit == 0) { // moloch_denier
           army.moloch_denier.level += n_units;
        } else if (unit == 1) { // apprentice
           army.apprentice.level += n_units;
        } else if (unit == 2) { // annointed
             army.annointed.level += n_units;
        } else if (unit == 3) { // champion
           army.champion.level += n_units;
        }
    }

    receive() external payable {
        revert("No plain Ether accepted, use register() function to check in :)");
    }

    // Revert any non-function-call Ether transfers or calls to non-existent functions
    fallback() external payable {
        revert("No fallback calls accepted");
    }
}
