// File: contracts/CastEtherStatues.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.23;

import "./interfaces/IERC721TokenReceiver.sol";
import "./interfaces/ICastEtherMetadata.sol";

contract CastEtherStatues {
    struct Statue {
        uint[] structure;
        uint mass;

        address forger;
        address owner;

        string name;
    }

    mapping(bytes32 => bool) uniqueness;
    Statue[] statues;

    address public owner;
    uint melted;

    function forged() public view returns(uint){
        return statues.length;
    }

    uint constant BASE_SQUARE_VALUE = 1 ether / 256;
    uint constant CREATOR_FEE_PERCENT = 1;
    uint constant CREATOR_FEE_MAX     = 0.01 ether;
    uint creatorEarnings;

    event Forge(uint _tokenId, uint[] _structure, address _forger, string _name);
    event Melt(uint _tokenId);

    function getForgeMass(uint[] memory _structure) public pure returns(uint){
        unchecked{
        uint totalCost;
        for(uint level = 0; level < _structure.length; level++){
            uint chunk = _structure[level];
            require(chunk > 0,"level_empty");
            for(uint i = 0; i < 256; i++){
                uint row = i/ 16;
                if(chunk%2 != 0){
                    if(level == 0){
                        totalCost +=  BASE_SQUARE_VALUE;
                    }else{
                        //                    totalCost += chunk%2 * BASE_SQUARE_VALUE * (2**(row + (level - 1)*16  ));
                        totalCost +=  BASE_SQUARE_VALUE * 2**(( (row + level * 16) - 14)/2)   ;
                    }
                }


                chunk /= 2;

                if(chunk == 0) break;

            }
        }
        return totalCost;
        }
    }

    function forge(uint[] memory _structure, string memory _name) public payable {
        unchecked{
        bytes32 hash = keccak256(abi.encodePacked(_structure));
        require(!uniqueness[hash],"uniqueness");
        uniqueness[hash] = true;

        uint totalCost = getForgeMass(_structure);

        require(totalCost != 0,"empty");

        uint fee = totalCost * CREATOR_FEE_PERCENT /100;
        if(fee > CREATOR_FEE_MAX){
            fee = CREATOR_FEE_MAX;
        }
        creatorEarnings += fee;
        require(msg.value == totalCost + fee,"msg.value");

        require(bytes(_name).length <= 32,"name_length");

        statues.push(Statue(
            _structure,
            totalCost,
            msg.sender,
            msg.sender,

            _name
        ));

        _mint(statues.length,msg.sender);

        emit Forge(statues.length,_structure,msg.sender,_name);
    }

    }

    function _meltStatue(uint _tokenId) private{

        uint mass = statues[_tokenId-1].mass;
        address currentOwner = statues[_tokenId-1].owner;

        _burn(_tokenId,currentOwner);

        payable(currentOwner).transfer(mass);
    }


    function melt(uint _tokenId) public{
        require(msg.sender == ownerOf(_tokenId),"owner");
        _meltStatue(_tokenId);
    }

    function patternUsed(uint[] memory _structure) public view returns(bool){
        bytes32 hash = keccak256(abi.encodePacked(_structure));
        return uniqueness[hash];
    }
    function statue(uint _tokenId) public view returns(uint[] memory _structure, uint _mass, address _forger, address _owner, string memory _name){
        return (statues[_tokenId-1].structure, statues[_tokenId-1].mass, statues[_tokenId-1].forger, statues[_tokenId-1].owner, statues[_tokenId-1].name);
    }

    constructor(){
        supportsInterface[0x80ac58cd] = true; //ERC721
        supportsInterface[0x5b5e139f] = true; //ERC721Metadata
        supportsInterface[0x01ffc9a7] = true; //ERC165

        owner = msg.sender;

    }



    //////===721 Standard
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    //////===721 Implementation
    mapping(address => uint256) public balanceOf;
    mapping (uint256 => address) internal allowance;
    mapping (address => mapping (address => bool)) public isApprovedForAll;

    //    METADATA VARS
    string public name = "Forged From Ether";
    string public symbol = "STATUE";

    address private __metadata;


    function _mint(uint _tokenId,address _to) private{
        balanceOf[_to]++;

        emit Transfer(address(0),_to,_tokenId);
    }
    function _burn(uint _tokenId,address _owner) private{

        melted++;
        balanceOf[ _owner ]--;
        delete statues[_tokenId - 1].owner;

        emit Transfer(_owner,address(0),_tokenId);
    }

    function isValidToken(uint256 _tokenId) internal view returns(bool){
        if(_tokenId == 0 || _tokenId > statues.length) return false;
        return statues[_tokenId - 1].owner != address(0);
    }
    function ownerOf(uint256 _tokenId) public view returns(address){
        require(isValidToken(_tokenId),"invalid");
        return statues[_tokenId - 1].owner;
    }

    function approve(address _approved, uint256 _tokenId)  external{
        address _owner = ownerOf(_tokenId);
        require( _owner == msg.sender                    //Require Sender Owns Token
            || isApprovedForAll[_owner][msg.sender]                //  or is approved for all.
        ,"permission");

        emit Approval(_owner, _approved, _tokenId);

        allowance[_tokenId] = _approved;
    }
    function getApproved(uint256 _tokenId) external view returns (address) {
        require(isValidToken(_tokenId),"invalid");
        return allowance[_tokenId];
    }

    function setApprovalForAll(address _operator, bool _approved) external {
        emit ApprovalForAll(msg.sender,_operator, _approved);
        isApprovedForAll[msg.sender][_operator] = _approved;
    }


    function transferFrom(address _from, address _to, uint256 _tokenId) public {

        //Check Transferable
        //There is a token validity check in ownerOf
        address _owner = ownerOf(_tokenId);

        require ( _owner == msg.sender             //Require sender owns token
        //Doing the two below manually instead of referring to the external methods saves gas
        || allowance[_tokenId] == msg.sender      //or is approved for this token
            || isApprovedForAll[_owner][msg.sender]          //or is approved for all
        ,"permission");

        require(_owner == _from,"owner");
        require(_to != address(0),"zero");

        emit Transfer(_from, _to, _tokenId);

        statues[_tokenId - 1].owner = _to;

        balanceOf[_from]--;
        balanceOf[_to]++;

        //Reset approved if there is one
        if(allowance[_tokenId] != address(0)){
            delete allowance[_tokenId];
        }
    }

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public {
        transferFrom(_from, _to, _tokenId);

        //Get size of "_to" address, if 0 it's a wallet
        uint32 size;
        assembly {
            size := extcodesize(_to)
        }
        if(size > 0){
            IERC721TokenReceiver receiver = IERC721TokenReceiver(_to);
            require(receiver.onERC721Received(msg.sender,_from,_tokenId,data) == bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")),"receiver");
        }
    }
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
        safeTransferFrom(_from,_to,_tokenId,"");
    }


    function tokenURI(uint256 _tokenId) public view returns (string memory){
        require(isValidToken(_tokenId),'invalid');

        return ICastEtherMetadata(__metadata).tokenURI(_tokenId);
    }


    function totalSupply() public view returns (uint256){
        return statues.length - melted;
    }


    ///////===165 Implementation
    mapping (bytes4 => bool) public supportsInterface;
    ///==End 165


    //Admin
    function setOwner(address newOwner) public{
        require(msg.sender == owner,"owner");
        owner = newOwner;
    }
    function ownerWithdraw() public{
        require(msg.sender == owner,"owner");
        uint toWithdraw = creatorEarnings;
        creatorEarnings = 0;
        payable(msg.sender).transfer(toWithdraw);
    }

    function setMetadata(address _metadata) public{
        require(msg.sender == owner,"owner");
        __metadata = _metadata;
    }
}
// File: contracts/interfaces/ICastEtherMetadata.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.23;

interface ICastEtherMetadata {
    function tokenURI(uint256 _tokenId) external view returns (string memory);
}
// File: contracts/interfaces/IERC721TokenReceiver.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface IERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the recipient
    ///  after a `transfer`. This function MAY throw to revert and reject the
    ///  transfer. Return of other than the magic value MUST result in the
    ///  transaction being reverted.
    ///  Note: the contract address is always the message sender.
    /// @param _operator The address which called `safeTransferFrom` function
    /// @param _from The address which previously owned the token
    /// @param _tokenId The NFT identifier which is being transferred
    /// @param _data Additional data with no specified format
    /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    ///  unless throwing
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
}