// Contract: CryptoCrab
// Target function: function train(uint256 crab) public
 // 
//                 (______)                   //
//           ...//( 00  )\.....               //
//                                            //
//              CRYPTO CRABS                  //
//                                            //
/*--------------------------------------------*/

contract CryptoCrab is ERC721, ERC721Enumerable, Ownable {

    /*--------------------------------------------*/
    //         Only 10000 first gen crabs         //
    /*--------------------------------------------*/
    // 9000 publicly mintable crabs
    uint256 public constant maxSupply = 10000;
    //Mint and breed price the same
    uint256 private _price = 0.01 ether;
    // 1000 reserved crabs (for publicity and giveaways)
    uint256 private _reserved = 1000;

    uint256 public startingIndex;
    bool private _saleStarted;
    string public baseURI;
    uint256 public bredCrabs = 0;

    // address withdrawlAddress = 0x6A15A2f7EC02471C4Ee11c12F4C15B8D7AE90A00;

    struct CrabyDNA {
        bytes32 crabyDNA;
    }

    mapping( uint256 => CrabyDNA) private _tokenDetails;

    constructor() ERC721("CryptoCrabs", "CC"){
        _saleStarted = false;
    }

    modifier whenSaleStarted() {
        require(_saleStarted);
        _;
    }

    function getTokenDetails(uint256 tokenId) public view returns (CrabyDNA memory) {
        return _tokenDetails[tokenId];
    }

    /*--------------------------------------------*/
    //         Make some random Craby DNA         //
    /*--------------------------------------------*/

    /*--------------------------------------------*/
    //               DNA Structure                //
    // ------------------------------------------ //
    // | Byte |             Data                | //
    // ------------------------------------------ //
    // |                Crab Data               | //
    // ------------------------------------------ //
    // |  0   | Stage - 0=Shell 1=Baby 2=Adult  | //
    // |  1   | Has Bred - 0=No, 1=Yes          | //
    // |  2   | Generation                      | //
    // |  3   | Crab Special Reserve            | //
    // ------------------------------------------ //
    // |                Crab Stats              | //
    // ------------------------------------------ //
    // |  4   | Cuteness                        | //
    // |  5   | Sasiness                        | //
    // |  6   | Size                            | //
    // |  7   | Speed                           | //
    // |  8   | Smarts                          | //
    // |  9   | Wisdom                          | //
    // |  10  | Power                           | //
    // |  11  | Luck                            | //
    // ------------------------------------------ //
    // |                Shell Stage             | //
    // ------------------------------------------ //
    // |  12  | Background                      | //
    // |  13  | Shell Type                      | //
    // |  14  | Shell Accessory                 | //
    // ------------------------------------------ //
    // |                Baby Stage              | //
    // ------------------------------------------ //
    // |  15  | Background                      | //
    // |  16  | Shell Type                      | //
    // |  17  | Shell Accessory                 | //
    // |  18  | Crab Type                       | //
    // |  19  | Crab Claw                       | //
    // |  20  | Crab Eyes                       | //
    // |  21  | Crab Mouth                      | //
    // |  22  | Crab Accessory                  | //
    // ------------------------------------------ //
    // |      |        Adult Stage              | //
    // |  23  | Background                      | //
    // |  24  | Shell Type                      | //
    // |  25  | Shell Accessory                 | //
    // |  26  | Crab Type                       | //
    // |  27  | Crab Claw                       | //
    // |  28  | Crab Eyes                       | //
    // |  29  | Crab Mouth                      | //
    // |  30  | Crab Accessory                  | //
    // |  31  | Overflow protection             | //
    // ------------------------------------------ //
    /*--------------------------------------------*/

    function randBytes32(uint256 tokenId) internal view returns(bytes32){
        // The premmise is that even if the DNA is psudo-random but deterministic, make it impractably so.
        // Its a tad overkill, but you can add / remove bits to adjust the complexity... Good luck!
        uint256 dna = uint256(keccak256(abi.encodePacked(               // Take the hash of
            uint256(blockhash(block.number - 2))                        // the hash of the block 2 blocks ago XOR with
            // ^ uint256(blockhash(block.number - 1)                    // the hash of the last block XOR with
            ^ block.timestamp                                           // the time stamp XOR with
            ^ block.difficulty                                          // the difficulty XOR with
            // ^ block.gaslimit                                         // the gas limit XOR with
            // ^ block.number                                           // the block number XOR with
            // ^ uint256(keccak256(abi.encodePacked(block.coinbase)))   // the hash of the coinbase XOR with
            ^ uint256(keccak256(abi.encodePacked(msg.sender)))          // the hash of the calling wallet XOR with
            // ^ uint256(keccak256(abi.encodePacked(tokenId)))          // the hash of the tokenId
            + tokenId                                                   // add the tokenId
        )));

        return bytes32(dna);
    }

    /*--------------------------------------------*/
    //         Mint some random Crabs             //
    /*--------------------------------------------*/
    function internalMint(address to, uint256 tokenId, bytes1 generation, bytes1 reserve) internal {
        bytes32 x = 0xff00000000000000000000000000000000000000000000000000000000000000;

        //Start with some random bytes
        bytes32 setCrabyDNA = randBytes32(tokenId);

        // mask out the status and other unused bytes
        setCrabyDNA &= 0x00000000ffffffffffffffffffffff000000ffffffffff000000000000000000;

        //Set generation and reserve status
        setCrabyDNA ^= (x & generation) >> 16;
        setCrabyDNA ^= (x & reserve) >> 24;

        //trim attributes to leave room for growth when evolving
        for(uint i=12; i <= 22; i++){
            uint8 byteResult = uint8(setCrabyDNA[i]);

            if( i > 17 ){
                if(byteResult > 0xf0){
                    unchecked {
                        //Crab attributes only evolve once so take off the max benifit of evolving once
                        byteResult ^= (byteResult << 4);
                    }
                    if(byteResult > uint8(setCrabyDNA[i])){byteResult = 0x00;}
                    setCrabyDNA ^= (x & bytes1(byteResult)) >> (i*8);
                }
            }else{
                if(byteResult > 0xe0){
                    unchecked {
                        //Shell and background attributes can evolve twice so take off a bit more
                        byteResult = (byteResult ^ (byteResult << 4))-0x10;
                    }
                    if(byteResult > uint8(setCrabyDNA[i])){byteResult = 0x00;}
                    setCrabyDNA ^= (x & bytes1(byteResult)) >> (i*8);
                }
            }
        }

        //Ready to Mint
        _tokenDetails[tokenId] = CrabyDNA(setCrabyDNA);
        _safeMint(to, tokenId);
    }

    //Mint from the reserve stash
    function ownerMintReserved(uint256 _nbTokens, address reciever) public onlyOwner { 
        uint256 supply = totalSupply();
        require(_nbTokens <= _reserved, "!MAXRESERVED");

        for (uint256 i; i < _nbTokens; i++) {
            internalMint(reciever, supply + i, 0x01, 0x01);
        }
    }

    //Anyone can mint when the sale is on
    function mint(uint256 _nbTokens) external payable whenSaleStarted {
        uint256 supply = totalSupply() + _reserved;
        require(_nbTokens < 21, "!<21");
        require(supply + _nbTokens <= maxSupply, "!TOKENSLEFT");
        require(_nbTokens * _price <= msg.value, "!AMOUNT");

        for (uint256 i; i < _nbTokens; i++) {
            internalMint(msg.sender, supply + i, 0x01, 0x00);
        }
    }

    /*--------------------------------------------*/
    //         Put them Crabs on sale             //
    /*--------------------------------------------*/
    function flipSaleStarted() external onlyOwner {
        _saleStarted = !_saleStarted;

        if (_saleStarted && startingIndex == 0) {
            setStartingIndex();
        }
    }

    //Check if the sale has started
    function saleStarted() public view returns(bool) {
        return _saleStarted;
    }

    //Change the bas URI if required
    function setBaseURI(string memory _URI) external onlyOwner {
        baseURI = _URI;
    }

    //Check the base URI
    function _baseURI() internal view override(ERC721) returns(string memory) {
        return baseURI;
    }

    // Make it possible to change the price: just in case
    function setPrice(uint256 _newPrice) external onlyOwner {
        _price = _newPrice;
 
    }

    //Check the price
    function getPrice() public view returns (uint256){
        return _price;
    }

    //Check how many reserved crabs are left
    function getReservedLeft() public view returns (uint256) {
        return _reserved;
    }

    //Check how many have been minted
    function getTotalMinted() public view returns (uint256) {
        return totalSupply();
    }

    // Show me my crabs!
    function walletOfOwner(address _owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }

    // Do they own that crab?
    function _owns(address user, uint256 craby) internal view returns (bool){
        uint count = balanceOf(user);
        if (count == 0){ return false;}
        for (uint i; i < count; i++){
            if (tokenOfOwnerByIndex(user, i) == craby){return true;}
        }
        return false;
    }


    // Set the starting index
    function setStartingIndex() public {
        require(startingIndex == 0, "ALREADYSET");

        // BlockHash only works for the most 256 recent blocks.
        uint256 _block_shift = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
        _block_shift =  1 + (_block_shift % 255);

        // This shouldn't happen, but just in case the blockchain gets a reboot?
        if (block.number < _block_shift) {
            _block_shift = 1;
        }

        uint256 _block_ref = block.number - _block_shift;
        startingIndex = uint(blockhash(_block_ref)) % maxSupply;

        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex + 1;
        }
    }

    // Check the starting index
    function getStartingIndex() public view returns (uint) {
        return startingIndex;
    }

    // Withdraw from the contract
    function withdraw(uint256 withdrawlAddress) public onlyOwner {
        require(payable(address(uint160(uint256(withdrawlAddress)))).send(address(this).balance));
    }

    // Make a string from a bytes32 array
    function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
        uint8 i = 0;
        bytes memory bytesArray = new bytes(64);
        for (i = 0; i < bytesArray.length; i++) {

            uint8 _f = uint8(_bytes32[i/2] & 0x0f);
            uint8 _l = uint8(_bytes32[i/2] >> 4);

            bytesArray[i] = toByte(_l);
            i = i + 1;
            bytesArray[i] = toByte(_f);
        }
        return string(bytesArray);
    }

    function toByte(uint8 _uint8) internal pure returns (bytes1) {
        if(_uint8 < 10) {
            return bytes1(_uint8 + 48);
        } else {
            return bytes1(_uint8 + 87);
        }
    }

    // Convert uint to string
    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;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }

    // Get the URI
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "!TOKEN");
        CrabyDNA storage crabyDNA = _tokenDetails[tokenId];
        string memory dnaString = bytes32ToString(crabyDNA.crabyDNA);
        string memory tokenIdString = uint2str(tokenId);
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, dnaString, "&tokenID=", tokenIdString)) : "";
    }

    /*--------------------------------------------*/
    //        Interact with crabs for gas         //
    /*--------------------------------------------*/

    // Decreases one crab stat (or any byte in any bytes32 array by any amount)
    function decreaseStat(bytes32 currentDNA, bytes32 newDNA, uint pos, uint8 amount) internal pure returns (bytes32){
        bytes32 x = 0xff00000000000000000000000000000000000000000000000000000000000000;
        if(currentDNA[pos] < bytes1(amount)){ newDNA ^= ((x & 0x00) >> (pos*8)); }else{
            newDNA ^= (x & bytes1(uint8(currentDNA[pos]) - amount)) >> (pos*8);}
        return newDNA;
    }

    // Increases one crab stat (or any byte in any bytes32 array by any amount)
    function increaseStat(bytes32 currentDNA, bytes32 newDNA, uint pos, uint8 amount) internal pure returns (bytes32){
        bytes32 x = 0xff00000000000000000000000000000000000000000000000000000000000000;
        if(currentDNA[pos] > bytes1(0xff - amount)){ newDNA ^= ((x & bytes1(0xff)) >> (pos*8)); }else{
            newDNA ^= (x & bytes1(uint8(currentDNA[pos]) + amount)) >> (pos*8);}
        return newDNA;
    }

    // Feed that crab!
    function feed(uint256 crab) public{
        require(_owns(msg.sender, crab), "!OWN");

        CrabyDNA storage crabyDNA = _tokenDetails[crab];
        bytes32 currentDNA =crabyDNA.crabyDNA;
        bytes32 newDNA;
        bytes32 statDNAMask = 0xffffffffff000000ffff00ffffffffffffffffffffffffffffffffffffffffff;
        
        newDNA ^= currentDNA & statDNAMask;

        newDNA = decreaseStat(currentDNA, newDNA, 5, 0x20);
        newDNA = increaseStat(currentDNA, newDNA, 6, 0x20);
        newDNA = decreaseStat(currentDNA, newDNA, 7, 0x20);
        newDNA = increaseStat(currentDNA, newDNA, 10, 0x20);

        crabyDNA.crabyDNA = newDNA;
    }

    // Play with the crab!
    function play(uint256 crab) public{
        require(_owns(msg.sender, crab), "!OWN");

        CrabyDNA storage crabyDNA = _tokenDetails[crab];
        bytes32 currentDNA =crabyDNA.crabyDNA;
        bytes32 newDNA;
        bytes32 statDNAMask = 0xffffffffff0000ffff0000ffffffffffffffffffffffffffffffffffffffffff;

        newDNA ^= currentDNA & statDNAMask;

        newDNA = increaseStat(currentDNA, newDNA, 5, 0x20);
        newDNA = decreaseStat(currentDNA, newDNA, 6, 0x20);
        newDNA = increaseStat(currentDNA, newDNA, 9, 0x20);
        newDNA = increaseStat(currentDNA, newDNA, 10, 0x20);

        crabyDNA.crabyDNA = newDNA;
    }