contract Plushies is ERC721, Ownable {  

    using SafeMath for uint256;

    uint256 public constant INITIAL_PLUSHIES_CAP = 6400;

    uint256 private _saleBeginTime = 1617120000; // 30 March 2021, 16:00 GMT

    uint256 private _plushiesCap = INITIAL_PLUSHIES_CAP;

    uint256 private _accsPrice = 1*(10 ** 16);  // 0,01 ETH
    uint256 private _shinePrice = 5*(10 ** 16);  // 0,05 ETH
    uint256 private _wrapPrice = (10 ** 16);  // 0,01 ETH

    address private safeAddress;

    mapping (uint256 => string) private plushiesNames;
    mapping (uint256 => uint256) private plushiesAccessories;
    mapping (uint256 => uint256) private plushiesShine;
    mapping (uint256 => bool) private plushiesWrapped;

    constructor(address _safeAddress, string memory myBase) ERC721("PLUSHIES", "PLSHS") {
        _setBaseURI(myBase);
        safeAddress = _safeAddress;
    }

    modifier isOwnerOfPlushie(uint _tokenId) {
        require(msg.sender == ownerOf(_tokenId), "You do not own this Plushie");
        _;
    }

    /**
    * @dev Plushies vending machine
    */
    function grabPlushie(uint256 _numOfPlushies) 
	public 
	payable 
    {
        require(totalSupply() < _plushiesCap, "All Plushies are already grabbed");
        require(_numOfPlushies > 0 && _numOfPlushies <= 20, "Min 1 plushie. Max 20 dont be greedy");
        require(totalSupply().add(_numOfPlushies) <= _plushiesCap, "Exceeds Capacity, grab less, please");
        require(msg.value >= getPlushiePrice().mul(_numOfPlushies), "You havent sent enough chatloos");
        
        for (uint256 i = 0; i < _numOfPlushies; i++) {
            uint mintIndex = totalSupply().add(1); //we start from tokenId = 1, not from 0
            _safeMint(msg.sender, mintIndex);
        }                        
    }

    /***************************************************************************
    *
    * Plushies management block
    *
    ***************************************************************************/

    // Naming

    function setPlushieName(uint256 _tokenId, string memory _plushieName) public isOwnerOfPlushie(_tokenId) {
        plushiesNames[_tokenId] = _plushieName;
    }

    // Accessories

    function setPlushieAccs(uint256 _tokenId, uint256 _accsNumber) public payable isOwnerOfPlushie(_tokenId) {
            require(msg.value == getAccsPrice(), "Value sent not correct");
            plushiesAccessories[_tokenId] = _accsNumber;
    }

    function removePlushieAccs(uint256 _tokenId) public isOwnerOfPlushie(_tokenId) {
            plushiesAccessories[_tokenId] = 0;
    }

    // Shining

    function setPlushieShine(uint256 _tokenId, uint256 _shineNumber) public payable isOwnerOfPlushie(_tokenId) {
            require(msg.value == getShinePrice(), "Value sent not correct");
            plushiesShine[_tokenId] = _shineNumber;
    }

    function removePlushieShine(uint256 _tokenId) public isOwnerOfPlushie(_tokenId) {
            plushiesShine[_tokenId] = 0;
    }

    // Wrapping

    function wrapPlushie(uint256 _tokenId) public payable isOwnerOfPlushie(_tokenId) {
            require(msg.value == getWrapPrice(), "Value sent not correct");
            plushiesWrapped[_tokenId] = true;
    }

    function unwrapPlushie(uint256 _tokenId) public isOwnerOfPlushie(_tokenId) {
            plushiesWrapped[_tokenId] = false;
    }

    /***************************************************************************
    *
    * General contract management block
    *
    ***************************************************************************/

    /**
    * @dev Sets new capacity
    */
    function setNewCap(uint256 _newCap) public onlyOwner {
        _plushiesCap = _newCap;
    }

    /**
     *  @dev sets new base URI in case old is broken
     * baseURI is an URI for the json with the metadata for a token
    */ 
    function _setNewBaseURI(string memory _newBaseURI) external onlyOwner {
        _setBaseURI(_newBaseURI); 
    }

    /***************************************************************************
    * Sale start time management block
    ***************************************************************************/

    /**
    * @dev Check sale start time
    */
    function getSaleStartTime() public view returns(uint256) {
        return _saleBeginTime;
    }

    /**
    * @dev Set sale start time
    */
    function setSaleStartTime(uint256 _newSaleStartTime) onlyOwner public {
        _saleBeginTime = _newSaleStartTime;
    }

    /***************************************************************************
    * Accessories and wrapping prices management
    ***************************************************************************/

    /**
    * @dev Check accs price
    */
    function getAccsPrice() public view returns(uint256) {
        return _accsPrice;
    }

    /**
    * @dev Set accs price
    */
    function setAccsPrice(uint256 _newAccsPrice) onlyOwner public {
        _accsPrice = _newAccsPrice;
    }

    /**
    * @dev Check shine price
    */
    function getShinePrice() public view returns(uint256) {
        return _shinePrice;
    }

    /**
    * @dev Set shine price
    */
    function setShinePrice(uint256 _newShinePrice) onlyOwner public {
        _shinePrice = _newShinePrice;
    }

    /**
    * @dev Check wrap price
    */
    function getWrapPrice() public view returns(uint256) {
        return _wrapPrice;
    }

    /**
    * @dev Set wrap price
    */
    function setWrapPrice(uint256 _newWrapPrice) onlyOwner public {
        _wrapPrice = _newWrapPrice;
    }


    /***************************************************************************
    *
    * SOME MORE GETS
    *
    ***************************************************************************/

    /**
    * @dev returns Capacity
    */
    function getCapacity() public view returns(uint256) {
        return _plushiesCap;
    }

    /**
    * @dev returns Plushie name
    */
    function getPlushieName(uint256 _tokenId) public view returns(string memory) {
        return plushiesNames[_tokenId];
    }

    /**
    * @dev returns accessorie number for the Plushie
    */
    function getAccsOfPlushie(uint256 _tokenId) public view returns(uint256) {
        return plushiesAccessories[_tokenId];
    }

    /**
    * @dev returns shine number for the Plushie
    */
    function getShineOfPlushie(uint256 _tokenId) public view returns(uint256) {
        return plushiesShine[_tokenId];
    }

    /**
    * @dev returns Plushie wrapped status
    */
    function isPlushieWrapped(uint256 _tokenId) public view returns(bool) {
        return plushiesWrapped[_tokenId];
    }


    /**
    * @dev returns safeAccount address
    */
    function getSafeAccountAddress() onlyOwner public view returns(address) {
        return safeAddress;
    }

    /**
    * @dev returns array of tokenNumbers for the _owner
    */

    function getTokensOfOwner(address _owner) external view returns(uint256[] memory ) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }

    /**
    * @dev Gets current Plushie price.
    */

    function getPlushiePrice() public view returns (uint256) {
        
        require(block.timestamp >= _saleBeginTime, "Sale has not started yet");
        
        require(totalSupply() < _plushiesCap, "All Plushies are already owned");

        uint currentSupply = totalSupply();

        if (currentSupply >= 6300) {
            return 1000000000000000000; // 6301  - 6400 1 ETH
        } else if (currentSupply >= 6000) {
            return 700000000000000000; // 6001 - 6300 0.7 ETH
        } else if (currentSupply >= 5000) {
            return 500000000000000000; // 5001 - 6000 0.5 ETH
        } else if (currentSupply >= 4500) {
            return 200000000000000000; // 4501 - 5000 0.2 ETH
        } else if (currentSupply >= 4000) {
            return 170000000000000000; // 4001 - 4500 0.17 ETH
        } else if (currentSupply >= 3500) {
            return 150000000000000000; // 3501 - 4000 0.15 ETH
        } else if (currentSupply >= 1000) {
            return 50000000000000000 + (( 1 + (currentSupply - 1000) / 500) * (10 ** 16)); // 1001 - 3500 Increase 0.01 each 500
        } else if (currentSupply >= 500) {
            return 50000000000000000; // 501 - 1000 0.05 ETH
        } else {
            return 20000000000000000; // 1 - 500 0.02 ETH 
        }
    }

    /***************************************************************************
    * Finances block
    ***************************************************************************/

    /**
    * @dev Check contract balance
    */
    function getContractBalance() public view returns(uint256) {
        uint256 balance = address(this).balance;
        return balance;
    }

    /**
     * @dev Withdraw ether from this contract 
    */
    function withdraw() onlyOwner public {
        uint256 balance = address(this).balance;
        msg.sender.transfer(balance);
    }
    
    /**
     * @dev Withdraw to the SafeAddress 
    */
    function withdrawToSafeAddress() onlyOwner public {
	    uint256 balance = address(this).balance;
	    payable(safeAddress).transfer(balance);
    }

    /**
    * @dev Admin key to vending machine
    */
    function givePlushie(address _toAddress) onlyOwner
	public 
    {
        require(totalSupply() < _plushiesCap, "All Plushies are already grabbed");
        uint mintIndex = totalSupply().add(1); //we start from tokenId = 1, not from 0
        _safeMint(_toAddress, mintIndex);
                                
    }

    function setPlushieAccsAdmin(uint256 _tokenId, uint256 _accsNumber) public onlyOwner {
            plushiesAccessories[_tokenId] = _accsNumber;
    }

    function setPlushieShineAdmin(uint256 _tokenId, uint256 _shineNumber) public onlyOwner {
            plushiesShine[_tokenId] = _shineNumber;
    }

    function wrapUnwrapPlushieAdmin(uint256 _tokenId, bool _wrapStatus) public onlyOwner {
            plushiesWrapped[_tokenId] = _wrapStatus;
    }

}
function getPlushieName(uint256 _tokenId) public view returns(string memory) {
    return plushiesNames[_tokenId];
}

/**
 * @dev returns Plushie name
 */