// File: AlgocracyPass.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8;

import "./iAlgocracyPass.sol";
import "./AlgocracyPassNFT.sol";
import "./AlgocracyPassRegistry.sol";

/// @title Algocracy Pass
/// @author jolan.eth

contract AlgocracyPass is AlgocracyPassNFT, AlgocracyPassRegistry {
    iAlgocracyPass public DAO;

    constructor(
        address _DAO,
        address _Core
    ) {
        DAO = iAlgocracyPass(_DAO);
        AlgocracyPassRegistry.setPassRegistration(ACCESS_LEVEL_CORE);
        AlgocracyPassNFT._mint(_Core);
    }

    function name() 
    public view returns (string memory) {
        return DAO.name();
    }

    function symbol() 
    public view returns (string memory) {
        return DAO.symbol();
    }

    function mintPassFromCollection(address to, uint256 _PRIME_REGISTRY_IDENTIFIER)
    public {
        iAlgocracyPass CollectionNFT = iAlgocracyPass(DAO.CollectionNFT());

        require(
            CollectionNFT.getCollectionContract(
                _PRIME_REGISTRY_IDENTIFIER
            ).Prime == msg.sender && !LOCK,
            "AlgocracyPass::mintPass() - msg.sender does not match requirement"
        );
            
        AlgocracyPassRegistry.setPassRegistration(
            AlgocracyPassRegistry.ACCESS_LEVEL_BASE
        );
            
        AlgocracyPassNFT._mint(to);
    }

    function mintPassFromCore(address to)
    public {
        require(
            AlgocracyPassRegistry.getAccessLevel(
                AlgocracyPassNFT.ownedBy(msg.sender)
            ) == AlgocracyPassRegistry.ACCESS_LEVEL_CORE,
            "AlgocracyPass::mintPass() - msg.sender does not match requirement"
        );
            
        AlgocracyPassRegistry.setPassRegistration(
            AlgocracyPassRegistry.ACCESS_LEVEL_BASE
        );
            
        AlgocracyPassNFT._mint(to);
    }

    function setLock()
    public {
        require(
            AlgocracyPassRegistry.getAccessLevel(
                ownedBy(msg.sender)
            ) == AlgocracyPassRegistry.ACCESS_LEVEL_CORE,
            "AlgocracyPass::setLock() - msg.sender does not have access level"
        );

        AlgocracyPassRegistry.LOCK = false;
    }

    function setOperatorPassAccessLevel(uint256 id)
    public {
        require(
            AlgocracyPassRegistry.getAccessLevel(
                ownedBy(msg.sender)
            ) == AlgocracyPassRegistry.ACCESS_LEVEL_CORE,
            "AlgocracyPass::setOperatorPassAccessLevel() - msg.sender does not have access level"
        );

        require(
            AlgocracyPassRegistry.getAccessLevel(id) == AlgocracyPassRegistry.ACCESS_LEVEL_BASE,
            "AlgocracyPass::setOperatorPassAccessLevel() - current ACCESS_LEVEL do not match"
        );

        AlgocracyPassRegistry.PassRegistry[id].ACCESS_LEVEL = AlgocracyPassRegistry.ACCESS_LEVEL_OPERATOR;
    }

    function unsetOperatorPassAccessLevel(uint256 id)
    public {
        require(
            AlgocracyPassRegistry.getAccessLevel(
                ownedBy(msg.sender)
            ) == AlgocracyPassRegistry.ACCESS_LEVEL_CORE,
            "AlgocracyPass::unsetOperatorPassAccessLevel() - msg.sender does not have access level"
        );

        require(
            AlgocracyPassRegistry.getAccessLevel(id) == AlgocracyPassRegistry.ACCESS_LEVEL_OPERATOR,
            "AlgocracyPass::unsetOperatorPassAccessLevel() - current ACCESS_LEVEL do not match"
        );

        AlgocracyPassRegistry.PassRegistry[id].ACCESS_LEVEL = AlgocracyPassRegistry.ACCESS_LEVEL_BASE;
    }
    
    function tokenURI(uint256 id)
    public view returns (string memory) {
        iAlgocracyPass PassProvider = iAlgocracyPass(DAO.PassProvider());

        require(
            AlgocracyPassNFT.exist(id),
            "AlgocracyPass::tokenUri() - id do not exist"
        );

        return PassProvider.generateMetadata(id);
    }

    function owner()
    public view returns (address) {
        return AlgocracyPassNFT.ownerOf(1);
    }
}
// File: AlgocracyPassNFT.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8;

interface iAlgocracyPassNFT {
    function onERC721Received(address operator, address from, uint256 id, bytes calldata data)
    external returns (bytes4);
}

/// @title Algocracy Pass NFT
/// @author jolan.eth

