contract DuskBreakers is ERC721Enumerable, Ownable, ReentrancyGuard {
    using Strings for string;
    using SafeMath for uint256;
    using Counters for Counters.Counter;

    /*
    * Enforce the existence of only 10 Breakers.
    */
    uint256 public constant BREAKER_SUPPLY = 10000;
    uint256 public constant breakerPrice = 0.06 ether;
    string public baseTokenURI;
    bool private PAUSE = true;
    bool private fcfsMint = false;
    
    // Ensure Fairness
    string public BREAKER_PROVENANCE = "";
    uint256 public startingIndexBlock;
    uint256 public startingIndex;

    // tokenID storage
    Counters.Counter private _tokenIdCounter;

    // Presale, Play2Mint
    PresaleConfig public presaleConfig;
    Play2MintConfig public play2MintConfig;

    // Presales and Play2Mints mappings
    mapping(address => uint256) private _presaleList;
    mapping(address => uint256) private _play2MintList;
    
    // Presale and Play2Mint Structs
    struct PresaleConfig {
        uint256 mintMax;
    }
    struct Play2MintConfig {
        uint256 mintMax;
    }
    
    // Breaker Contract Events
    event ChangePresaleConfig(
        uint256 _mintMax
    );
    
    event ChangePlay2MintConfig(
        uint256 _mintMax
    );
    
    // Events
    event PauseEvent(bool pause);
    //event welcomeToTheDusk(uint256 indexed id);
    
    // Safety Modifiers
    function isUnpausedAndSupplied() public view returns (bool){
        return (hasSupply(1)) && (!PAUSE);
    }
    
    modifier saleIsOpen {
        require(hasSupply(1), "Breakers Sold Out!");
        require(!PAUSE, "Sales not open");
        _;
    }

    function setPause(bool _pause) public onlyOwner{
        PAUSE = _pause;
        emit PauseEvent(PAUSE);
    }
    
    // Open Floodgates for FCFS
    event FCFSEvent(bool pause);
    function setFCFS(bool _fcfsMint) public onlyOwner{
        fcfsMint = _fcfsMint;
        emit FCFSEvent(fcfsMint);
    }
    
    // Token URI overrides
    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

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

    // token counters
    function nextToken() public view returns (uint256) {
        return _tokenIdCounter.current();
    }

    constructor(string memory name, string memory symbol) ERC721(name, symbol){
        
        // First we allow presales to mint during Play2Mint
        // Then, we allow Play2Mint 1 day
        // Finally we open for anyone to mint
        
        //preseale starts on contract creation
        uint256 _mintMax = 2;
        
        presaleConfig = PresaleConfig(_mintMax);
        emit ChangePresaleConfig(_mintMax);
        // Play2Mint allow minting config
        uint256 _p2MmintMax = 3;

        play2MintConfig = Play2MintConfig(_p2MmintMax);
        emit ChangePlay2MintConfig(_p2MmintMax);
        
        setBaseURI("https://duskbreakers.gg/api/breakers/");
        
    }
    
    /*
    * Fairness Section....
    * Pre mint, we set the provenance hash to the hash of hashes of the sha256
    * in order of original image generation
    * We then have an offest based on the last block sold, modulo the total supply
    * Thus you know we genereated them in the order we said and anyone including us had no way
    * to know what the starting index will be
    */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        BREAKER_PROVENANCE = provenanceHash;
    }

    // It has gone wrong and the presale didn't fully mint so this allows us to reveal
    function emergencySetStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");
        startingIndexBlock = block.number;
    }

    function setStartingIndex() public {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");

        startingIndex = uint(blockhash(startingIndexBlock)) % BREAKER_SUPPLY;
        // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
        if (block.number.sub(startingIndexBlock) > 255) {
            startingIndex = uint(blockhash(block.number - 1)) % BREAKER_SUPPLY;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }
    // 
    function addTreasury(address _address)
    external
    onlyOwner     
    {
        require(
            _address != address(0),
            "Breaker Treasury: Can't add a zero address"
        );
        if (_presaleList[_address] == 0) {
            _presaleList[_address] = 420; // It seems appropriate
        }
    }
    
    // Presale and Play2Mint whitelists
    function addToPresaleList(address[] calldata _addresses)
    external
    onlyOwner
    {
            for (uint256 ind = 0; ind < _addresses.length; ind++) {
            require(
                _addresses[ind] != address(0),
                "Breaker Presale: Can't add a zero address"
            );
            if (_presaleList[_addresses[ind]] == 0) {
                _presaleList[_addresses[ind]] = presaleConfig.mintMax;
            }
        }
    }
    
    function addToPlay2Mint(address[] calldata _addresses)
    external
    onlyOwner
    {
          for (uint256 ind = 0; ind < _addresses.length; ind++) {
            require(
                _addresses[ind] != address(0),
                "DuskBreaker P2M: Can't add a zero address"
            );
            if (_play2MintList[_addresses[ind]] == 0) {
                _play2MintList[_addresses[ind]] = play2MintConfig.mintMax;
            }
        }
    }

    function mintBreaker(uint256 numberOfTokens) public payable saleIsOpen nonReentrant() {
 
        require(hasSupply(numberOfTokens), "Purchase exceeds max total Breakers");
        require(canMint(numberOfTokens), "Mint Access Not Granted");
        require(breakerPrice.mul(numberOfTokens) <= msg.value, "ETH sent in transaction too low");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = nextToken();
            if (mintIndex < BREAKER_SUPPLY) {
                _safeMint(msg.sender, mintIndex);
                //emit welcomeToTheDusk(mintIndex);
                _tokenIdCounter.increment();
                // remove presales sales first then play2mint sales next
                if (_presaleList[msg.sender] > 0) {
                    _presaleList[msg.sender] = _presaleList[msg.sender].sub(1);
                } else if (_play2MintList[msg.sender] > 0) {
                    _play2MintList[msg.sender] = _play2MintList[msg.sender].sub(1);
                }
            }
        }
        
        // Did we mint the last block? If so, ready the starting index based on this block number
        if (startingIndexBlock == 0 && (nextToken() == BREAKER_SUPPLY)) {
            startingIndexBlock = block.number;
        }
    }
    
    // Safeguards

    function hasSupply(uint256 numberOfTokens) public view returns (bool) {
        uint256 creatureSupply = nextToken();
        return creatureSupply.add(numberOfTokens) <= BREAKER_SUPPLY;
    }
    
    function canMint(uint256 numberOfTokens) public view returns (bool) {    
        // skip guard so web3 has easy time
        if(! isUnpausedAndSupplied()) {
            return false;
        }
        // Presales can mint their max
        if(_presaleList[msg.sender] > 0 &&
            numberOfTokens <= _presaleList[msg.sender] 
        ) {
            return true;
        }
        // same for play2mint people, but if you are both whitelist and p2m, you can mint the sum
        if(_play2MintList[msg.sender] > 0 &&
            numberOfTokens <= _play2MintList[msg.sender]
        ) {
            return true;
        }
        // after Play2Mint, anyone can mint
        if(fcfsMint) {
            return true;
        }
        
        return false;
    }
    
    // Web3 Economics
    function withdrawAll() public onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0);
        _widthdraw(msg.sender, address(this).balance);
    }

    function _widthdraw(address _address, uint256 _amount) private {
        (bool success, ) = _address.call{value: _amount}("");
        require(success, "Transfer failed.");
    }
}
function _baseURI() internal view virtual override returns (string memory) {
    return baseTokenURI;
}

// Token URI overrides