contract HollywoodPunks is ERC721Enumerable, Ownable, IERC2981 {
    using Strings for uint256;
    using SafeMath for uint256;
    
    string baseURI;
    string baseExtension = ".json";
    string public contractUri;
    string public notRevealedUri;
    
    uint256 public preSaleCost = 0.135 ether;
    uint256 public publicSaleCost = 0.2 ether;
    uint256 public maxSupply = 8877;
    uint256 public maxPreSaleMintAmount = 5;
    uint256 public maxMintAmount = 15;
    uint256 public maxPreSale = 1500;
    uint256 public preSaleStart = 1643153580;
    uint256 public publicSaleStart = 1643412780;
    bool public paused = false;
    bool public revealed = false;
    bool public onlyWhiteList = true;
    address public immutable multisig;
    mapping(address => bool) public whitelistedAddresses;

    // emit when royalties recieved
    event RoyaltiesReceived(
        address indexed _royaltyRecipient,
        address indexed _buyer,
        uint256 indexed _tokenId,
        address _tokenPaid,
        uint256 _amount,
        bytes32 _metadata
    );

    modifier onlyMultisig() {
        require(msg.sender == multisig, "Only multisig wallet can perfrom this action");
        _;
    }

    constructor(
        string memory name,
        string memory symbol,
        string memory initBaseURI, 
        string memory initNotRevealedUri,
        string memory initContractUri,
        address multisigAddr
    ) ERC721(name, symbol) {
        baseURI = initBaseURI;
        notRevealedUri = initNotRevealedUri;
        multisig = multisigAddr;
        contractUri = initContractUri;
    }

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

    function contractURI() public view returns (string memory) {
        return contractUri;
    }

    function mint(uint256 mintAmount) external payable {
        require(!paused, "Minting is paused");
        require(block.timestamp > preSaleStart, "Not started");
        require(mintAmount > 0, "Mint amount should be greater than 0");
        uint256 supply = totalSupply();
        require(supply + mintAmount <= maxSupply, "Max supply overflow");
        
        if (block.timestamp < publicSaleStart) {
            require(!onlyWhiteList || isWhitelisted(msg.sender), "Account is not whitelisted");
            require(supply + mintAmount <= maxPreSale, "Only 1500 tokens are allowed for presale");
            require(balanceOf(msg.sender) + mintAmount <= maxPreSaleMintAmount, "Limit for presale is 5 tokens per one wallet");
            require(msg.value == preSaleCost * mintAmount, "wrong ETH value");
        } else {
            require(balanceOf(msg.sender) + mintAmount <= maxMintAmount, "Limit is 15 tokens per one wallet");
            require(msg.value == publicSaleCost * mintAmount, "wrong ETH value");
        }
        for (uint256 i = 1; i <= mintAmount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    function mintWithOwner(address to, uint256 mintAmount) external onlyMultisig {
        require(to != address(0), "Can not mint to zero");
        require(block.timestamp > preSaleStart, "Not allowed yet");
        uint256 supply = totalSupply();
        require(maxSupply > supply, "Max supply is reached");
        if (maxSupply - supply > mintAmount) {
            for (uint256 i = 1; i <= mintAmount; i++) {
                _mint(to, supply + i);
            }
        } else {
            for (uint256 i = 1; i <= maxSupply - supply; i++) {
                _mint(to, supply + i);
            }
        }
    }

    function setMaxPreSale(uint256 max) external onlyMultisig {
        maxPreSale = max;
    }

    function isWhitelisted(address user) public view returns (bool) {
        return whitelistedAddresses[user];
    }

    function tokensOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory tokenIds = new uint256[](ownerTokenCount);
        for (uint256 i; i < ownerTokenCount; i++) {
            tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokenIds;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require( _exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        if (revealed == false) {
            return notRevealedUri;
        }
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
                ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension)) : "";
    }

    function reveal() external onlyMultisig {
        revealed = true;
    }

    function setCost(uint256 _preSaleCost, uint256 _publicSaleCost) external onlyMultisig {
        preSaleCost = _preSaleCost;
        publicSaleCost = _publicSaleCost;
    }

    function setMaxSupply(uint256 newMaxSupply) external onlyMultisig {
        maxSupply = newMaxSupply;
    }

    function setMaxMintAmount(uint256 newmaxMintAmount, uint256 newmaxPresaleMintAmount) external onlyMultisig {
        maxMintAmount = newmaxMintAmount;
        maxPreSaleMintAmount = newmaxPresaleMintAmount;
    }

    function setNotRevealedURI(string memory notRevealedURI) public onlyMultisig {
        notRevealedUri = notRevealedURI;
    }

    function setBaseURI(string memory newBaseURI) public onlyMultisig {
        baseURI = newBaseURI;
    }

    function setBaseExtension(string memory newBaseExtension) external onlyMultisig {
        baseExtension = newBaseExtension;
    }

    function pause(bool state) external onlyMultisig {
        paused = state;
    }

    function setOnlyWhiteList(bool state) external onlyMultisig {
        onlyWhiteList = state;
    }

    function setDates(uint256 _preSaleStart, uint256 _publicSaleStart) external onlyMultisig {
        preSaleStart = _preSaleStart;
        publicSaleStart = _publicSaleStart;
    }

    function whitelistUsers(address[] calldata users) external onlyOwner {
        for (uint256 i; i < users.length; i++) {
            whitelistedAddresses[users[i]] = true;
        }
    }

    function deleteFromWhitelist(address user) external onlyOwner {
        whitelistedAddresses[user] = false;
    }

    function withdraw() external onlyMultisig {
        require(payable(msg.sender).send(address(this).balance));
    }

    /**
    * @notice Returns royalty reciever address and royalty amount
    * @param _tokenId Token Id
    * @param _salePrice Value to calculate royalty from
    * @return receiver Royalty reciever address
    * @return amount Royalty amount
    */
    function royaltyInfo(uint256 _tokenId, uint256 _salePrice) external view override returns (address receiver, uint256 amount) {
        require(_tokenId > 0);
        receiver = this.owner();
        if(_salePrice <= 100) {
            amount = 0;
        } else {
            amount = _salePrice.mul(5).div(100);
        }
    }

    /**
    * @notice Calls when royalty recieved
    */
    function onRoyaltiesReceived(address _royaltyRecipient, address _buyer, uint256 _tokenId, address _tokenPaid, uint256 _amount, bytes32 _metadata) external returns (bytes4) {
        emit RoyaltiesReceived(_royaltyRecipient, _buyer, _tokenId, _tokenPaid, _amount, _metadata);
        return bytes4(keccak256("onRoyaltiesReceived(address,address,uint256,address,uint256,bytes32)"));
    }
}
function royaltyInfo(uint256 _tokenId, uint256 _salePrice) external view override returns (address receiver, uint256 amount) {
    require(_tokenId > 0);
    receiver = this.owner();
    if(_salePrice <= 100) {
        amount = 0;
    } else {
        amount = _salePrice.mul(5).div(100);
    }
}

/**
 * @notice Returns royalty reciever address and royalty amount
 * @param _tokenId Token Id
 * @param _salePrice Value to calculate royalty from
 * @return receiver Royalty reciever address
 * @return amount Royalty amount
 */