abstract contract AlgocracyPassNFT {
    uint256 supply;
    
    mapping (address => uint256) owned;
    mapping (uint256 => address) owners;
    
    mapping (uint256 => address) approvals;
    mapping (address => mapping(address => bool)) operatorApprovals;

    event Transfer(address indexed from, address indexed to, uint256 indexed id);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    event Approval(address indexed owner, address indexed approved, uint256 indexed id);

    function supportsInterface(bytes4 interfaceId)
    public pure returns (bool) {
        return interfaceId == 0x80ac58cd || interfaceId == 0x5b5e139f;
    }

    function exist(uint256 id)
    public view returns (bool) {
        return owners[id] != address(0);
    }

    function totalSupply()
    public view returns (uint256) {
        return supply == 0 ? 0 : supply - 1;
    }

    function balanceOf(address owner)
    public view returns (uint256) {
        require(
            owner != address(0),
            "AlgocracyPassNFT::balanceOf() - owner is address(0)"
        );
        return owned[owner] > 0 ? 1 : 0;
    }

    function ownerOf(uint256 id)
    public view returns (address) {
        require(
            exist(id),
            "AlgocracyPassNFT::ownerOf() - id do not exist"
        );
        return owners[id];
    }

    function ownedBy(address owner)
    public view returns (uint256) {
        uint256 id = owned[owner];
        require(
            exist(id),
            "AlgocracyPassNFT::ownedBy() - id do not exist"
        );
        return id;
    }

    function isApprovedForAll(address owner, address operator)
    public view returns (bool) {
        return operatorApprovals[owner][operator];
    }

    function getApproved(uint256 id)
    public view returns (address) {
        require(
            exist(id),
            "AlgocracyPassNFT::getApproved() - id do not exist"
        );
        return approvals[id];
    }

    function approve(address to, uint256 id)
    public {
        address owner = owners[id];
        require(
            to != owner,
            "AlgocracyPassNFT::approve() - to is owner"
        );
        require(
            owner == msg.sender ||
            operatorApprovals[owner][msg.sender],
            "AlgocracyPassNFT::approve() - msg.sender is not owner or approved"
        );
        approvals[id] = to;
        emit Approval(owner, to, id);
    }

    function setApprovalForAll(address operator, bool approved)
    public {
        require(
            operator != msg.sender,
            "AlgocracyPassNFT::setApprovalForAll() - msg.sender is operator"
        );
        operatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(address from, address to, uint256 id)
    public {
        address owner = owners[id];
        require(
            exist(id),
            "AlgocracyPassNFT::transferFrom() - id do not exist"
        );
        require(
            msg.sender == owner ||
            msg.sender == approvals[id] ||
            operatorApprovals[owner][msg.sender],
            "AlgocracyPassNFT::transferFrom() - msg.sender is not owner or approved"
        );

        _transfer(from, to, id);
    }

    function safeTransferFrom(address from, address to, uint256 id)
    public {
        address owner = owners[id];
        require(
            exist(id),
            "AlgocracyPassNFT::safeTransferFrom() - id do not exist"
        );
        require(
            msg.sender == owner ||
            msg.sender == approvals[id] ||
            operatorApprovals[owner][msg.sender],
            "AlgocracyPassNFT::safeTransferFrom() - msg.sender is not owner or approved"
        );

        _safeTransfer(from, to, id, '');
    }

    function safeTransferFrom(address from, address to, uint256 id, bytes memory data)
    public {
        address owner = owners[id];
        require(
            exist(id),
            "AlgocracyPassNFT::safeTransferFrom() - id do not exist"
        );
        require(
            msg.sender == owner ||
            msg.sender == approvals[id] ||
            operatorApprovals[owner][msg.sender],
            "AlgocracyPassNFT::safeTransferFrom() - msg.sender is not owner or approved"
        );

        _safeTransfer(from, to, id, data);
    }

    function _mint(address to)
    internal {
        require(
            balanceOf(to) == 0,
            "AlgocracyPassNFT::_mint() - balance of to is not 0"
        );

        if (supply == 0) supply = 1;        
        owned[to] = supply;
        owners[supply] = to;
        emit Transfer(address(this), to, supply++);
    }

    function _transfer(address from, address to, uint256 id)
    private {
        require(
            address(0) != to,
            "AlgocracyPassNFT::_transferFrom() - to is address(0)"
        );

        require(
            balanceOf(to) == 0,
            "AlgocracyPassNFT::_mint() - balance of to is not 0"
        );

        approve(address(0), id);
        delete owned[from];
        owned[to] = id;
        owners[id] = to;
        
        emit Transfer(from, to, id);
    }

    function _safeTransfer(address from, address to, uint256 id, bytes memory data)
    private {
        _transfer(from, to, id);
        require(
            _checkOnERC721Received(from, to, id, data),
            "AlgocracyPassNFT::_safeTransfer() - to is not ERC721 receiver"
        );
    }

    function _checkOnERC721Received(address from, address to, uint256 id, bytes memory _data)
    internal returns (bool) {
        uint256 size;

        assembly {
            size := extcodesize(to)
        }

        if (size > 0)
            try iAlgocracyPassNFT(to).onERC721Received(msg.sender, from, id, _data) returns (bytes4 retval) {
                return retval == iAlgocracyPassNFT(to).onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) revert("error ERC721Receiver");
                else assembly {
                        revert(add(32, reason), mload(reason))
                    }
            }
        else return true;
    }

}
// File: AlgocracyPassRegistry.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8;

