contract BitBandits is ERC721{
    using Strings for uint256;


    using Counters for Counters.Counter;

    Counters.Counter private _tokenSupply;
    Counters.Counter private _testaSupply;

    string baseURI;
    string public baseExtension = ".json";
    uint256 public publicSaleCost = 0.05 ether;
    uint256 public preSale2Cost = 0.04 ether;
    uint256 public preSale1Cost = 0.03 ether;

    address public owner = msg.sender;

    
    uint256 public maxSupply = 3333; 
    uint256 public testaMaxSupply = 15; // testas start after maxSupply

    uint256 public TESTA_PRICE = 300 ether;
    // Mapping owner address to testa
    mapping(address => uint256) public testa_balances;


    bool public publicSaleActive = false;

    uint256 public preSale1StartSupply;
    uint256 public preSale1Supply = 500;
    bool public preSale1Active = false;

    uint256 public preSale2StartSupply;
    uint256 public preSale2Supply = 1000;
    bool public preSale2Active = false;

    ILUT public yieldToken;

    bool public revealed = false;
    string public notRevealedUri;
    bool public tokenLinked = false;
    //

    address public owner1 = 0xf03B838b42B6313Fa6abdF5c68D7ba672C683A50;
    address public owner2 = 0x5384e2110A466F7E611efd37E82FEeE77Dd7e3f5;
    address public owner3 = 0x75D48BC691a6c102966FED66B0e3D676dBfC6E90;


    constructor(
        string memory _name,
        string memory _symbol,
        string memory _initBaseURI,
        string memory _initNotRevealedUri

    ) ERC721(_name, _symbol) {
        setBaseURI(_initBaseURI);
        setNotRevealedURI(_initNotRevealedUri);

    }



    ////////////////////
    //  TOKEN BASED   //
    ////////////////////

    // called on transfer & on mint
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (tokenLinked == true){
            yieldToken.updateReward(from, to);
        }
        

        if (tokenId > maxSupply && from != address(0) && to != address(0)){
            testa_balances[from] -= 1;
            testa_balances[to] += 1;
        }

    }

    function setLUT(address LUTAddress) external onlyOwner {
        yieldToken = ILUT(LUTAddress);
        tokenLinked = true;
    }




    ////////////////////
    //  NFT BASED   //
    ////////////////////



    function mintGift(address reciever, uint256 _howMany) external payable onlyOwner {
        uint256 fullMintAmount = _tokenSupply.current() + _howMany; 
        require(fullMintAmount <= maxSupply, "Sold out!");
        
        for (uint256 i = 0; i < _howMany; i++){
            uint256 mintIndex = _tokenSupply.current() + 1; // Start IDs at 1
            // Mint.
            _tokenSupply.increment();
            _safeMint(reciever, mintIndex);
        }
    }


    function mintTestaGift() external payable onlyOwner {
        uint256 mintIndexTesta = _testaSupply.current() + 1; // Start IDs at 1
        require(mintIndexTesta <= testaMaxSupply, "No more Testa's available!");
        // Mint.
        _testaSupply.increment();
        testa_balances[msg.sender] += 1;
        _safeMint(msg.sender, maxSupply + mintIndexTesta);
    }



    ////////////////////
    //  PUBLIC SALE   //
    ////////////////////

    function mintPubSale(uint256 _howMany) public payable {
        require(publicSaleActive, "Public Sale Is Closed");

        uint256 fullMintAmount = _tokenSupply.current() + _howMany; 
        require(fullMintAmount <= maxSupply, "Sold out!");

        uint256 mintPrice = publicSaleCost;
        require(msg.value >= mintPrice * _howMany, "Not enough ETH to buy!");


        for (uint256 i = 0; i < _howMany; i++){
            uint256 mintIndex = _tokenSupply.current() + 1; // Start IDs at 1
            // Mint.
            _tokenSupply.increment();
            _safeMint(msg.sender, mintIndex);
        }
    }

    ////////////////////
    //  PRE SALES   //
    ////////////////////

    function mintPreSale1() public payable {
        require(preSale1Active, "Pre Sale Is Closed");

        uint256 mintIndex = _tokenSupply.current() + 1; 
       
        require(mintIndex <= preSale1StartSupply + preSale1Supply, "Sold out!");
        require(mintIndex <= maxSupply, "Sold out!");

        uint256 mintPrice = preSale1Cost;
        require(msg.value >= mintPrice, "Not enough ETH to buy!");

        // Mint.
        _tokenSupply.increment();
        _safeMint(msg.sender, mintIndex);
    }

    function mintPreSale2() public payable {
        require(preSale2Active, "Pre Sale Is Closed");

        uint256 mintIndex = _tokenSupply.current() + 1; 
       
        require(mintIndex <= preSale2StartSupply + preSale2Supply, "Sold out!");
        require(mintIndex <= maxSupply, "Sold out!");

        uint256 mintPrice = preSale2Cost;
        require(msg.value >= mintPrice, "Not enough ETH to buy!");

        // Mint.
        _tokenSupply.increment();
        _safeMint(msg.sender, mintIndex);
    }




    ////////////////////
    //  TESTA     //
    ////////////////////

    function mintTesta() public payable {
        uint256 mintIndexTesta = _testaSupply.current() + 1; // Start IDs at 1
        require(mintIndexTesta <= testaMaxSupply, "No more Testa's available!");
        yieldToken.spend(msg.sender, TESTA_PRICE);
        // Mint.
        _testaSupply.increment();
        testa_balances[msg.sender] += 1;
        _safeMint(msg.sender, maxSupply + mintIndexTesta);
    }


	function testaBalanceOf(address _owner) external view returns(uint256){
        require(_owner != address(0), "ERC721: balance query for the zero address");
        return testa_balances[_owner];
    }


    function setTestaPrice(uint256 _newCost) public onlyOwner {
        TESTA_PRICE = _newCost;
    }

        function setTestaSupply(uint256 _amount) public onlyOwner {
        testaMaxSupply = _amount;
    }
    //only owner

    function reveal() public onlyOwner {
      revealed = true;
  }

    function setPublicSaleCost(uint256 _newCost) public onlyOwner {
        publicSaleCost = _newCost;
    }
    
    function setPreSale1Cost(uint256 _newCost) public onlyOwner {
        preSale1Cost = _newCost;
    }
    function setPreSale2Cost(uint256 _newCost) public onlyOwner {
        preSale2Cost = _newCost;
    }

   function setPreSale1Amount(uint256 _newAmount) public onlyOwner {
        preSale1Supply = _newAmount;
    }
   function setPreSale2Amount(uint256 _newAmount) public onlyOwner {
        preSale2Supply = _newAmount;
    }



    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }

    function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
        baseExtension = _newBaseExtension;
    }

    function setPublicSaleState(bool _state) public onlyOwner {
        publicSaleActive = _state;
    }
    function setPreSale1State(bool _state) public onlyOwner {
        preSale1StartSupply = _tokenSupply.current();
        preSale1Active = _state;
    }
    function setPreSale2State(bool _state) public onlyOwner {
        preSale2StartSupply = _tokenSupply.current();
        preSale2Active = _state;
    }

    // Owner can withdraw ETH from here
    function withdrawETH() external onlyOwner {
        uint256 balance = address(this).balance;

        uint256 _splitTo3 = (balance * 0.333 ether) / 1 ether;

        payable(owner1).transfer(_splitTo3);
        payable(owner2).transfer(_splitTo3);
        payable(owner3).transfer(_splitTo3);

    }

     ///////////////////
    // Query Method  //
    ///////////////////


    function remainingSupply() public view returns (uint256) {
        return maxSupply - _tokenSupply.current();
    }


    function tokenSupply() public view returns (uint256) {
        return _tokenSupply.current();
    }

    function testaTokenSupply() public view returns (uint256) {
        return _testaSupply.current();
    }

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


     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))
        : "";
  }

    ///////////////////
    //  Helper Code  //
    ///////////////////



    modifier onlyOwner() {
        require(owner == msg.sender, "Ownable: caller is not the owner");
        _;
    }
}
function mintGift(address reciever, uint256 _howMany) external payable onlyOwner {
    uint256 fullMintAmount = _tokenSupply.current() + _howMany; 
    require(fullMintAmount <= maxSupply, "Sold out!");
        
    for (uint256 i = 0; i < _howMany; i++){
        uint256 mintIndex = _tokenSupply.current() + 1; // Start IDs at 1
        // Mint.
        _tokenSupply.increment();
        _safeMint(reciever, mintIndex);
    }
}

////////////////////