contract ChainmonstersPresale is ERC721Full, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    
    struct Bundle {
        string name;
        uint256 price; // price in WEI
        bool isActive; // available for purchase?
        uint256 maxSupply;
    }
    
    struct Key {
        bool claimed;
    }
    
    uint256 public keysSold;
    uint256 public keysClaimed;
    Key[] keys;
    Bundle[] bundles;
    
    mapping(uint256 => bool) public keysToClaimed;
    mapping(uint256 => uint256) public keyToBundleId;
    mapping(uint256 => address) public keyToClaim;
    mapping(uint256 => uint256) public bundleToCurrentSupply;
    mapping(address => uint256[]) public addressToClaims; // in this limited scenario this is fine...
    
    event KeyClaimed(address owner, uint keyId, uint bundleId); // Event
    
    constructor() ERC721Full("ChainmonstersMMOPresale", "CMPS") public {
       
        
        // 3 bundles combined to 500 keys
        //
        Bundle memory _bundle1 = Bundle({
            name: "Explorer's Pack",
            price: 100000000000000000,
            isActive: true,
            maxSupply: 350
            
        });
        
        uint256 _bundleId1 = bundles.push(_bundle1);
        
        
        Bundle memory _bundle2 = Bundle({
            name: "Founder's Pack",
            price: 200000000000000000,
            isActive: true,
            maxSupply: 100
            
        });
        uint256 _bundleId2 = bundles.push(_bundle2);
        
        
        Bundle memory _bundle3 = Bundle({
            name: "Champion's Pack",
            price: 400000000000000000,
            isActive: true,
            maxSupply: 50
            
        });
        uint256 _bundleId3 = bundles.push(_bundle3);
        
    }

  function baseTokenURI() public view returns (string memory) {
    return "https://us-central1-chainmonsters-250213.cloudfunctions.net/getGameKey?id=";
  }
  
  function tokenURI(uint256 _tokenId) public view returns (string memory) {
    return append(baseTokenURI(), uint2str(_tokenId));
  }
  
   function append(string memory a, string memory c) internal pure returns (string memory) {

    return string(abi.encodePacked(a, c));

}


function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
    if (_i == 0) {
        return "0";
    }
    uint j = _i;
    uint len;
    while (j != 0) {
        len++;
        j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint k = len - 1;
    while (_i != 0) {
        bstr[k--] = byte(uint8(48 + _i % 10));
        _i /= 10;
    }
    return string(bstr);
}

function getClaimsOfPlayer(address _address) external view returns ( uint256[] memory) {
    uint256[] memory _claims = addressToClaims[_address];
    return _claims;
}
  
  // claims the game code for the caller address(!)
  // keys need to be burned first before the Steam key can be revealed(!)
  function claim(uint256 _token) public {
      require(ownerOf(_token) == msg.sender);
      require(keysToClaimed[_token] == false);
      
      keysToClaimed[_token] = true;
      keyToClaim[_token] = msg.sender;
      keysClaimed++;
      addressToClaims[msg.sender].push(_token);
      
      uint256 _bundleId = keyToBundleId[_token];
      
      _burn(_token);
      
      // emit event for backend to make Steam key visible
      emit KeyClaimed(msg.sender, _token, _bundleId);
      
      
  }
  
  function disableBundle(uint256 _bundleId)  external onlyOwner {
      
      bundles[_bundleId].isActive = false;
  }
  
  function enableBundle(uint256 _bundleId) external onlyOwner {
      
      bundles[_bundleId].isActive = true;
  }
  
  
  
  function createBundle (string memory _name, uint256 _price, bool _isActive, uint256 _maxSupply) public onlyOwner{
      
       Bundle memory _bundle = Bundle({
            name: _name,
            price: _price,
            isActive: _isActive,
            maxSupply: _maxSupply
            
        });
        uint256 _bundleId = bundles.push(_bundle);
        
        bundleToCurrentSupply[_bundleId] = 0;
      
      
  }
  
  
  
  
  
  function withdrawBalance()
        external 
        onlyOwner 
        {
            uint256 balance = address(this).balance;
            address payable _owner = address(uint160(owner()));
            _owner.transfer(balance);
        }
  
 
  
  
  
  
  function purchase(uint256 _bundleId) public payable returns (uint256) {
      
        require((bundleToCurrentSupply[_bundleId] < bundles[_bundleId].maxSupply) && bundles[_bundleId].maxSupply != 0);
        require(bundles[_bundleId].isActive == true);
        require(msg.value == bundles[_bundleId].price);
              
        _tokenIds.increment();
        keysSold++;
        
        Key memory _key = Key({claimed: false});
        
        

        uint256 newKeyId = _tokenIds.current();
        keys.push(_key);
        keyToBundleId[newKeyId] = _bundleId;
        bundleToCurrentSupply[_bundleId]++;
        _mint(msg.sender, newKeyId);
        _setTokenURI(newKeyId, baseTokenURI());

        return newKeyId;
    }
}
function claim(uint256 _token) public {
    require(ownerOf(_token) == msg.sender);
    require(keysToClaimed[_token] == false);
      
    keysToClaimed[_token] = true;
    keyToClaim[_token] = msg.sender;
    keysClaimed++;
    addressToClaims[msg.sender].push(_token);
      
    uint256 _bundleId = keyToBundleId[_token];
      
    _burn(_token);
      
    // emit event for backend to make Steam key visible
    emit KeyClaimed(msg.sender, _token, _bundleId);
      
      
}

// claims the game code for the caller address(!)
// keys need to be burned first before the Steam key can be revealed(!)