// Contract: NFTFactoryProxy
// Target function: function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool)
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;


contract NFTFactoryProxy {

    event LogicContractChanged(address _newImplementation);

    event AdminChanged(address _newAdmin);

    //address where the proxy will make the delegatecall
    bytes32 private constant logic_contract = keccak256("reneMakela.proxy.logic");
    
    bytes32 private constant proxy_admin = keccak256("reneMakela.proxy.admin");
    

    constructor() {
       bytes32 position = proxy_admin;
       address admin = msg.sender;
       assembly{
           sstore(position, admin)
       }
    }
    /**
     * @dev Setters
     */
    function setLogicContract(address _logicAddress) public onlyProxyAdmin {   
        bytes32 position = logic_contract;   
        assembly {
            sstore(position, _logicAddress)
        } 
        emit LogicContractChanged(_logicAddress);
    } 
    function setProxyAdmin(address _newAdmin) public onlyProxyAdmin  {
        bytes32 position = proxy_admin;   
        assembly {
            sstore(position, _newAdmin)
        } 
        emit AdminChanged(_newAdmin);
    }
    /**
     * @dev Getter for the logic contract address
     */
    function implementation() public view returns(address impl) {   
        bytes32 position = logic_contract;   
        assembly {
            impl := sload(position)
        } 
    } 
    /**
     * @dev Getter for the proxy admin address
     */
    function proxyAdmin() public view returns(address admin) {   
        bytes32 position = proxy_admin;   
        assembly {
            admin := sload(position)
        } 
    } 

    fallback() external payable {
        bytes32 position = logic_contract;
    assembly {
      let _target := sload(position)
      calldatacopy(0x0, 0x0, calldatasize())
      let result := delegatecall(gas(), _target, 0x0, calldatasize(), 0x0, 0)
      returndatacopy(0x0, 0x0, returndatasize())
      switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())}
        }   
    }

    modifier onlyProxyAdmin {
        require(proxyAdmin() == msg.sender);
        _;
    }
    
}

contract FactoryStorage {

  address public storage_admin;
  //address allowed to call some functions with onlyAdmin restrictions
  address public contract_admin;
  //owner of royalties
  address payable public royaltyRecipient;
  //address of the artist 
  address payable public artist;

  uint256 public NFT_ID;

  struct ModelNFT {
    string name;
    uint256 id;
    uint24 fee;
  }
  //Array with all NFT's minted
  ModelNFT [] public NFTModels;
  //map to get the amount of minted tokens according to the token id
  mapping(uint256 => uint256) public tokenAmount;

  constructor(address payable _artist) {
    artist = _artist;
    storage_admin = _msg_Sender();
    contract_admin = _msg_Sender();
    royaltyRecipient = payable(_msg_Sender());
    NFT_ID = 0;
  }
  /**
  * @dev Setter of 'storage_admin' 
  */
  function setNewAdmin(address _newAdmin) public onlyAdmin {
    require(storage_admin != _newAdmin, "this is already the address setted");
    storage_admin = _newAdmin;
  }
  /**
  * @dev Setter of 'contract_admin' 
  */
  function setNewContractAdmin(address _newAdmin) public onlyAdmin {
    require(contract_admin != _newAdmin, "this is already the address setted");
    contract_admin = _newAdmin;
  }
  /**
  * @dev Setter of 'royaltyRecipient' 
  */
  function setRoyaltyRecipient(address payable _royaltyRecipient) public onlyAdmin {
    require(royaltyRecipient != _royaltyRecipient, "this is already the address setted");
    royaltyRecipient = _royaltyRecipient;
  }
  /**
  * @dev called by 'contract_admin' when a token is minted in the logic contract and saves the new token data
  */
  function addNFT(string memory _name, uint256 _amount, uint24 _fee) public onlyContractAdmin returns (uint256){
    ModelNFT memory model = ModelNFT(_name, NFT_ID , _fee);
    NFTModels.push(model);
    tokenAmount[NFT_ID] = _amount;
    NFT_ID++;
    return (NFT_ID - 1);
  }
 /**
  * @dev Returns the Artist of the collection
  */
  function getArtist() public view returns (address payable) {
    return artist;
  }
  /**
  * @dev Returns the admins
  */
  function getAdmin() public view returns (address, address) {
    return (storage_admin, contract_admin);
  }
  /**
  * @dev Returns 'royaltyRecipient'
  */
  function getRoyaltyRecipient() public view returns (address) {
    return royaltyRecipient;
  }
  /**
  * @dev Returns 'NFT_ID'
  */
  function getNFT_ID() public view returns (uint256) {
    return NFT_ID;
  }

  /**
  * @dev Returns a NFT from 'NFTModels[]'
  */
  function getNFTModel(uint256 _tokenId) public view returns (ModelNFT memory) {
    return NFTModels[_tokenId];
  }

  /**
  * @dev Returns the amount of a minted token
  */
  function getTokenAmount(uint256 _tokenId) public view returns (uint256) {
    return tokenAmount[_tokenId];
  }
  /**
  * @dev Returns all info of a minted token
  */
  function getTokenInfo(uint256 _tokenId) public view returns (string memory name, uint24 fee,uint256 amount, address payable _artist) {
    return(
      NFTModels[_tokenId].name,
      NFTModels[_tokenId].fee,
      tokenAmount[_tokenId],
      artist
    );
  }
  /**
  * @dev Returns true if the token has been minted 
  */
  function _exists(uint256 tokenId) public view returns (bool) {
    return tokenAmount[tokenId] > 0;
  }

  function _msg_Sender() internal view returns (address) {
    return msg.sender;
  }

  modifier onlyAdmin() {
    require(storage_admin == _msg_Sender());
    _;
  }
  modifier onlyContractAdmin() {
    require(contract_admin == _msg_Sender());
    _;
  }

}


/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        require(_initializing || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }
}



/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}



/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155Upgradeable is IERC165Upgradeable {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}



/**
 * @dev _Available since v3.1._
 */
interface IERC1155ReceiverUpgradeable is IERC165Upgradeable {
    /**
        @dev Handles the receipt of a single ERC1155 token type. This function is
        called at the end of a `safeTransferFrom` after the balance has been updated.
        To accept the transfer, this must return
        `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
        (i.e. 0xf23a6e61, or its own function selector).
        @param operator The address which initiated the transfer (i.e. msg.sender)
        @param from The address which previously owned the token
        @param id The ID of the token being transferred
        @param value The amount of tokens being transferred
        @param data Additional data with no specified format
        @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
    */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
        @dev Handles the receipt of a multiple ERC1155 token types. This function
        is called at the end of a `safeBatchTransferFrom` after the balances have
        been updated. To accept the transfer(s), this must return
        `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
        (i.e. 0xbc197c81, or its own function selector).
        @param operator The address which initiated the batch transfer (i.e. msg.sender)
        @param from The address which previously owned the token
        @param ids An array containing ids of each token being transferred (order and length must match values array)
        @param values An array containing amounts of each token being transferred (order and length must match ids array)
        @param data Additional data with no specified format
        @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
    */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}



/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURIUpgradeable is IERC1155Upgradeable {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}




/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
    function __ERC165_init() internal initializer {
        __ERC165_init_unchained();
    }

    function __ERC165_init_unchained() internal initializer {
    }