contract CBS is ERC721, Ownable {
    using SafeMath for uint256;

    uint256 public _price = 55000000000000000;
    
    uint256 private _tokensAllowedPerPresaleAddress = 9;
    uint256 private _tokensAllowedPerPublicMint = 21;
    
    uint256 public _maxTotalSupply = 8999;
    uint256 private _giveAwayReserve = 200;
    uint256 public _reserveAmountMinted = 0;
    
    uint256 public _totalAvailableForSale = _maxTotalSupply.sub(_giveAwayReserve);
    

    bool private _paused = true;
    
    address private _giveawayWalletAddress = 0x5a1b7bfA7C220cF020e3cF30F4561F7011339E45;

    address private _withdrawAddress = 0x966B0d7e79cE40191e914E9a28E019b6846F73ad;
    
    bool private _initialReserveMinted = false;
    bool private _presaleEnabled = false;
    
    mapping (address => bool) public _canTransferBeforeMintingIsEnabled;
    mapping (address => uint256) public _amountMintedByAddress;

    constructor() ERC721('Chill Bear Society', 'CBS') {
        setBaseURI("https://ipfs.io/ipfs/QmZsgrZvhU2ALwdsL9br9W7ajjyCo8rvRqBpaL6G3sYhUj/");
        mintInitialReserve();
    }

    
    //MINT x amount of Chill Bears
    function mint(uint256 _num) public payable {
        uint supply = totalSupply();
        require(supply.add(_num) < _totalAvailableForSale, "Supply exceeded!");
        
        if(msg.sender != _giveawayWalletAddress) {
            require(msg.value >= getPrice() * _num, "ETH sent not correct!");
            if(_presaleEnabled){
                 require(_canTransferBeforeMintingIsEnabled[msg.sender], "This Address has not been whitelisted");
                 require((_amountMintedByAddress[msg.sender].add(_num))<_tokensAllowedPerPresaleAddress, "Max Number of presale mints reached");
                 _amountMintedByAddress[msg.sender]+=_num;
            } else {
                require(_num < _tokensAllowedPerPublicMint, "You can only mint 20 Chill Bears with each transaction!");
                require(!_paused, "Minting is currently paused!");
            }

        }

        for(uint i = 0; i < _num; i++) {
            _safeMint(msg.sender, supply+i);
        }
    }

    
    function mintInitialReserve() private {
        require(!_initialReserveMinted, "Reserve has already been minted");
        uint supply = totalSupply();

        for(uint i = 0; i < 50; i++) {
            _safeMint(_giveawayWalletAddress, supply+i);
        }
        _initialReserveMinted = true;
    }
    
    function addToWhitelist(address[] memory accounts) public onlyOwner {
        for(uint i=0; i<accounts.length; i++){
            require(!_canTransferBeforeMintingIsEnabled[accounts[i]], "Already added to list");
            _canTransferBeforeMintingIsEnabled[accounts[i]] = true;
        }
    }
    
    function removeFromWhitelist(address[] memory accounts) public onlyOwner {
        for(uint i=0; i<accounts.length; i++){
            require(_canTransferBeforeMintingIsEnabled[accounts[i]], "Account not whitelisted");
            _canTransferBeforeMintingIsEnabled[accounts[i]] = false;
        }
    }
    
    
    /*giveaway wallet can mint from reserve of 200 for contests / payment for influencers*/
    function giveAwayFromReserve(address[] memory accounts) public {
        require(_reserveAmountMinted < _giveAwayReserve, "Reserve cap has been reached");
        require(msg.sender==_giveawayWalletAddress, "Function can only be called by giveawayWallet");
        
        uint supply = totalSupply();

        for(uint i = 0; i < accounts.length; i++) {
            _safeMint(accounts[i], supply+i);
           _reserveAmountMinted++;
        }
    }
    
    /*owner should also be able to giveaway as backup*/
    function ownerGiveAway(address[] memory accounts) public onlyOwner {
        require(_reserveAmountMinted < _giveAwayReserve, "Reserve cap has been reached");
        uint supply = totalSupply();

        for(uint i = 0; i < accounts.length; i++) {
            _safeMint(accounts[i], supply+i);
           _reserveAmountMinted++;
        }
    }


    //CHANGE PAUSE STATE
    function pause(bool _value) public onlyOwner {
        _paused = _value;
    }


    //GET PRICE
    function getPrice() public view returns (uint256) {
        return _price;
    }


    //RETURN ALL TOKENS OF A SPECIFIC ADDRESS
    function walletOfOwner(address _owner) external view
    returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i = 0; i < tokenCount; i++) {
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }

        return tokensId;
    }

    //RETURNS THE AMOUNT OF TOKES THAT HAVE BEEN MINTED ALREADY
    function returnSupply() external view returns (uint256) {
        return totalSupply();
    }

    //WITHDRAW CONTRACT BALANCE TO DEFINED ADDRESS
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(_withdrawAddress).transfer(balance);
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }
    
    function togglePresaleEnabled(bool enabled) public onlyOwner{
        _presaleEnabled = enabled;
    }
    
     function canTransferBeforeMintingIsEnabled(address account) public view returns(bool) {
        return _canTransferBeforeMintingIsEnabled[account];
    }

}
//TODO
// CHANGE WITHDRAW ADDRESS
function ownerGiveAway(address[] memory accounts) public onlyOwner {
    require(_reserveAmountMinted < _giveAwayReserve, "Reserve cap has been reached");
    uint supply = totalSupply();

    for(uint i = 0; i < accounts.length; i++) {
        _safeMint(accounts[i], supply+i);
       _reserveAmountMinted++;
    }
}

/*owner should also be able to giveaway as backup*/