contract ProofOfWork721 is Context, ERC721, Ownable, ERC721Burnable {
    using Counters for Counters.Counter;
    using SafeMath for uint256;
    
    // bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    
    address payable public withdrawalAddress;
    
    //This needs to be the same as the maxNumberOfPieces
    bool[50] public isTokenMinted;
    
    //Token sale control logic
    Counters.Counter private piecesSold;
    uint256 public maxNumberOfPieces;
    uint256 public maxTokenIdForSale;
    uint256 public minTokenIdForSale;
    
    //Standard sale
    bool public standardSaleActive;
    uint256 public pricePerPiece;
    
    //Dutch Auction
    uint256 public startBlock;
    uint256 public startPrice;
    uint256 public offerPriceDecrement;
    uint256 public endPrice;
    uint256 public currentPrice;
    bool public dutchAuctionActive;
    
    event Purchase(address buyer, uint256 price, uint256 tokenId);
    event MetadataUpdated(string newTokenUriBase);
    
    constructor(
        uint256 givenPricePerPiece,
        address payable givenWithdrawalAddress,
        string memory givenTokenUriBase
    ) public ERC721("Proof of Work", "POW") {
        pricePerPiece = givenPricePerPiece;
        withdrawalAddress = givenWithdrawalAddress;
        _setBaseURI(givenTokenUriBase);
        maxNumberOfPieces = 50;
        maxTokenIdForSale = 0;
        minTokenIdForSale = 1;
    }
    
    //Update metadata base  
    function setMetadataBase(string memory givenTokenUriBase) public onlyOwner{
        emit MetadataUpdated(givenTokenUriBase);
        _setBaseURI(givenTokenUriBase);
    }
    
    //Specify a range of tokens for current sale
    function setSaleRange(uint256 givenMinTokenIdForSale, uint256 givenMaxTokenIdForSale) public onlyOwner {
         maxTokenIdForSale = givenMaxTokenIdForSale;
         minTokenIdForSale = givenMinTokenIdForSale;
    }
    
    //Dutch auction, price decreases each block until it reaches the minimum
    function startDutchAuction(uint256 givenStartPrice, uint256 givenOfferPriceDecrement, uint256 givenEndPrice) public onlyOwner{
        startBlock = block.number;
        startPrice = givenStartPrice;
        endPrice = givenEndPrice;
        currentPrice = givenStartPrice;
        offerPriceDecrement = givenOfferPriceDecrement;
        dutchAuctionActive = true;
    }
    
    function endDutchAuction() public onlyOwner{
        dutchAuctionActive = false;
    }
    
     //Called by front-end to determine current price
    function getDutchPrice() external view returns(uint256) {
        uint256 tempPrice = startPrice - (block.number - startBlock) * offerPriceDecrement;
       
        if(tempPrice < endPrice || tempPrice > startPrice){
            tempPrice = endPrice;
        }
        return tempPrice;
    }
    
    //Dutch auction buy function, calculates appropriate price based on blocks elapsed
    function buyDutch( uint256 givenTokenId) public payable{
        require(dutchAuctionActive, "dutch auction must be active"); 
       
        currentPrice = startPrice - (block.number - startBlock) * offerPriceDecrement;
     
        if(currentPrice < endPrice || currentPrice > startPrice){
            currentPrice = endPrice;
        }
        require(msg.value >= currentPrice, "must send in correct amount ");
        mintItem(givenTokenId);
    }
    
    //Standard buying functions are separate
    
    //Switch 
    function setSaleActive(bool isActive) public onlyOwner {
        standardSaleActive = isActive;
    }
    
    function setPrice(uint256 givenPrice) external onlyOwner {
        pricePerPiece = givenPrice;    
    }
    
    function getPrice() external view returns(uint256) {
        return pricePerPiece;
    }
    
    function buy( uint256 givenTokenId) public payable {
        require(standardSaleActive || msg.sender == owner(), "sale must be active"); 
        require(msg.value == pricePerPiece, "must send in correct amount");
        mintItem(givenTokenId);
    }

    //Withdrawal functions
    function setWithdrawalAddress(address payable givenWithdrawalAddress) public onlyOwner {
        withdrawalAddress = givenWithdrawalAddress;
    }

    function withdrawEth() public onlyOwner {
        Address.sendValue(withdrawalAddress, address(this).balance);
    }
    
    //Token owner info
    function tokenInfo(uint256 tokenId) public view returns (address) {
        return (ownerOf(tokenId));
    }
    
    function getOwners(uint256 start, uint256 end) public view returns (address[] memory){
        address[] memory re = new address[](end - start);
        for (uint256 i = start; i < end; i++) {
            if(isTokenMinted[i]){
                re[i - start] = ownerOf(i);
            }
        }
        return re;
    }
    
    function mintItem(uint256 givenTokenId) private returns (uint256) {
        
        require(givenTokenId <= maxTokenIdForSale, "given token value is greater than sale limit");
        require(givenTokenId >= minTokenIdForSale, "given token value is less than sale limit");
        require(piecesSold.current() < maxNumberOfPieces, "series is sold out");
        
        emit Purchase(msg.sender, msg.value, givenTokenId);
    
        _mint(msg.sender, givenTokenId);
        
        isTokenMinted[givenTokenId] = true;
        piecesSold.increment();
    
        return givenTokenId;
    }
}
/**
 * @dev {ERC721} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - token ID and URI autogeneration
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
function setWithdrawalAddress(address payable givenWithdrawalAddress) public onlyOwner {
    withdrawalAddress = givenWithdrawalAddress;
}

//Withdrawal functions