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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import {MintGate} from "@esportsplus/erc721/contracts/libraries/MintGate.sol";
import {Withdrawable} from "@esportsplus/erc721/contracts/utilities/Withdrawable.sol";
import {ERC20, ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {LOOTBOARD_MAX_TILES, LOOTBOARD_SIGNER, LOOTBOARD_TILE_PRICE} from './Constants.sol';

error ClaimFailed();
error InvalidMaxTile();
error InvalidSignature();
error PurchaseFailed();

contract Loot is ERC20Burnable, Ownable, Withdrawable {
    using BitMaps for BitMaps.BitMap;
    using ECDSA for bytes32;


    event Purchased(address indexed account, uint256[] tiles);


    BitMaps.BitMap private _claimed;

    BitMaps.BitMap private _purchased;


    uint256 public _i;

    uint256 public _maxTile = 22500;

    uint256 public _price = LOOTBOARD_TILE_PRICE;

    address public _signer = LOOTBOARD_SIGNER;


    constructor() ERC20("Loot", "LOOT") {}


    function _isValidSignature(bytes32 message, bytes memory signature) internal view returns (bool) {
        return message.toEthSignedMessageHash().recover(signature) == _signer;
    }

    function _isValidTile(uint256 tile) internal view returns (bool) {
        return tile > 0 && tile <= _maxTile;
    }


    function claim(bytes memory signature, uint256[] calldata tiles, uint256 tokens) external {
        address account = msg.sender;
        uint256[] memory available = claimable(tiles);
        uint256 i = available.length;

        if (i != tiles.length) {
            revert ClaimFailed();
        }

        while (i != 0) {
            unchecked {
                --i;
            }

            _claimed.set(tiles[i]);
        }

        if (!_isValidSignature(keccak256(abi.encodePacked(account, tiles, tokens)), signature)) {
            revert InvalidSignature();
        }

        _mint(account, tokens);
    }

    function claimable(uint256[] calldata tiles) public view returns(uint256[] memory) {
        uint256 i = tiles.length;
        uint256 quantity = 0;
        uint256[] memory unfiltered = new uint256[](i);

        while (i != 0) {
            unchecked {
                --i;
            }

            uint256 tile = tiles[i];

            if (!_isValidTile(tile) || _claimed.get(tile) || !_purchased.get(tile)) {
                continue;
            }

            unchecked {
                unfiltered[quantity++] = tile;
            }
        }

        if (quantity == unfiltered.length) {
            return unfiltered;
        }

        uint256[] memory filtered = new uint256[](quantity);

        while (quantity != 0) {
            unchecked {
                --quantity;
            }

            filtered[quantity] = unfiltered[quantity];
        }

        return filtered;
    }

    function decimals() public view virtual override returns (uint8) {
        return 0;
    }

    function purchase(uint256[] calldata tiles) external payable {
        uint256 i = tiles.length;
        uint256 quantity = 0;
        uint256[] memory unfiltered = new uint256[](i);

        while (i != 0) {
            unchecked {
                --i;
            }

            uint256 tile = tiles[i];

            if (!_isValidTile(tile) || _purchased.get(tile)) {
                continue;
            }

            _purchased.set(tile);

            unchecked {
                unfiltered[quantity++] = tile;
            }
        }

        if (quantity == 0) {
            revert PurchaseFailed();
        }

        MintGate.price(msg.sender, _price, quantity, msg.value);
        MintGate.supply(LOOTBOARD_MAX_TILES - _i, quantity);

        unchecked {
            _i += quantity;
        }

        uint256[] memory filtered = new uint256[](quantity);

        while (quantity != 0) {
            unchecked {
                --quantity;
            }

            filtered[quantity] = unfiltered[quantity];
        }

        emit Purchased(msg.sender, filtered);
    }

    function setMaxTile(uint256 max) external onlyOwner {
        if (max > LOOTBOARD_MAX_TILES) {
            revert InvalidMaxTile();
        }

        _maxTile = max;
    }

    function setPrice(uint256 price) public onlyOwner {
        _price = price;
    }

    function setSigner(address signer) public onlyOwner {
        _signer = signer;
    }

    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;

        _withdraw(0x976A1082fE55bF2ef50e62aedADA4ccDf4088191, (balance * 5000) / 10000);
        _withdraw(0x5c45aFc97acd8DF074F765507c5bD41A7ec7a8eA, (balance * 4000) / 10000);
        _withdraw(0x3BCe6a0C6d20C56A2942838187dEd2540B292B27, address(this).balance);
    }
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"string","name":"method","type":"string"}],"name":"AmountExceedsBalance","type":"error"},{"inputs":[],"name":"ClaimFailed","type":"error"},{"inputs":[],"name":"InvalidMaxTile","type":"error"},{"inputs":[],"name":"InvalidSignature","type":"error"},{"inputs":[],"name":"NeedToSendMoreETH","type":"error"},{"inputs":[],"name":"PartialRefundFailed","type":"error"},{"inputs":[],"name":"PurchaseFailed","type":"error"},{"inputs":[],"name":"QuantityWouldExceedMaxSupply","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tiles","type":"uint256[]"}],"name":"Purchased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"_i","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_maxTile","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_signer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"uint256[]","name":"tiles","type":"uint256[]"},{"internalType":"uint256","name":"tokens","type":"uint256"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tiles","type":"uint256[]"}],"name":"claimable","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tiles","type":"uint256[]"}],"name":"purchase","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"max","type":"uint256"}],"name":"setMaxTile","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"price","type":"uint256"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"setSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]