contract DeathCombat is ERC721Enumerable, Ownable {
    using SafeMath for uint256;
    using Address for address;
    using Strings for uint256;

    uint256 public constant NFT_PRICE = 60000000000000000; // 0.06 ETH
    uint256 public constant NFT_PRICE_BULK = 150000000000000000; // 0.15 ETH
    uint public constant MAX_NFT_PURCHASE = 21;
    uint256 maxFreeNFTSupply = 2500;
    uint256 freeNFTDistributed = 0;
    uint256 public MAX_SUPPLY = 10000;
    bool public saleIsActive = false;

    string private _baseURIExtended;
    mapping(uint256 => string) _tokenURIs;
    mapping(address => bool) minted;
    mapping(address => uint256) purchased;

    modifier mintOnlyOnce() {
        require(!minted[_msgSender()], 'Can only mint once');
        minted[_msgSender()] = true;
        _;
    }

    constructor() ERC721('Death Combat', 'DCOM') {}

    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }
    
    function getfreeNFTDistributed() public view returns (uint256) {
        return freeNFTDistributed;
    }
    
    function getMaxFreeNFTSupply() public view returns(uint256) {
       return maxFreeNFTSupply;
    }
    
    function updateMaxFreeNFTSupply(uint256 i) public onlyOwner {
        maxFreeNFTSupply = i;
    }
    
    function isFreeWeaponClaimed() public view returns(bool) {
        return minted[_msgSender()];
    }

    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }


    function mintWeaponBase(uint numberOfTokens) private {
        require(saleIsActive, 'Sale is not active at the moment');
        require(numberOfTokens > 0, "Cannot mint 0 tokens");
        require(purchased[msg.sender].add(numberOfTokens) <= MAX_NFT_PURCHASE, 'Can only mint up to 21 per address');
        require(totalSupply().add(numberOfTokens) <= MAX_SUPPLY, "Purchase would exceed max supply of Weapons");
        purchased[msg.sender] = purchased[msg.sender].add(numberOfTokens);
        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(msg.sender, totalSupply());
        }
    }
    
    function mintWeapon(uint numberOfTokens) public payable {
        require(numberOfTokens > 0 && numberOfTokens < 22, "0-21 tokens");
        require(NFT_PRICE.mul(numberOfTokens) <= msg.value, "Sent ether value is incorrect");
        mintWeaponBase(numberOfTokens);
    }

    function mintWeaponBulk(uint numberOfTokens) public payable {
        require(numberOfTokens > 0 && numberOfTokens < 8, "0-21 tokens");
        require(NFT_PRICE_BULK.mul(numberOfTokens) <= msg.value, "Sent ether value is incorrect");
        mintWeaponBase(numberOfTokens * 3);
    }

    function mintWeaponForFree() public mintOnlyOnce {
        require(saleIsActive, 'Sale is not active at the moment');
        require(totalSupply().add(1) <= MAX_SUPPLY, 'Purchase would exceed max supply of Weapons');
        require(freeNFTDistributed.add(1) <= maxFreeNFTSupply, 'All Free NFTs have been distributed');
        freeNFTDistributed = freeNFTDistributed.add(1);
        _safeMint(msg.sender, totalSupply());
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return _baseURIExtended;
    }

    // Sets base URI for all tokens, only able to be called by contract owner
    function setBaseURI(string memory baseURI_) external onlyOwner {
        _baseURIExtended = baseURI_;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), 'ERC721Metadata: URI query for nonexistent token');

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(base, tokenId.toString(), ".json"));
    }

    function royaltyInfo(uint256 tokenId, uint256 salePrice) external view returns (address receiver, uint256 royaltyAmount) {
      LibPart.Part[] memory _royalties = royalties[tokenId];
      if (_royalties.length > 0) {
        return (_royalties[0].account, (salePrice * _royalties[0].value) / 10000);
      } 
      return (address(0), 0);
    }
}
function setBaseURI(string memory baseURI_) external onlyOwner {
    _baseURIExtended = baseURI_;
}

// Sets base URI for all tokens, only able to be called by contract owner