contract MintGame is ERC1155, Ownable {

    string public name = "MintGame";
    string public symbol = "MG";
    uint public MAX_ROLLS = 8785;
    uint256 private _totalRolls;
    uint256 private _totalSupply;
    string private _baseURI;

    // prices
    mapping(uint => uint) private prices;
    mapping(uint => uint) private presalePrices;
    
    uint public salesStatus = 0; // 0 - paused, 2 - pre-sales, 1 - public sales
 
    // whitelist & luckylist
    mapping (address => uint) private whitelist;
    mapping (address => uint) private luckylist;
    uint private whitelistCount = 3;
    uint private luckylistCount = 3;

    using Strings for uint256;

    constructor() public ERC1155('') { }

    function rollDice(address _to, uint _diceCount) public payable returns (uint256){
        require(totalRolls() < MAX_ROLLS, "Exceeds limit rolls");
        require(_diceCount == 1 || _diceCount == 2, "Dice count should be 1 or 2");
        require(salesStatus != 0 || msg.sender == owner(), "Sales paused");

        if (salesStatus == 1) { // public sales
            require(msg.value == getPrice(_diceCount), "Value below price");
        }
        
        if (salesStatus == 2) { // pre-sales
            require(inWhiteListCount(msg.sender) > 0, "Not on whitelist");
            require(msg.value == getPresalePrice(_diceCount), "Value below pre-sale price");
            whitelist[msg.sender] -= 1;
        }

        uint _count = rollDice(_diceCount, msg.sender);

        // check luckylist

        if (inLuckyListCount(msg.sender) > 0){
            if (_count < 3 * _diceCount){
                _count = 3 * _diceCount;
            }
            luckylist[msg.sender] -= 1;
        }

        for(uint i = 0; i < _count; i++){
            _mint(_to, totalSupply(), 1, '');
            _totalSupply = _totalSupply + 1;
        }

        _totalRolls += 1;

        return _count;
    }

    function rollDice(uint256 _diceCount, address _seed) private view returns (uint256) {
        require(_diceCount == 1 || _diceCount == 2, "Cannot roll more than 2 dice");
        uint _value = (uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, _seed))) % (6 * _diceCount)) + 1;
        if (_diceCount == 2 && _value == 1){ _value = _value + 1; }
        return _value;
    }

    function getPrice(uint256 _diceCount) public view virtual returns (uint256) {
        require(_diceCount == 1 || _diceCount == 2, "Dice count should be 1 or 2");
        return prices[_diceCount];
    }

    function getPresalePrice(uint256 _diceCount) public view virtual returns (uint256) {
        require(_diceCount == 1 || _diceCount == 2, "Dice count should be 1 or 2");
        return presalePrices[_diceCount];
    }

    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    function totalRolls() public view virtual returns (uint256) {
        return _totalRolls;
    }

    function uri(uint256 _tokenId) public view virtual override returns (string memory) {
        require(_tokenId <= totalSupply(), "Token not exist");
        return string(abi.encodePacked(_baseURI, _tokenId.toString()));
    }

    // whitelist functions

    function inWhiteListCount(address _address) public view returns (uint) {
        return whitelist[_address];
    }
    
    function addToWhiteList(address[] memory _addreses) external onlyOwner {
        for(uint i = 0; i < _addreses.length; i++){
            whitelist[_addreses[i]] = whitelistCount;
        }
    }
    
    function deleteFromWhiteList(address[] memory _addreses) external onlyOwner {
        for(uint i = 0; i < _addreses.length; i++){
            whitelist[_addreses[i]] = 0;
        }
    }

    // luckylist functions

    function inLuckyListCount(address _address) public view returns (uint) {
        return luckylist[_address];
    }
    
    function addToLuckyList(address[] memory _addreses) external onlyOwner {
        for(uint i = 0; i < _addreses.length; i++){
            luckylist[_addreses[i]] = luckylistCount;
        }
    }
    
    function deleteFromLuckyList(address[] memory _addreses) external onlyOwner {
        for(uint i = 0; i < _addreses.length; i++){
            luckylist[_addreses[i]] = 0;
        }
    }

    // owned function

    function setBaseURI(string memory _uri) public onlyOwner {
        _baseURI = _uri;
    }

    function setStatusSales(uint _status) public onlyOwner {
        require(_status == 0 || _status == 1 || _status == 2, "Invalid status");
        salesStatus = _status;
    }

    function setPrices(uint _priceOneDice, uint _priceTwoDice, uint _presalePriceOneDice, uint _presalePriceTwoDice) public onlyOwner {
        prices[1] = _priceOneDice;
        prices[2] = _priceTwoDice;
        presalePrices[1] = _presalePriceOneDice;
        presalePrices[2] = _presalePriceTwoDice;
    }

    function withdrawAll() public payable onlyOwner {
        address cofounder = 0xF654E178C293f84209da7DfEFab0061e41F23084;
        uint256 cofounder_value = address(this).balance / 100 * 50;
        uint256 owner_value = address(this).balance - cofounder_value;
        require(payable(cofounder).send(cofounder_value));
        require(payable(msg.sender).send(owner_value));
    }

}
function inWhiteListCount(address _address) public view returns (uint) {
    return whitelist[_address];
}

// whitelist functions