contract Ticket is ERC1155, Ownable, ReentrancyGuard {
     using Counters for Counters.Counter;
     using SafeMath for uint256;
     string public BaseURI;
     Counters.Counter private _tokenCounter;
     
     uint256 private totalCount;
     event RedeemFractions(address indexed _address, uint256 indexed _id, uint256 _tickets);
     event RedeemNFT(address indexed _address, uint256 indexed _id, uint256 _tickets);
     event RedeemPhysical(address indexed _address, uint256 indexed _id, uint256 _tickets);

     Iminter public CurrentNFTContract;
     IERC20 public FractionsContract;
     
     mapping(uint256 => string) uriTokenHash;
     
     mapping(uint256 => uint256) public TotalRedeemed;
     
     mapping(uint256 => bool) public FractionRedeemAllowed;
     mapping(uint256 => uint256) public FractionsPerTicket;
     
     mapping(uint256 => bool) public AlternateRedeemAllowed;
     mapping(uint256 => uint256) public TicketsPerAlternate;
     
     mapping(uint256 => bool) public ERC721RedeemAllowed;
     mapping(uint256 => uint256) public TicketsPerERC721;
     
     address constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
     
    constructor() ERC1155("PDRP") {

    }
    
    function setFractionsContract(address fractions) public onlyOwner{
        FractionsContract = IERC20(fractions);
    }
    
    function setNFTContract(address nftAddress) public onlyOwner{
        CurrentNFTContract = Iminter(nftAddress);
    }
    
    function addNewTicket(string memory hash) public onlyOwner {
        _tokenCounter.increment();
        uint256 tokenId = _tokenCounter.current();
        uriTokenHash[tokenId] = hash;
        
        FractionRedeemAllowed[tokenId] = false;
        AlternateRedeemAllowed[tokenId] = false;
        ERC721RedeemAllowed[tokenId] = false;
        
        FractionsPerTicket[tokenId] = 0;
        TicketsPerAlternate[tokenId] = 0;
        TicketsPerERC721[tokenId] = 0;
    }
    
    function editTokenRedemption(uint256 tokenId, bool isFractionAllowed, bool isAlternateAllowed, bool isNFTAllowed
                                , uint256 numberOfFractionsPerTicket, uint256 numberofTicketsPerAlternate, uint256 numberOfTicketsPerNFT) public onlyOwner tokenExists(tokenId){
        FractionRedeemAllowed[tokenId] = isFractionAllowed;
        AlternateRedeemAllowed[tokenId] = isAlternateAllowed;
        ERC721RedeemAllowed[tokenId] = isNFTAllowed;
        
        FractionsPerTicket[tokenId] = numberOfFractionsPerTicket;
        TicketsPerAlternate[tokenId] = numberofTicketsPerAlternate;
        TicketsPerERC721[tokenId] = numberOfTicketsPerNFT;
    }
    
    function setTokenHash(uint256 tokenId, string memory hash) public onlyOwner tokenExists(tokenId){
        uriTokenHash[tokenId] = hash;
    }
    
    function setURI(string memory newuri) public onlyOwner {
        BaseURI = newuri;
    }

    
    function uri(uint256 id) public view virtual tokenExists(id) override returns (string memory) {
        return string(abi.encodePacked(BaseURI, uriTokenHash[id]));
    }
    
    modifier tokenExists(uint256 tokenId){
        require(tokenId <= _tokenCounter.current(), "Token does not exist");
        _;
    }
    
    function mintbulk(uint256 tokenId, address[] memory addresses, uint256[] memory quantities) public onlyOwner tokenExists(tokenId) {
        require(addresses.length == quantities.length, "Address and quantities need to be equal length");
        require(TotalRedeemed[tokenId].add(quantities.length) <= 10000, "Not enough tokens remaining");
        bytes memory x;
        for(uint256 i; i < quantities.length; i++){
            _mint(addresses[i], tokenId, quantities[i], x);
            TotalRedeemed[tokenId] = TotalRedeemed[tokenId].add(quantities[i]);
            totalCount = totalCount.add(quantities[i]);
        }
        
        
    }
    
    function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
     
     return operator == address(this) || super.isApprovedForAll(account, operator);   
    }
    
    function recallTokens(uint256 tokenId, address[] memory addresses) public onlyOwner{
        
        bytes memory x;
        for(uint256 i; i < addresses.length; i++){
            _safeTransferFrom(addresses[i], msg.sender, tokenId, balanceOf(addresses[i], tokenId), x);
        }
    }
    
    function burnTokenForFractions(uint256 numberOfTickets, uint256 tokenId) public tokenExists(tokenId) nonReentrant  {
        require(FractionRedeemAllowed[tokenId], "Kitty Bank fraction redemption is not currently open for this token");
        require(this.balanceOf(msg.sender, tokenId) >= numberOfTickets && numberOfTickets != 0, "You do not have any of these tokens to redeem");
        
        uint256 fractions = numberOfTickets.mul(FractionsPerTicket[tokenId]);
        require(FractionsContract.balanceOf(address(this)) >= fractions, "Not enough fractions available to redeem");
        
        //burn the tickets
        this.safeTransferFrom(msg.sender, BURN_ADDRESS, tokenId, numberOfTickets, "0x0");
        totalCount = totalCount.sub(numberOfTickets);
        //send tokens
        FractionsContract.transfer(msg.sender, fractions);
        
        emit RedeemFractions(msg.sender, tokenId, numberOfTickets);
    }
    
    function burnTokenForAlternate(uint256 numberOfTickets, uint256 tokenId) public tokenExists(tokenId) nonReentrant {
        require(AlternateRedeemAllowed[tokenId], "Alternate redemption is not currently open for this token");
        require(this.balanceOf(msg.sender, tokenId) >= numberOfTickets && numberOfTickets != 0, "You do not have any of these tokens to redeem");
        require(numberOfTickets == TicketsPerAlternate[tokenId] && numberOfTickets != 0, "Not enough tickets to redeem");

        
        //burn the tickets
        this.safeTransferFrom(msg.sender, BURN_ADDRESS, tokenId, numberOfTickets, "0x0");
        totalCount = totalCount.sub(numberOfTickets);
        
        emit RedeemPhysical(msg.sender, tokenId, numberOfTickets);
    }
    
    function burnTokenForCompanion(uint256 numberOfTickets, uint256 tokenId) public tokenExists(tokenId) nonReentrant {
        require(ERC721RedeemAllowed[tokenId], "NFT redemption is not currently open for this token");
        require(this.balanceOf(msg.sender, tokenId) >= numberOfTickets && numberOfTickets != 0, "You do not have any of these tokens to redeem");
        require(numberOfTickets.mod(TicketsPerERC721[tokenId]) == 0 && numberOfTickets != 0, "Not enough tickets to redeem");
    
        CurrentNFTContract.mint(msg.sender, numberOfTickets.div(TicketsPerERC721[tokenId]));
    
        //burn the tickets
        this.safeTransferFrom(msg.sender, BURN_ADDRESS, tokenId, numberOfTickets, "0x0");
        totalCount = totalCount.sub(numberOfTickets);
        
        emit RedeemNFT(msg.sender, tokenId, numberOfTickets);
    }
    
	function withdrawTokens(IERC20 token) public onlyOwner {
    	require(address(token) != address(0));
    	uint256 balance = token.balanceOf(address(this));
    	token.transfer(msg.sender, balance);
	}
	
	//no-one should transfer eth... but if they do.
	function withdraw() public onlyOwner {
		uint256 balance = address(this).balance;
		payable(msg.sender).transfer(balance);
	}

    function totalSupply() public view returns(uint256) {
        return totalCount;
    }

}
function withdraw() public onlyOwner {
		uint256 balance = address(this).balance;
		payable(msg.sender).transfer(balance);
	}
//no-one should transfer eth... but if they do.