contract ArtBtc is ERC721Full, Ownable {
    using Strings for string;
    address proxyRegistryAddress;
    uint256 private _currentTokenId = 0;
    address payable beneficiary;
    uint256 public constant TOKEN_LIMIT = 10000;
    uint256 public salePrice;
    bool public publicSale;
    uint256 public saleLimit;
    uint256 public numSales;
    uint256 public saleBeginTime;
    uint256 public saleDuration;
    /**
     * baseUri
     */
    string public Uri;

    string[] private ipfsList;

    function pushMetaData(string[] memory _ipfsList)
        public
        onlyOwner
        reentrancyGuard
    {
        require(ipfsList.length <= 200, "Invalid List size");
        for (uint256 i = 0; i < _ipfsList.length; i++) {
            ipfsList.push(_ipfsList[i]);
        }
    }

    constructor(
        string memory _name,
        string memory _symbol,
        address _proxyRegistryAddress,
        address payable _beneficiary
    ) public ERC721Full(_name, _symbol) {
        proxyRegistryAddress = _proxyRegistryAddress;
        beneficiary = _beneficiary;
    }

    /**
     * @dev Mints a token to an address with a tokenURI.
     * @param _to address of the future owner of the token
     */
    function mintTo(address _to) internal returns (uint256) {
        uint256 newTokenId = _getNextTokenId();
        _mint(_to, newTokenId);
        _incrementTokenId();
        return newTokenId;
    }

    /**
     * @dev calculates the next token ID based on value of _currentTokenId
     * @return uint256 for the next token ID
     */
    function _getNextTokenId() private view returns (uint256) {
        return _currentTokenId.add(1);
    }

    /**
     * @dev increments the value of _currentTokenId
     */
    function _incrementTokenId() private {
        _currentTokenId++;
    }

    function tokenURI(uint256 _tokenId) external view returns (string memory) {
        require(_tokenId <= totalSupply() && _tokenId > 0, "Invalid tokenId");
        return Strings.strConcat(baseTokenURI(), ipfsList[--_tokenId / 50]);
    }

    /**
     * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator)
        public
        view
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }

    function baseTokenURI() public view returns (string memory) {
        return Uri;
    }

    /**
     * Update baseUri.
     */
    function updateUri(string memory _Uri) public onlyOwner {
        Uri = _Uri;
    }

    function saleIndexRange() public view returns (uint256 _min, uint256 _max) {
        uint256 num = balanceOf(address(this));
        for (uint256 i = 0; i < num; i++) {
            uint256 tokenId = tokenOfOwnerByIndex(address(this), i);
            if (i == 0) {
                _min = tokenId;
                _max = tokenId;
                continue;
            }
            if (tokenId > _max) {
                _max = tokenId;
                continue;
            }
            if (tokenId < _min) {
                _min = tokenId;
            }
        }
    }

    /**
     *@param times: Quantity purchased
     */
    function saleRandom(uint256 times)
        public
        payable
        reentrancyGuard
        returns (uint256[] memory _tokenIds)
    {
        require(times > 0 && times < 16, "Invalid times");
        uint256 size = balanceOf(address(this));
        require(size >= times, "Out of stock");

        require(publicSale, "Sale not started.");
        require(
            block.timestamp > saleBeginTime &&
                block.timestamp <= saleBeginTime.add(saleDuration),
            "Sales have not started"
        );
        uint256 totalPrice = salePrice.mul(times);
        require(numSales < saleLimit, "Sale limit reached.");
        require(msg.value >= totalPrice, "Insufficient funds to purchase.");
        if (msg.value > totalPrice) {
            msg.sender.transfer(msg.value.sub(totalPrice));
        }
        beneficiary.transfer(totalPrice);

        _tokenIds = new uint256[](times);
        for (uint256 i = 0; i < times; i++) {
            _tokenIds[i] = _tokenIdRandom();
            _transferFrom(address(this), msg.sender, _tokenIds[i]);
        }
    }

    function _tokenIdRandom() internal returns (uint256 tokenId) {
        uint256 size = balanceOf(address(this));
        require(size > 0, "Out of stock");
        uint256 index =
            uint256(
                keccak256(
                    abi.encodePacked(
                        numSales,
                        msg.sender,
                        block.difficulty,
                        block.timestamp
                    )
                )
            ) % size;
        numSales++;
        return tokenOfOwnerByIndex(address(this), index);
    }

    /**
     *@param params [_salePrice,[_saleBeginTime,_saleDuration,saleLimit,numSales]]
     */
    function unsale(bool _publicSale, uint256[] calldata params)
        external
        onlyOwner
        reentrancyGuard
    {
        publicSale = _publicSale;
        require(
            params.length == 0 ||
                params.length == 1 ||
                params.length == 3 ||
                params.length == 5,
            "Invalid params"
        );
        if (params.length > 0) {
            require(params[0] > 0, "Invalid params salePrice");
            salePrice = params[0];
        }
        if (params.length > 2) {
            require(params[1] > block.timestamp, "Invalid saleBeginTime");
            require(params[2] > 0, "Invalid saleDuration");
            saleBeginTime = params[1];
            saleDuration = params[2];
        }
        if (params.length > 3) {
            require(params[3] > 0, "Invalid saleLimit");
            saleLimit = params[3];
            if (params.length > 4) {
                numSales = params[4];
                require(params[4] >= 0, "Invalid numSales");
            }
        }
    }

    bool private reentrancyLock = false;

    /* Prevent a contract function from being reentrant-called. */
    modifier reentrancyGuard {
        if (reentrancyLock) {
            revert();
        }
        reentrancyLock = true;
        _;
        reentrancyLock = false;
    }

    function mintDev(uint256 num, bool self)
        external
        onlyOwner
        returns (uint256[] memory)
    {
        require(_currentTokenId < TOKEN_LIMIT, "Mint limit");
        uint256[] memory _rs = new uint256[](num);
        for (uint256 i = 0; i < num; i++) {
            _rs[i] = mintTo(self ? msg.sender : address(this));
        }
        return _rs;
    }

    
    function mint() external payable reentrancyGuard returns (uint256) {
        uint256 size = balanceOf(address(this));
        require(size > 0 , "Out of stock");
        require(publicSale, "Sale not started.");
        require(
            block.timestamp > saleBeginTime &&
                block.timestamp <= saleBeginTime.add(saleDuration),
            "Sales have not started"
        );
        
        require(numSales < saleLimit, "Sale limit reached.");
        require(msg.value >=  salePrice, "Insufficient funds to purchase.");
        if (msg.value > salePrice) {
            msg.sender.transfer(msg.value.sub(salePrice));
        }
        beneficiary.transfer(salePrice);
        _transferFrom(address(this), msg.sender, _tokenIdRandom());
    }
}
/**
 * @title Creature
 * Creature - a contract for my non-fungible creatures.
 */
function updateUri(string memory _Uri) public onlyOwner {
    Uri = _Uri;
}

/**
 * Update baseUri.
 */