/// @title Algocracy Pass Registry
/// @author jolan.eth

abstract contract AlgocracyPassRegistry {
    struct Pass {
        uint256 ACCESS_LEVEL;
        uint256 blockNumber;
    }

    bool public LOCK;
    
    uint256 public ACCESS_LEVEL_CORE = 42069;
    uint256 public ACCESS_LEVEL_VETOER = 444;
    uint256 public ACCESS_LEVEL_OPERATOR = 420;
    uint256 public ACCESS_LEVEL_BASE = 69;

    uint256 passIndex;
    mapping(uint256 => Pass) PassRegistry;

    function getAccessLevel(uint256 id)
    public view returns (uint256) {
        return PassRegistry[id].ACCESS_LEVEL;
    }

    function setPassRegistration(
        uint256 _ACCESS_LEVEL
    ) internal {
        if (passIndex == 0) passIndex = 1;
        Pass memory newPass = Pass(_ACCESS_LEVEL, block.number);
        PassRegistry[passIndex++] = newPass;
    }
}
// File: iAlgocracyPass.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8;

interface iAlgocracyPass {
    /// AlgocracyDAO::
    function VRFConsumer() external view returns (address);
    
    function Deployer() external view returns (address);
    function NFTFactory() external view returns (address);
    function PrimeFactory() external view returns (address);

    function PassNFT() external view returns (address);
    function CollectionNFT() external view returns (address);
    function RandomNFT() external view returns (address);

    function PassProvider() external view returns (address);
    function CollectionProvider() external view returns (address);

    function chain() external view returns (uint256);

    /// AlgocracyPass::
    function LOCK() external view returns (bool);
    function ACCESS_LEVEL_CORE() external view returns (uint256);
    function ACCESS_LEVEL_VETOER() external view returns (uint256);
    function ACCESS_LEVEL_OPERATOR() external view returns (uint256);
    function ACCESS_LEVEL_BASE() external view returns (uint256);
    function getAccessLevel(uint256) external view returns (uint256);
    function mintPassFromCollection(address, uint256) external;
    function mintPassFromEvent(address) external;
    function mintPassFromCore(address) external;
    
    /// AlgocracyCollection::
    struct Meta{string name; string cover; string description; uint256 maxSupply; uint256 blockNumber;}
    struct Mint{bool isActive;bool isRandom;bool isAllowListed;uint256 maxQuantity;uint256 price;}
    struct Module{address NFT; address Prime; address Provider;}
    function getCollectionRegistryLength() external view returns (uint256);
    function getCollectionData(uint256) external view returns (Meta memory);
    function getCollectionState(uint256) external view returns (Mint memory);
    function getCollectionContract(uint256) external view returns (Module memory);
    function setCollectionStateAllowListInternal(uint256, bool) external;
    function setCollectionStateRandomInternal(uint256, bool) external;
    function mintCollectionNFT(
        address, address, address, address,
        string memory, string memory, string memory,
        uint256
    ) external;

    /// AlgocracyRandom
    struct Random {uint256 id;uint256 provableRandom;}
    function FIXED_TIC() external view returns (uint16);
    function FIXED_GAS() external view returns (uint32);
    function FIXED_QTY() external view returns (uint32);
    function getRandomRegistry(uint256) external view returns (Random memory);
    function mintRandom(uint256, uint256) external;

    /// AlgocracyFactory::
    function deployAlgocracyNFT(address, uint256) external returns (address);
    function deployAlgocracyPrime(uint256) external returns (address);

    /// AlgocracyERC721::
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function maxSupply() external view returns (uint256);
    function totalSupply() external view returns (uint256);
    function exist(uint256) external view returns (bool);
    function balanceOf(address) external view returns (uint256);
    function ownerOf(uint256) external view returns (address);
    function ownedBy(address) external view returns (uint256);
    function mintSequentialNFT(address, uint256) external;
    function mintRandomNFT(address, uint256[] memory) external;

    /// AlgocracyProvider::
    function generateMetadata(uint256 id) external view returns (string memory);

    /// AlgocracyUtils::Regex
    function matches(string memory) external pure returns (bool);

    /// AlgocracyVRF::
    function getRequestConfig() external view returns (uint16, uint32, bytes32[] memory);
    function requestRandomWords(bytes32, uint64, uint16, uint32, uint32 ) external returns (uint256);
    function createSubscription() external returns (uint64);
    function getSubscription(uint64) external view returns (uint96, uint64, address, address[] memory);
    function requestSubscriptionOwnerTransfer(uint64, address) external;
    function acceptSubscriptionOwnerTransfer(uint64) external;
    function addConsumer(uint64, address) external;
    function removeConsumer(uint64, address) external;
    function cancelSubscription(uint64, address) external;
    function pendingRequestExists(uint64) external view returns (bool);
}