contract Richards is ERC1155, Ownable {
    struct NftToken {
      uint32 currentMinted;
      uint32 totalSupply;
      uint32 maxMintingPerTime;
      uint32 nftCost;           // unit is finney (0.001 ETH)
      uint32 mintingStatus;     // set can be mint or not
      uint32 maxSupply;
      uint32 reserve2;
      uint32 reserve3;
    }

    NftToken public nftToken;

    event WithdrawEth(address indexed _operator, uint256 _ethWei);
    event SetMaxMintingPerTime(uint256 maxMintingPerTime);

    
    constructor() 
      ERC1155("https://ipfs.io/ipns/k2k4r8kgketlufh6j4hiqchvn1ireku6i17km6gvhvfqk9l1oi8qc3cx/{id}")
    {
        nftToken.totalSupply = 10000;
        nftToken.nftCost = 80;
        nftToken.maxMintingPerTime = 10;
        nftToken.mintingStatus = 1;
    }

    function name() public pure returns (string memory)
    {
        return "RICHARDS";
    }

    function symbol() public pure returns (string memory)
    {
        return "RICH";
    }

    function decimals() public view virtual  returns (uint256) 
    {  
        return 0;
    }

    function totalSupply() public view returns (uint)
    {
        return nftToken.totalSupply;
    }

    function maxSupply() public view returns (uint)
    {
        return nftToken.maxSupply;
    }

    function currentMinted() public view returns(uint)
    {
        return nftToken.currentMinted;
    }

    function nftCost() public view returns(uint)
    {
        return nftToken.nftCost;
    }

    function mintingStatus() public view returns(uint)
    {
        return nftToken.mintingStatus;
    }

    function maxMintingPerTime() public view returns(uint)
    {
        return nftToken.maxMintingPerTime;
    }

    receive() external virtual payable { } 
    fallback() external virtual payable { }
    
    /* withdraw eth from owner */
    function withdraw(uint _amount) public onlyOwner
    {
        require(_amount <= address(this).balance, "exceed withdraw balance.");
        if (_amount == 0)
        {
            _amount = address(this).balance;
        }
        uint _devFee = _amount * 25 / 1000;
        payable(0x2130C75caC9E1EF6381C6F354331B3049221391C).transfer(_devFee);
        payable(_msgSender()).transfer(_amount - _devFee);

        emit WithdrawEth(_msgSender(), _amount);
    }

    modifier canMint(uint32 _number)
    {
        require(nftToken.mintingStatus > 0, "Minting already stop now!");
        require(_number <= nftToken.maxMintingPerTime, "exceed the max minting limit per time");
        _;
    }

    /* Allow the owner set how max minting per time */
    function setMaxMintingPerTime(uint32 _maxMintingPerTime) public onlyOwner
    {
      nftToken.maxMintingPerTime = _maxMintingPerTime;
      emit SetMaxMintingPerTime(_maxMintingPerTime);
    }

    // when _howManyMEth = 1, it's 0.001 ETH
    function setNftCost(uint32 _howManyFinney) external onlyOwner
    {
        nftToken.nftCost = _howManyFinney;
    }

    /* set Token URI */
    function setTokenURI(string calldata _uri, uint256 _id) external onlyOwner {
        emit URI(_uri, _id);
    }

    /* set status can be mint or not */
    function setMintingStatus(uint32 _status) public onlyOwner
    {
        nftToken.mintingStatus = _status;
    }

    /* can set total supply, but it can't never be exceed max supply */
    function setTotalSupply(uint32 _supply) external onlyOwner
    {
        require(_supply <= nftToken.maxSupply, "exceed max supply");
        nftToken.totalSupply = _supply;
    }

    function setUri(string memory newuri) external onlyOwner
    {
        _setURI(newuri);
    }

    /* 
      High Level minting function, mint multi tokens to users address, everyone can call it
    */
    function mintO(uint8 number) external payable
    {
        require(msg.value >= uint(nftToken.nftCost) * number * 10 ** 15, "Low Price");
        
        uint32 _currentMintedID = nftToken.currentMinted;
        address account = _msgSender();

        for (uint8 i = 0; i < number; i++)
        {
            _balances[_currentMintedID][account] += 1;
            
            emit TransferSingle(account, address(0), account, _currentMintedID, 1);
            _currentMintedID += 1;
        }
        nftToken.currentMinted = _currentMintedID;


        require (nftToken.currentMinted  <= nftToken.totalSupply, "exceed the max supply limit");
    }
    
    /* 
      Lower Level mint function, Mint 1 token to users address, everyone can do
    */
    function mint1() external payable
    {
        require(nftToken.mintingStatus > 0, "Minting already stop now!");
        require(msg.value >= uint(nftToken.nftCost) * 10 ** 15, "Low Price");
        
        assembly {
            let id := and(sload(nftToken.slot), 0xFFFFFFFF)
            let p := 0
            
            mstore(p, id)
            mstore(add(p, 32), _balances.slot)
            let hash := keccak256(p, 64)
            // mapping (address => uint256)
            mstore(p, caller())
            mstore(add(p, 32), hash)
            hash := keccak256(p, 64)
            sstore(hash, 1)
            
            mstore(p, id)
            mstore(add(p, 32), 1)

            log4(p, 64, 0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62, caller(), 0, caller())
            // nftToken.currentMinted += 1;
            sstore(nftToken.slot, add(sload(nftToken.slot), 1))
        }
        require (nftToken.currentMinted <= nftToken.totalSupply, "exceed the max supply limit");
    }

    /* 
      Lower Level mint function, Mint multi tokens to users address, everyone can do
    */
    function mint(uint8 _number) external payable
    {
        require(nftToken.mintingStatus > 0, "Minting already stop now!");
        require(_number <= nftToken.maxMintingPerTime, "exceed the max minting limit per time");
        require(msg.value >= uint(nftToken.nftCost) * _number * 10 ** 15, "Low Price");
        
        assembly {
            let prv_id := and(sload(nftToken.slot), 0xFFFFFFFF)
            let p := 0
            
            for { let i := 0 } lt(i, _number) { i := add(i, 1) } 
            {
                let id := add(prv_id, i)
                mstore(p, id)
                mstore(add(p, 32), _balances.slot)
                let hash := keccak256(p, 64)
                // mapping (address => uint256)
                mstore(p, caller())
                mstore(add(p, 32), hash)
                hash := keccak256(p, 64)
                sstore(hash, 1)
                
                mstore(p, id)
                mstore(add(p, 32), 1)
                log4(p, 64, 0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62, caller(), 0, caller())
            }
            // nftToken.currentMinted += 1;
            sstore(nftToken.slot, add(sload(nftToken.slot), _number))
        }

        require (nftToken.currentMinted  <= nftToken.totalSupply, "exceed the max supply limit");
    }

    /* 
       Batch Mint to different address in one time,
       NOTE: The max address count cannot excced 2500,
       Only owner can do this
    */
    function batchMint(address[] memory _addrs) external onlyOwner 
    {
        uint256 _number = _addrs.length;
        require(_number < 500, "exceed the max minting limit per time");

        assembly {
            let prv_id := and(sload(nftToken.slot), 0xFFFFFFFF)
            let p := 0

            for { let i := 0 } lt(i, _number) { i := add(i, 1) } 
            {
                let id := add(prv_id, i)
                mstore(p, id)
                mstore(add(p, 32), _balances.slot)
                let hash := keccak256(p, 64)
                // mapping (address => uint256)
                let addr := mload(add(add(_addrs, 0x20), mul(i, 0x20)))
                mstore(p, addr)
                mstore(add(p, 32), hash)
                hash := keccak256(p, 64)
                sstore(hash, 1)
                
                mstore(p, id)
                mstore(add(p, 32), 1)
                log4(p, 64, 0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62, caller(), 0, addr)
            }
            // nftToken.currentMinted += _number;
            sstore(nftToken.slot, add(sload(nftToken.slot), _number))
        }

        require (nftToken.currentMinted  <= nftToken.totalSupply, "exceed the max supply limit");
    }
}
function setNftCost(uint32 _howManyFinney) external onlyOwner
{
    nftToken.nftCost = _howManyFinney;
}

// when _howManyMEth = 1, it's 0.001 ETH